From b930274f009a2d29853ff605928a36701ae7f6f6 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 15:51:11 +0000 Subject: [PATCH 01/31] wip: onChainSendERC20 --- .eslintrc.js | 1 + lib/connextclient/ConnextClient.ts | 89 ++- lib/connextclient/ethprovider.ts | 54 ++ lib/db/seeds/simnet.ts | 8 +- npm-shrinkwrap.json | 889 +++++++++++++++++++++++++++++ package.json | 1 + 6 files changed, 1023 insertions(+), 19 deletions(-) create mode 100644 lib/connextclient/ethprovider.ts diff --git a/.eslintrc.js b/.eslintrc.js index 370fe370c..3228d04be 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,7 @@ module.exports = { }, rules: { 'no-empty': 'off', + "object-curly-newline": 0, }, plugins: [ '@typescript-eslint', diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 0cb803194..a21043f8d 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -3,6 +3,7 @@ import assert from 'assert'; import http from 'http'; import { combineLatest, defer, from, fromEvent, interval, Observable, of, Subscription, throwError, timer } from 'rxjs'; import { catchError, distinctUntilChanged, filter, mergeMap, mergeMapTo, pluck, take, timeout } from 'rxjs/operators'; +// import { ContractABIs } from 'boltz-core'; import { SwapClientType, SwapRole, SwapState } from '../constants/enums'; import { CurrencyInstance } from '../db/types'; import Logger from '../Logger'; @@ -43,6 +44,7 @@ import { ProvidePreimageEvent, TransferReceivedEvent, } from './types'; +import { onChainSendERC20 } from './ethprovider'; interface ConnextClient { on(event: 'preimage', listener: (preimageRequest: ProvidePreimageEvent) => void): void; @@ -377,6 +379,38 @@ class ConnextClient extends SwapClient { this.emit('connectionVerified', { newIdentifier: publicIdentifier }); this.setStatus(ClientStatus.ConnectionVerified); this.reconcileDeposit(); + /* + const provider = new ethers.providers.JsonRpcProvider( + { + url: `http://${this.host}:${this.port}/ethprovider/${CHAIN_IDENTIFIERS[this.network]}`, + }, + { + name: 'simnet', + chainId: 1337, + }, + ); + const erc20abi = [ + 'function balanceOf(address) view returns (uint)', + 'function transfer(address to, uint amount)', + ]; + const address = this.getTokenAddress('USDT'); + const signer = ethers.Wallet.fromMnemonic(this.seed).connect(provider); + const erc20 = new ethers.Contract(address, erc20abi, signer); + const balance = await erc20.balanceOf(signer.address); + const ethBalance = await provider.getBalance(signer.address); + console.log('ethBalance', ethBalance.toString()); + try { + const amountToSend = balance.div(10); + const gasPrice = await provider.getGasPrice(); + // const someRandom = ethers.Wallet.createRandom(); + const { hash } = await erc20.transfer('0xE223A8135A27733Fed9e4812f443EC15ece31BA6', amountToSend, { + gasPrice, + }); + console.log('transfer complete', hash); + } catch (e) { + this.logger.error(e); + } + */ } catch (err) { this.logger.error( `could not verify connection to connext, retrying in ${ConnextClient.RECONNECT_INTERVAL} ms`, @@ -391,7 +425,7 @@ class ConnextClient extends SwapClient { this._reconcileDepositSubscriber.unsubscribe(); } const ethBalance$ = interval(30000).pipe( - mergeMap(() => from(this.getBalanceForAddress(this.channelAddress!))), + mergeMap(() => from(this.getBalanceForAddress(this.channelAddress!, this.getTokenAddress('ETH')))), // only emit new ETH balance events when the balance changes distinctUntilChanged(), ); @@ -774,13 +808,18 @@ class ConnextClient extends SwapClient { return gweiGasPrice; }; - private getBalanceForAddress = async (assetId: string) => { - const res = await this.sendRequest(`/ethprovider/${CHAIN_IDENTIFIERS[this.network]}`, 'POST', { - method: 'eth_getBalance', - params: [assetId, 'latest'], - }); - const getBalanceResponse = await parseResponseBody(res); - return parseInt(getBalanceResponse.result, 16); + private getBalanceForAddress = async (address: string, assetId: string) => { + if (assetId === this.getTokenAddress('ETH')) { + const res = await this.sendRequest(`/ethprovider/${CHAIN_IDENTIFIERS[this.network]}`, 'POST', { + method: 'eth_getBalance', + params: [address, 'latest'], + }); + const getBalanceResponse = await parseResponseBody(res); + return parseInt(getBalanceResponse.result, 16); + } else { + // TODO: fixme + return '123'; + } }; public getInfo = async (): Promise => { @@ -912,7 +951,7 @@ class ConnextClient extends SwapClient { const tokenAddress = this.getTokenAddress(currency); getBalancePromise = Promise.all([ this.sendRequest(`/${this.publicIdentifier}/channels/${this.channelAddress}`, 'GET'), - this.getBalanceForAddress(this.signerAddress!), + this.getBalanceForAddress(this.signerAddress!, tokenAddress), ]) .then(async ([channelDetailsRes, onChainBalance]) => { const channelDetails = await parseResponseBody(channelDetailsRes); @@ -1043,13 +1082,33 @@ class ConnextClient extends SwapClient { throw new Error('either all must be true or amount must be non-zero'); } - const res = await this.sendRequest('/onchain-transfer', 'POST', { - assetId: this.getTokenAddress(currency), - amount: unitsStr, - recipient: destination, + if (!this.seed) { + throw new Error('cannot broadcast transaction without seed'); + } + + // This is a bridge between the imperative and functional code. First step is to get rid of all of the external state. + const broadcastTransaction$ = onChainSendERC20( + this.host, + this.port, + CHAIN_IDENTIFIERS[this.network], + this.seed, + this.getTokenAddress(currency), + destination, + ); + broadcastTransaction$.subscribe({ + // portal pack to imperative world + next: (transaction) => { + this.logger.info(`on-chain transfer broadcasted, transaction hash: ${transaction.hash}`); + }, + error: (e) => { + this.logger.error(`could not broadcast on-chain transfer: ${JSON.stringify(e)}`); + }, + complete: () => { + // on-chain transfer complete + }, }); - const { txhash } = await parseResponseBody(res); - return txhash; + const transaction = await broadcastTransaction$.toPromise(); + return transaction.hash; }; /** Connext client specific cleanup. */ diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts new file mode 100644 index 000000000..59419a0b1 --- /dev/null +++ b/lib/connextclient/ethprovider.ts @@ -0,0 +1,54 @@ +import { Observable, from, combineLatest } from 'rxjs'; +import { ethers, BigNumber } from 'ethers'; +import { mergeMap } from 'rxjs/operators'; + +type OnChainTransaction = { + nonce: number; + gasPrice: BigNumber; + gasLimit: BigNumber; + to: string; + value: BigNumber; + data: string; + chainId: number; + v: number; + r: string; + s: string; + from: string; + hash: string; +}; + +const onChainSendERC20 = ( + host: string, + port: number, + chainId: number, + seed: string, + contractAddress: string, + destinationAddress: string, + units: string, +): Observable => { + const provider = new ethers.providers.JsonRpcProvider( + { url: `http://${host}:${port}/ethprovider/${chainId}` }, + { + name: 'simnet', + chainId, + }, + ); + const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; + const signer = ethers.Wallet.fromMnemonic(seed).connect(provider); + const erc20 = new ethers.Contract(contractAddress, erc20abi, signer); + // convert promises to observables + const erc20balance$ = from(erc20.balanceOf(signer.address)) as Observable; + const ethBalance$ = from(provider.getBalance(signer.address)); + const gasPrice$ = from(provider.getGasPrice()); + // gather up all the observables so that we wait for each one to emit a value before + // we emit ready to transfer event + const readyToTransfer$ = combineLatest(erc20balance$, ethBalance$, gasPrice$); + return readyToTransfer$.pipe( + mergeMap(([_erc20balance, _ethBalance, gasPrice]) => { + // const amountToSend = erc20balance.div(10); + return from(erc20.transfer(destinationAddress, units, { gasPrice })) as Observable; + }), + ); +}; + +export { onChainSendERC20 }; diff --git a/lib/db/seeds/simnet.ts b/lib/db/seeds/simnet.ts index 0d68d1cd9..0c2e8545f 100644 --- a/lib/db/seeds/simnet.ts +++ b/lib/db/seeds/simnet.ts @@ -2,6 +2,7 @@ import * as db from '../types'; import { SwapClientType } from '../../constants/enums'; const nodes = [ + /* { nodePubKey: '03ece33a30db1dbce4b62fa96a5e9541138a24997ef5672eebed2d332270e39542', addresses: [ @@ -22,6 +23,7 @@ const nodes = [ }, ], }, + */ ] as db.NodeAttributes[]; const currencies = [ @@ -33,20 +35,18 @@ const currencies = [ decimalPlaces: 18, tokenAddress: '0x0000000000000000000000000000000000000000', }, - /* { id: 'USDT', swapClient: SwapClientType.Connext, decimalPlaces: 6, - tokenAddress: '0x6149AA6798a75450EFb0151204513ce197f626Ce', + tokenAddress: '0x5C533069289be37789086DB7A615ca5e963Fe5Bc', }, { id: 'DAI', swapClient: SwapClientType.Connext, decimalPlaces: 18, - tokenAddress: '0x69C3d485623bA3f382Fc0FB6756c4574d43C1618', + tokenAddress: '0x514a44ABFB7F02256eF658d31425385787498Fcd', }, - */ /* { id: 'XUC', diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 86e20ea09..28533854b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -546,6 +546,183 @@ } } }, + "@ethersproject/abi": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.9.tgz", + "integrity": "sha512-ily2OufA2DTrxkiHQw5GqbkMSnNKuwZBqKsajtT0ERhZy1r9w2CpW1bmtRMIGzaqQxCdn/GEoFogexk72cBBZQ==", + "requires": { + "@ethersproject/address": "^5.0.4", + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/hash": "^5.0.4", + "@ethersproject/keccak256": "^5.0.3", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/strings": "^5.0.4" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/abstract-provider": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.7.tgz", + "integrity": "sha512-NF16JGn6M0zZP5ZS8KtDL2Rh7yHxZbUjBIHLNHMm/0X0BephhjUWy8jqs/Zks6kDJRzNthgmPVy41Ec0RYWPYA==", + "requires": { + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/networks": "^5.0.3", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/transactions": "^5.0.5", + "@ethersproject/web": "^5.0.6" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + } + } + }, + "@ethersproject/abstract-signer": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.9.tgz", + "integrity": "sha512-CM5UNmXQaA03MyYARFDDRjHWBxujO41tVle7glf5kHcQsDDULgqSVpkliLJMtPzZjOKFeCVZBHybTZDEZg5zzg==", + "requires": { + "@ethersproject/abstract-provider": "^5.0.4", + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + } + } + }, + "@ethersproject/address": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.8.tgz", + "integrity": "sha512-V87DHiZMZR6hmFYmoGaHex0D53UEbZpW75uj8AqPbjYUmi65RB4N2LPRcJXuWuN2R0Y2CxkvW6ArijWychr5FA==", + "requires": { + "@ethersproject/bignumber": "^5.0.10", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/keccak256": "^5.0.3", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/rlp": "^5.0.3" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + } + } + }, + "@ethersproject/base64": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.6.tgz", + "integrity": "sha512-HwrGn8YMiUf7bcdVvB4NJ+eWT0BtEFpDtrYxVXEbR7p/XBSJjwiR7DEggIiRvxbualMKg+EZijQWJ3az2li0uw==", + "requires": { + "@ethersproject/bytes": "^5.0.4" + } + }, + "@ethersproject/basex": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.6.tgz", + "integrity": "sha512-Y/8dowRxBF3bsKkqEp7XN4kcFFQ0o5xxP1YyopfqkXejaOEGiD7ToQdQ0pIZpAJ5GreW56oFOTDDSO6ZcUCNYg==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/properties": "^5.0.3" + } + }, "@ethersproject/bignumber": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.6.tgz", @@ -572,6 +749,226 @@ "@ethersproject/bignumber": "^5.0.6" } }, + "@ethersproject/contracts": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.8.tgz", + "integrity": "sha512-PecBL4vnsrpuks2lzzkRsOts8csJy338HNDKDIivbFmx92BVzh3ohOOv3XsoYPSXIHQvobF959W+aSk3RCZL/g==", + "requires": { + "@ethersproject/abi": "^5.0.5", + "@ethersproject/abstract-provider": "^5.0.4", + "@ethersproject/abstract-signer": "^5.0.4", + "@ethersproject/address": "^5.0.4", + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + } + } + }, + "@ethersproject/hash": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.9.tgz", + "integrity": "sha512-e8/i2ZDeGSgCxXT0vocL54+pMbw5oX5fNjb2E3bAIvdkh5kH29M7zz1jHu1QDZnptIuvCZepIbhUH8lxKE2/SQ==", + "requires": { + "@ethersproject/abstract-signer": "^5.0.6", + "@ethersproject/address": "^5.0.5", + "@ethersproject/bignumber": "^5.0.8", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/keccak256": "^5.0.3", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.4", + "@ethersproject/strings": "^5.0.4" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/hdnode": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.7.tgz", + "integrity": "sha512-89tphqlji4y/LNE1cSaMQ3hrBtJ4lO1qWGi2hn54LiHym85DTw+zAKbA8QgmdSdJDLGR/kc9VHaIPQ+vZQ2LkQ==", + "requires": { + "@ethersproject/abstract-signer": "^5.0.4", + "@ethersproject/basex": "^5.0.3", + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/pbkdf2": "^5.0.3", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/sha2": "^5.0.3", + "@ethersproject/signing-key": "^5.0.4", + "@ethersproject/strings": "^5.0.4", + "@ethersproject/transactions": "^5.0.5", + "@ethersproject/wordlists": "^5.0.4" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/json-wallets": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.9.tgz", + "integrity": "sha512-EWuFvJd8nu90dkmJwmJddxOYCvFvMkKBsZi8rxTme2XEZsHKOFnybVkoL23u7ZtApuEfTKmVcR2PTwgZwqDsKw==", + "requires": { + "@ethersproject/abstract-signer": "^5.0.4", + "@ethersproject/address": "^5.0.4", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/hdnode": "^5.0.4", + "@ethersproject/keccak256": "^5.0.3", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/pbkdf2": "^5.0.3", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/random": "^5.0.3", + "@ethersproject/strings": "^5.0.4", + "@ethersproject/transactions": "^5.0.5", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + } + } + }, "@ethersproject/keccak256": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.3.tgz", @@ -586,6 +983,120 @@ "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.5.tgz", "integrity": "sha512-gJj72WGzQhUtCk6kfvI8elTaPOQyMvrMghp/nbz0ivTo39fZ7IjypFh/ySDeUSdBNplAwhzWKKejQhdpyefg/w==" }, + "@ethersproject/networks": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.6.tgz", + "integrity": "sha512-2Cg1N5109zzFOBfkyuPj+FfF7ioqAsRffmybJ2lrsiB5skphIAE72XNSCs4fqktlf+rwSh/5o/UXRjXxvSktZw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + }, + "@ethersproject/pbkdf2": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.6.tgz", + "integrity": "sha512-CUYciSxR/AaCoKMJk3WUW+BDhR41G3C+O9lOeZ4bR1wDhLKL2Z8p0ciF5XDEiVbmI4CToW6boVKybeVMdngRrg==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/sha2": "^5.0.3" + } + }, + "@ethersproject/properties": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.6.tgz", + "integrity": "sha512-a9DUMizYhJ0TbtuDkO9iYlb2CDlpSKqGPDr+amvlZhRspQ6jbl5Eq8jfu4SCcGlcfaTbguJmqGnyOGn1EFt6xA==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + }, + "@ethersproject/providers": { + "version": "5.0.17", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.17.tgz", + "integrity": "sha512-bJnvs5X7ttU5x2ekGJYG7R3Z+spZawLFfR0IDsbaMDLiCwZOyrgk+VTBU7amSFLT0WUhWFv8WwSUB+AryCQG1Q==", + "requires": { + "@ethersproject/abstract-provider": "^5.0.4", + "@ethersproject/abstract-signer": "^5.0.4", + "@ethersproject/address": "^5.0.4", + "@ethersproject/basex": "^5.0.3", + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/hash": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/networks": "^5.0.3", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/random": "^5.0.3", + "@ethersproject/rlp": "^5.0.3", + "@ethersproject/sha2": "^5.0.3", + "@ethersproject/strings": "^5.0.4", + "@ethersproject/transactions": "^5.0.5", + "@ethersproject/web": "^5.0.6", + "bech32": "1.1.4", + "ws": "7.2.3" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + }, + "ws": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + } + } + }, + "@ethersproject/random": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.6.tgz", + "integrity": "sha512-8nsVNaZvZ9OD5NXfzE4mmz8IH/1DYJbAR95xpRxZkIuNmfn6QlMp49ccJYZWGhs6m0Zj2+FXjx3pzXfYlo9/dA==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + }, + "@ethersproject/rlp": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.6.tgz", + "integrity": "sha512-M223MTaydfmQSsvqAl0FJZDYFlSqt6cgbhnssLDwqCKYegAHE16vrFyo+eiOapYlt32XAIJm0BXlqSunULzZuQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + }, "@ethersproject/sha2": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.3.tgz", @@ -607,6 +1118,17 @@ } } }, + "@ethersproject/signing-key": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.7.tgz", + "integrity": "sha512-JYndnhFPKH0daPcIjyhi+GMcw3srIHkQ40hGRe6DA0CdGrpMfgyfSYDQ2D8HL2lgR+Xm4SHfEB0qba6+sCyrvg==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3", + "elliptic": "6.5.3" + } + }, "@ethersproject/solidity": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.3.tgz", @@ -629,6 +1151,238 @@ "@ethersproject/logger": "^5.0.5" } }, + "@ethersproject/transactions": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.8.tgz", + "integrity": "sha512-i7NtOXVzUe+YSU6QufzlRrI2WzHaTmULAKHJv4duIZMLqzehCBXGA9lTpFgFdqGYcQJ7vOtNFC2BB2mSjmuXqg==", + "requires": { + "@ethersproject/address": "^5.0.4", + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/keccak256": "^5.0.3", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/rlp": "^5.0.3", + "@ethersproject/signing-key": "^5.0.4" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + } + } + }, + "@ethersproject/units": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.8.tgz", + "integrity": "sha512-3O4MaNHFs05vC5v2ZGqVFVWtO1WyqFejO78M7Qh16njo282aoMlENtVI6cn2B36zOLFXRvYt2pYx6xCG53qKzg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + } + }, + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + } + } + }, + "@ethersproject/wallet": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.9.tgz", + "integrity": "sha512-GfpQF56PO/945SJq7Wdg5F5U6wkxaDgkAzcgGbCW6Joz8oW8MzKItkvYCzMh+j/8gJMzFncsuqX4zg2gq3J6nQ==", + "requires": { + "@ethersproject/abstract-provider": "^5.0.4", + "@ethersproject/abstract-signer": "^5.0.4", + "@ethersproject/address": "^5.0.4", + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/hash": "^5.0.4", + "@ethersproject/hdnode": "^5.0.4", + "@ethersproject/json-wallets": "^5.0.6", + "@ethersproject/keccak256": "^5.0.3", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/random": "^5.0.3", + "@ethersproject/signing-key": "^5.0.4", + "@ethersproject/transactions": "^5.0.5", + "@ethersproject/wordlists": "^5.0.4" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + } + } + }, + "@ethersproject/web": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.11.tgz", + "integrity": "sha512-x03ihbPoN1S8Gsh9WSwxkYxUIumLi02ZEKJku1C43sxBfe+mdprWyvujzYlpuoRNfWRgNhdRDKMP8JbG6MwNGA==", + "requires": { + "@ethersproject/base64": "^5.0.3", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/strings": "^5.0.4" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/wordlists": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.7.tgz", + "integrity": "sha512-ZjQtYxm41FmHfYgpkdQG++EDcBPQWv9O6FfP6NndYRVaXaQZh6eq3sy7HQP8zCZ8dznKgy6ZyKECS8qdvnGHwA==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/hash": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "@ethersproject/properties": "^5.0.3", + "@ethersproject/strings": "^5.0.4" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + }, + "dependencies": { + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + } + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + } + } + }, "@exchangeunion/grpc-dynamic-gateway": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/@exchangeunion/grpc-dynamic-gateway/-/grpc-dynamic-gateway-0.3.10.tgz", @@ -2372,6 +3126,11 @@ "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", "dev": true }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -3224,6 +3983,11 @@ "tweetnacl": "^0.14.3" } }, + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", @@ -6474,6 +7238,126 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "ethers": { + "version": "5.0.24", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.24.tgz", + "integrity": "sha512-77CEtVC88fJGEhxGXRvQqAEH6e2A+ZFiv2FBT6ikXndlty5sw6vMatAhg1v+w3CaaGZOf1CP81jl4Mc8Zrj08A==", + "requires": { + "@ethersproject/abi": "5.0.9", + "@ethersproject/abstract-provider": "5.0.7", + "@ethersproject/abstract-signer": "5.0.9", + "@ethersproject/address": "5.0.8", + "@ethersproject/base64": "5.0.6", + "@ethersproject/basex": "5.0.6", + "@ethersproject/bignumber": "5.0.12", + "@ethersproject/bytes": "5.0.8", + "@ethersproject/constants": "5.0.7", + "@ethersproject/contracts": "5.0.8", + "@ethersproject/hash": "5.0.9", + "@ethersproject/hdnode": "5.0.7", + "@ethersproject/json-wallets": "5.0.9", + "@ethersproject/keccak256": "5.0.6", + "@ethersproject/logger": "5.0.8", + "@ethersproject/networks": "5.0.6", + "@ethersproject/pbkdf2": "5.0.6", + "@ethersproject/properties": "5.0.6", + "@ethersproject/providers": "5.0.17", + "@ethersproject/random": "5.0.6", + "@ethersproject/rlp": "5.0.6", + "@ethersproject/sha2": "5.0.6", + "@ethersproject/signing-key": "5.0.7", + "@ethersproject/solidity": "5.0.7", + "@ethersproject/strings": "5.0.7", + "@ethersproject/transactions": "5.0.8", + "@ethersproject/units": "5.0.8", + "@ethersproject/wallet": "5.0.9", + "@ethersproject/web": "5.0.11", + "@ethersproject/wordlists": "5.0.7" + }, + "dependencies": { + "@ethersproject/bignumber": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", + "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "requires": { + "@ethersproject/bytes": "^5.0.8", + "@ethersproject/logger": "^5.0.5", + "bn.js": "^4.4.0" + } + }, + "@ethersproject/bytes": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", + "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "requires": { + "@ethersproject/logger": "^5.0.5" + } + }, + "@ethersproject/constants": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", + "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "requires": { + "@ethersproject/bignumber": "^5.0.7" + } + }, + "@ethersproject/keccak256": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.6.tgz", + "integrity": "sha512-eJ4Id/i2rwrf5JXEA7a12bG1phuxjj47mPZgDUbttuNBodhSuZF2nEO5QdpaRjmlphQ8Kt9PNqY/z7lhtJptZg==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "js-sha3": "0.5.7" + } + }, + "@ethersproject/logger": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", + "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" + }, + "@ethersproject/sha2": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.6.tgz", + "integrity": "sha512-30gypDLkfkP5gE3llqi0jEuRV8m4/nvzeqmqMxiihZ7veFQHqDaGpyFeHzFim+qGeH9fq0lgYjavLvwW69+Fkw==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/logger": "^5.0.5", + "hash.js": "1.1.3" + } + }, + "@ethersproject/solidity": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.7.tgz", + "integrity": "sha512-dUevKUZ06p/VMLP/+cz4QUV+lA17NixucDJfm0ioWF0B3R0Lf+6wqwPchcqiAXlxkNFGIax7WNLgGMh4CkQ8iw==", + "requires": { + "@ethersproject/bignumber": "^5.0.7", + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/keccak256": "^5.0.3", + "@ethersproject/sha2": "^5.0.3", + "@ethersproject/strings": "^5.0.4" + } + }, + "@ethersproject/strings": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", + "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "requires": { + "@ethersproject/bytes": "^5.0.4", + "@ethersproject/constants": "^5.0.4", + "@ethersproject/logger": "^5.0.5" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.0" + } + } + } + }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -14662,6 +15546,11 @@ "xmlchars": "^2.2.0" } }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, "secp256k1": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.7.1.tgz", diff --git a/package.json b/package.json index ab7809913..f81b0deb9 100644 --- a/package.json +++ b/package.json @@ -165,6 +165,7 @@ "cross-os": "^1.3.0", "distributions-poisson-quantile": "0.0.0", "dotenv": "^8.2.0", + "ethers": "^5.0.24", "express": "^4.17.1", "fastpriorityqueue": "^0.6.3", "google-protobuf": "^3.14.0", From a582c410038d9f3a1c6d7ed0623ea0d763d64096 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 16:28:40 +0000 Subject: [PATCH 02/31] extract getProvider and getSigner from onChainSendERC20 --- lib/connextclient/ConnextClient.ts | 12 ++++++---- lib/connextclient/ethprovider.ts | 36 +++++++++++++++++------------- lib/connextclient/types.ts | 4 ---- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index a21043f8d..bc306e80f 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -40,11 +40,10 @@ import { EthproviderGasPriceResponse, ExpectedIncomingTransfer, GetBlockByNumberResponse, - OnchainTransferResponse, ProvidePreimageEvent, TransferReceivedEvent, } from './types'; -import { onChainSendERC20 } from './ethprovider'; +import { onChainSendERC20, getProvider, getSigner } from './ethprovider'; interface ConnextClient { on(event: 'preimage', listener: (preimageRequest: ProvidePreimageEvent) => void): void; @@ -1087,13 +1086,18 @@ class ConnextClient extends SwapClient { } // This is a bridge between the imperative and functional code. First step is to get rid of all of the external state. - const broadcastTransaction$ = onChainSendERC20( + const provider = getProvider( this.host, this.port, + this.network, CHAIN_IDENTIFIERS[this.network], - this.seed, + ); + const signer = getSigner(provider, this.seed); + const broadcastTransaction$ = onChainSendERC20( + signer, this.getTokenAddress(currency), destination, + units, ); broadcastTransaction$.subscribe({ // portal pack to imperative world diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index 59419a0b1..0b452bca2 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -17,29 +17,35 @@ type OnChainTransaction = { hash: string; }; -const onChainSendERC20 = ( - host: string, - port: number, - chainId: number, - seed: string, - contractAddress: string, - destinationAddress: string, - units: string, -): Observable => { - const provider = new ethers.providers.JsonRpcProvider( +const getProvider = (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { + return new ethers.providers.JsonRpcProvider( { url: `http://${host}:${port}/ethprovider/${chainId}` }, { - name: 'simnet', + name, chainId, }, ); +}; + +const getSigner = ( + provider: ethers.providers.JsonRpcProvider, + seed: string, +): ethers.Wallet => { + return ethers.Wallet.fromMnemonic(seed).connect(provider); +} + +const onChainSendERC20 = ( + signer: ethers.Wallet, + contractAddress: string, + destinationAddress: string, + units: string, +): Observable => { const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; - const signer = ethers.Wallet.fromMnemonic(seed).connect(provider); const erc20 = new ethers.Contract(contractAddress, erc20abi, signer); // convert promises to observables const erc20balance$ = from(erc20.balanceOf(signer.address)) as Observable; - const ethBalance$ = from(provider.getBalance(signer.address)); - const gasPrice$ = from(provider.getGasPrice()); + const ethBalance$ = from(signer.provider.getBalance(signer.address)); + const gasPrice$ = from(signer.provider.getGasPrice()); // gather up all the observables so that we wait for each one to emit a value before // we emit ready to transfer event const readyToTransfer$ = combineLatest(erc20balance$, ethBalance$, gasPrice$); @@ -51,4 +57,4 @@ const onChainSendERC20 = ( ); }; -export { onChainSendERC20 }; +export { getProvider, getSigner, onChainSendERC20 }; diff --git a/lib/connextclient/types.ts b/lib/connextclient/types.ts index f1e0981db..a75b7de7e 100644 --- a/lib/connextclient/types.ts +++ b/lib/connextclient/types.ts @@ -319,7 +319,3 @@ export type TransferReceivedEvent = { units: bigint; routingId: string; }; - -export type OnchainTransferResponse = { - txhash: string; -}; From 22504bdee2fe21bacf256f3f2319118ce47dcd40 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 16:29:58 +0000 Subject: [PATCH 03/31] delete old comments/code --- lib/connextclient/ConnextClient.ts | 33 ------------------------------ 1 file changed, 33 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index bc306e80f..0077cd28b 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -3,7 +3,6 @@ import assert from 'assert'; import http from 'http'; import { combineLatest, defer, from, fromEvent, interval, Observable, of, Subscription, throwError, timer } from 'rxjs'; import { catchError, distinctUntilChanged, filter, mergeMap, mergeMapTo, pluck, take, timeout } from 'rxjs/operators'; -// import { ContractABIs } from 'boltz-core'; import { SwapClientType, SwapRole, SwapState } from '../constants/enums'; import { CurrencyInstance } from '../db/types'; import Logger from '../Logger'; @@ -378,38 +377,6 @@ class ConnextClient extends SwapClient { this.emit('connectionVerified', { newIdentifier: publicIdentifier }); this.setStatus(ClientStatus.ConnectionVerified); this.reconcileDeposit(); - /* - const provider = new ethers.providers.JsonRpcProvider( - { - url: `http://${this.host}:${this.port}/ethprovider/${CHAIN_IDENTIFIERS[this.network]}`, - }, - { - name: 'simnet', - chainId: 1337, - }, - ); - const erc20abi = [ - 'function balanceOf(address) view returns (uint)', - 'function transfer(address to, uint amount)', - ]; - const address = this.getTokenAddress('USDT'); - const signer = ethers.Wallet.fromMnemonic(this.seed).connect(provider); - const erc20 = new ethers.Contract(address, erc20abi, signer); - const balance = await erc20.balanceOf(signer.address); - const ethBalance = await provider.getBalance(signer.address); - console.log('ethBalance', ethBalance.toString()); - try { - const amountToSend = balance.div(10); - const gasPrice = await provider.getGasPrice(); - // const someRandom = ethers.Wallet.createRandom(); - const { hash } = await erc20.transfer('0xE223A8135A27733Fed9e4812f443EC15ece31BA6', amountToSend, { - gasPrice, - }); - console.log('transfer complete', hash); - } catch (e) { - this.logger.error(e); - } - */ } catch (err) { this.logger.error( `could not verify connection to connext, retrying in ${ConnextClient.RECONNECT_INTERVAL} ms`, From c3d9c0b4693c954d3b95d7f919dd4999db84292f Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 16:37:18 +0000 Subject: [PATCH 04/31] extract getContract --- lib/connextclient/ConnextClient.ts | 28 ++++++++-------------------- lib/connextclient/ethprovider.ts | 15 +++++++++------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 0077cd28b..ee37d5189 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -42,7 +42,7 @@ import { ProvidePreimageEvent, TransferReceivedEvent, } from './types'; -import { onChainSendERC20, getProvider, getSigner } from './ethprovider'; +import { onChainSendERC20, getProvider, getSigner, getContract } from './ethprovider'; interface ConnextClient { on(event: 'preimage', listener: (preimageRequest: ProvidePreimageEvent) => void): void; @@ -1053,32 +1053,20 @@ class ConnextClient extends SwapClient { } // This is a bridge between the imperative and functional code. First step is to get rid of all of the external state. - const provider = getProvider( - this.host, - this.port, - this.network, - CHAIN_IDENTIFIERS[this.network], - ); + const provider = getProvider(this.host, this.port, this.network, CHAIN_IDENTIFIERS[this.network]); const signer = getSigner(provider, this.seed); - const broadcastTransaction$ = onChainSendERC20( - signer, - this.getTokenAddress(currency), - destination, - units, - ); - broadcastTransaction$.subscribe({ + const contract = getContract(signer, this.getTokenAddress(currency)); + const sendTransaction$ = onChainSendERC20(signer, contract, destination, units); + sendTransaction$.subscribe({ // portal pack to imperative world next: (transaction) => { - this.logger.info(`on-chain transfer broadcasted, transaction hash: ${transaction.hash}`); + this.logger.info(`on-chain transfer sent, transaction hash: ${transaction.hash}`); }, error: (e) => { - this.logger.error(`could not broadcast on-chain transfer: ${JSON.stringify(e)}`); - }, - complete: () => { - // on-chain transfer complete + this.logger.error(`could not send on-chain transfer: ${JSON.stringify(e)}`); }, }); - const transaction = await broadcastTransaction$.toPromise(); + const transaction = await sendTransaction$.toPromise(); return transaction.hash; }; diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index 0b452bca2..cf184aa1c 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -34,16 +34,19 @@ const getSigner = ( return ethers.Wallet.fromMnemonic(seed).connect(provider); } +const getContract = (signer: ethers.Wallet, contractAddress: string): ethers.Contract => { + const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; + return new ethers.Contract(contractAddress, erc20abi, signer); +}; + const onChainSendERC20 = ( signer: ethers.Wallet, - contractAddress: string, + contract: ethers.Contract, destinationAddress: string, units: string, ): Observable => { - const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; - const erc20 = new ethers.Contract(contractAddress, erc20abi, signer); // convert promises to observables - const erc20balance$ = from(erc20.balanceOf(signer.address)) as Observable; + const erc20balance$ = from(contract.balanceOf(signer.address)) as Observable; const ethBalance$ = from(signer.provider.getBalance(signer.address)); const gasPrice$ = from(signer.provider.getGasPrice()); // gather up all the observables so that we wait for each one to emit a value before @@ -52,9 +55,9 @@ const onChainSendERC20 = ( return readyToTransfer$.pipe( mergeMap(([_erc20balance, _ethBalance, gasPrice]) => { // const amountToSend = erc20balance.div(10); - return from(erc20.transfer(destinationAddress, units, { gasPrice })) as Observable; + return from(contract.transfer(destinationAddress, units, { gasPrice })) as Observable; }), ); }; -export { getProvider, getSigner, onChainSendERC20 }; +export { getProvider, getSigner, getContract, onChainSendERC20 }; From c038c0a3b4524714e2552ec9b31ef35da17c430b Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 16:51:02 +0000 Subject: [PATCH 05/31] extract getSigner --- lib/connextclient/ethprovider.ts | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index cf184aa1c..b99b995dd 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -1,5 +1,5 @@ -import { Observable, from, combineLatest } from 'rxjs'; -import { ethers, BigNumber } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; +import { from, Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; type OnChainTransaction = { @@ -27,12 +27,9 @@ const getProvider = (host: string, port: number, name: string, chainId: number): ); }; -const getSigner = ( - provider: ethers.providers.JsonRpcProvider, - seed: string, -): ethers.Wallet => { +const getSigner = (provider: ethers.providers.JsonRpcProvider, seed: string): ethers.Wallet => { return ethers.Wallet.fromMnemonic(seed).connect(provider); -} +}; const getContract = (signer: ethers.Wallet, contractAddress: string): ethers.Contract => { const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; @@ -45,18 +42,13 @@ const onChainSendERC20 = ( destinationAddress: string, units: string, ): Observable => { - // convert promises to observables - const erc20balance$ = from(contract.balanceOf(signer.address)) as Observable; - const ethBalance$ = from(signer.provider.getBalance(signer.address)); - const gasPrice$ = from(signer.provider.getGasPrice()); - // gather up all the observables so that we wait for each one to emit a value before - // we emit ready to transfer event - const readyToTransfer$ = combineLatest(erc20balance$, ethBalance$, gasPrice$); - return readyToTransfer$.pipe( - mergeMap(([_erc20balance, _ethBalance, gasPrice]) => { - // const amountToSend = erc20balance.div(10); - return from(contract.transfer(destinationAddress, units, { gasPrice })) as Observable; - }), + // get the gas price + return from(signer.provider.getGasPrice()).pipe( + mergeMap( + (gasPrice) => + // then send the transaction + from(contract.transfer(destinationAddress, units, { gasPrice })) as Observable, + ), ); }; From c8beac54c2abff3f381cd0f4a46338cb953dcf45 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 17:16:38 +0000 Subject: [PATCH 06/31] fix units after rebase --- lib/connextclient/ConnextClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index ee37d5189..123f90eac 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -1056,7 +1056,7 @@ class ConnextClient extends SwapClient { const provider = getProvider(this.host, this.port, this.network, CHAIN_IDENTIFIERS[this.network]); const signer = getSigner(provider, this.seed); const contract = getContract(signer, this.getTokenAddress(currency)); - const sendTransaction$ = onChainSendERC20(signer, contract, destination, units); + const sendTransaction$ = onChainSendERC20(signer, contract, destination, unitsStr); sendTransaction$.subscribe({ // portal pack to imperative world next: (transaction) => { From 69cf028399e1e834303d9dd2db1ff0a2691a6315 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 20:28:44 +0000 Subject: [PATCH 07/31] curry all ethprovider exports --- lib/connextclient/ConnextClient.ts | 2 +- lib/connextclient/ethprovider.ts | 71 +++++++++++++++++------------- npm-shrinkwrap.json | 34 ++++++++++++++ package.json | 2 + 4 files changed, 77 insertions(+), 32 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 123f90eac..83b3718e1 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -1056,7 +1056,7 @@ class ConnextClient extends SwapClient { const provider = getProvider(this.host, this.port, this.network, CHAIN_IDENTIFIERS[this.network]); const signer = getSigner(provider, this.seed); const contract = getContract(signer, this.getTokenAddress(currency)); - const sendTransaction$ = onChainSendERC20(signer, contract, destination, unitsStr); + const sendTransaction$ = onChainSendERC20(signer)(contract)(destination)(unitsStr); sendTransaction$.subscribe({ // portal pack to imperative world next: (transaction) => { diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index b99b995dd..9838a3647 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -1,6 +1,7 @@ import { BigNumber, ethers } from 'ethers'; import { from, Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; +import { curry } from 'ramda'; type OnChainTransaction = { nonce: number; @@ -17,39 +18,47 @@ type OnChainTransaction = { hash: string; }; -const getProvider = (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { - return new ethers.providers.JsonRpcProvider( - { url: `http://${host}:${port}/ethprovider/${chainId}` }, - { - name, - chainId, - }, - ); -}; +const getProvider = curry( + (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { + return new ethers.providers.JsonRpcProvider( + { url: `http://${host}:${port}/ethprovider/${chainId}` }, + { + name, + chainId, + }, + ); + }, +); -const getSigner = (provider: ethers.providers.JsonRpcProvider, seed: string): ethers.Wallet => { - return ethers.Wallet.fromMnemonic(seed).connect(provider); -}; +const getSigner = curry( + (provider: ethers.providers.JsonRpcProvider, seed: string): ethers.Wallet => { + return ethers.Wallet.fromMnemonic(seed).connect(provider); + }, +); -const getContract = (signer: ethers.Wallet, contractAddress: string): ethers.Contract => { - const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; - return new ethers.Contract(contractAddress, erc20abi, signer); -}; +const getContract = curry( + (signer: ethers.Wallet, contractAddress: string): ethers.Contract => { + const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; + return new ethers.Contract(contractAddress, erc20abi, signer); + }, +); -const onChainSendERC20 = ( - signer: ethers.Wallet, - contract: ethers.Contract, - destinationAddress: string, - units: string, -): Observable => { - // get the gas price - return from(signer.provider.getGasPrice()).pipe( - mergeMap( - (gasPrice) => - // then send the transaction - from(contract.transfer(destinationAddress, units, { gasPrice })) as Observable, - ), - ); -}; +const onChainSendERC20 = curry( + ( + signer: ethers.Wallet, + contract: ethers.Contract, + destinationAddress: string, + units: string, + ): Observable => { + // get the gas price + return from(signer.provider.getGasPrice()).pipe( + mergeMap( + (gasPrice) => + // then send the transaction + from(contract.transfer(destinationAddress, units, { gasPrice })) as Observable, + ), + ); + }, +); export { getProvider, getSigner, getContract, onChainSendERC20 }; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 28533854b..f9ba30c9e 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2497,6 +2497,7 @@ "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", + "fsevents": "~2.1.2", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -2513,6 +2514,13 @@ "to-regex-range": "^5.0.1" } }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, "glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", @@ -2678,6 +2686,14 @@ "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==", "dev": true }, + "@types/ramda": { + "version": "0.27.33", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.33.tgz", + "integrity": "sha512-uStXyYPPe9VxOe++ymWugHoMGh8SlCFGVJxuZPZG+S0WHEqE3T9hKuFAkTNo5j1Cb3lWnfxMesT2SPyx333cGQ==", + "requires": { + "ts-toolbelt": "^6.15.1" + } + }, "@types/secp256k1": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-3.5.0.tgz", @@ -10946,6 +10962,7 @@ "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", "jest-serializer": "^26.6.2", @@ -11000,6 +11017,13 @@ "to-regex-range": "^5.0.1" } }, + "fsevents": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.2.1.tgz", + "integrity": "sha512-bTLYHSeC0UH/EFXS9KqWnXuOl/wHK5Z/d+ghd5AsFMYN7wIGkUCOJyzy88+wJKkZPGON8u4Z9f6U4FdgURE9qA==", + "dev": true, + "optional": true + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -15043,6 +15067,11 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, + "ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -16983,6 +17012,11 @@ "yn": "3.1.1" } }, + "ts-toolbelt": { + "version": "6.15.5", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", diff --git a/package.json b/package.json index f81b0deb9..e975ec7c5 100644 --- a/package.json +++ b/package.json @@ -157,6 +157,7 @@ "dependencies": { "@ethersproject/solidity": "5.0.3", "@exchangeunion/grpc-dynamic-gateway": "^0.3.10", + "@types/ramda": "0.27.33", "bip39": "3.0.2", "body-parser": "^1.19.0", "chalk": "^2.4.2", @@ -175,6 +176,7 @@ "keccak": "^2.1.0", "moment": "^2.29.1", "node-forge": "^0.10.0", + "ramda": "0.27.1", "rxjs": "^6.6.3", "secp256k1": "^3.7.1", "semver": "^6.3.0", From 284b350d2f2bd98821418fc47ec5f34f63b63db2 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 15 Dec 2020 21:21:58 +0000 Subject: [PATCH 08/31] export only getEthprovider --- lib/connextclient/ConnextClient.ts | 16 +++++++--------- lib/connextclient/ethprovider.ts | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 83b3718e1..bdb0d3f80 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -42,7 +42,7 @@ import { ProvidePreimageEvent, TransferReceivedEvent, } from './types'; -import { onChainSendERC20, getProvider, getSigner, getContract } from './ethprovider'; +import { EthProvider, getEthprovider } from './ethprovider'; interface ConnextClient { on(event: 'preimage', listener: (preimageRequest: ProvidePreimageEvent) => void): void; @@ -146,6 +146,7 @@ class ConnextClient extends SwapClient { public publicIdentifier: string | undefined; /** On-chain deposit address */ public signerAddress: string | undefined; + private ethProvider: EthProvider | undefined; /** The set of hashes for outgoing transfers. */ private outgoingTransferHashes = new Set(); private port: number; @@ -212,6 +213,7 @@ class ConnextClient extends SwapClient { // Related issue: https://github.com/ExchangeUnion/xud/issues/1494 public setSeed = (seed: string) => { this.seed = seed; + this.ethProvider = getEthprovider(this.host, this.port, this.network, CHAIN_IDENTIFIERS[this.network], this.seed); }; public initConnextClient = async (seedMnemonic: string) => { @@ -1048,17 +1050,13 @@ class ConnextClient extends SwapClient { throw new Error('either all must be true or amount must be non-zero'); } - if (!this.seed) { - throw new Error('cannot broadcast transaction without seed'); + if (!this.ethProvider) { + throw new Error('cannot send transaction without ethProvider'); } - // This is a bridge between the imperative and functional code. First step is to get rid of all of the external state. - const provider = getProvider(this.host, this.port, this.network, CHAIN_IDENTIFIERS[this.network]); - const signer = getSigner(provider, this.seed); - const contract = getContract(signer, this.getTokenAddress(currency)); - const sendTransaction$ = onChainSendERC20(signer)(contract)(destination)(unitsStr); + const contract = this.ethProvider.getContract(this.getTokenAddress(currency)); + const sendTransaction$ = this.ethProvider.onChainSendERC20(contract, destination, unitsStr); sendTransaction$.subscribe({ - // portal pack to imperative world next: (transaction) => { this.logger.info(`on-chain transfer sent, transaction hash: ${transaction.hash}`); }, diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index 9838a3647..a3ea23bda 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -61,4 +61,17 @@ const onChainSendERC20 = curry( }, ); -export { getProvider, getSigner, getContract, onChainSendERC20 }; +const getEthprovider = (host: string, port: number, name: string, chainId: number, seed: string) => { + const provider = getProvider(host, port, name, chainId); + const signer = getSigner(provider, seed); + return { + getContract: getContract(signer), + onChainSendERC20: onChainSendERC20(signer), + }; +}; + +type EthProvider = ReturnType; + +export { getEthprovider, EthProvider }; + +// export { getProvider, getSigner, getContract, onChainSendERC20 }; From ed949565f9f3bce0ab1740625baf93f67a1fe97c Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 10:40:51 +0000 Subject: [PATCH 09/31] remove curry from getProvider/getSigner --- lib/connextclient/ethprovider.ts | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index a3ea23bda..7b19575b7 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -18,23 +18,19 @@ type OnChainTransaction = { hash: string; }; -const getProvider = curry( - (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { - return new ethers.providers.JsonRpcProvider( - { url: `http://${host}:${port}/ethprovider/${chainId}` }, - { - name, - chainId, - }, - ); - }, -); +const getProvider = (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { + return new ethers.providers.JsonRpcProvider( + { url: `http://${host}:${port}/ethprovider/${chainId}` }, + { + name, + chainId, + }, + ); +}; -const getSigner = curry( - (provider: ethers.providers.JsonRpcProvider, seed: string): ethers.Wallet => { - return ethers.Wallet.fromMnemonic(seed).connect(provider); - }, -); +const getSigner = (provider: ethers.providers.JsonRpcProvider, seed: string): ethers.Wallet => { + return ethers.Wallet.fromMnemonic(seed).connect(provider); +}; const getContract = curry( (signer: ethers.Wallet, contractAddress: string): ethers.Contract => { @@ -73,5 +69,3 @@ const getEthprovider = (host: string, port: number, name: string, chainId: numbe type EthProvider = ReturnType; export { getEthprovider, EthProvider }; - -// export { getProvider, getSigner, getContract, onChainSendERC20 }; From 241a1e4e5da35222377cfba0cc16462c972fe8df Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 11:51:41 +0000 Subject: [PATCH 10/31] getEthBalance --- lib/connextclient/ConnextClient.ts | 39 ++++++++++++++++++++++++------ lib/connextclient/ethprovider.ts | 1 + lib/service/Service.ts | 1 + 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index bdb0d3f80..d05f708a6 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -776,18 +776,37 @@ class ConnextClient extends SwapClient { return gweiGasPrice; }; - private getBalanceForAddress = async (address: string, assetId: string) => { + private getBalanceForAddress = async (address: string, _assetId: string) => { + /* + assert(this.ethProvider, 'Cannot get balance without ethProvider'); + const ethBalance$ = this.ethProvider.getEthBalance(); + const ethBalance = await ethBalance$.toPromise(); + console.log('ethBalance new', ethBalance.toString(), typeof ethBalance.toString()); + */ + // const val = BigInt(ethBalance.toString()); + // old + const res = await this.sendRequest(`/ethprovider/${CHAIN_IDENTIFIERS[this.network]}`, 'POST', { + method: 'eth_getBalance', + params: [address, 'latest'], + }); + const getBalanceResponse = await parseResponseBody(res); + const returnBalanec = parseInt(getBalanceResponse.result, 16); + console.log('old balance to return', returnBalanec, typeof returnBalanec); + return returnBalanec; + /* + this.ethProvider?.getEthBalance().subscribe((bal) => { + console.log('balance is', bal.toString()); + }); + console.log('returnBalanec', returnBalanec); + return returnBalanec; + */ + /* if (assetId === this.getTokenAddress('ETH')) { - const res = await this.sendRequest(`/ethprovider/${CHAIN_IDENTIFIERS[this.network]}`, 'POST', { - method: 'eth_getBalance', - params: [address, 'latest'], - }); - const getBalanceResponse = await parseResponseBody(res); - return parseInt(getBalanceResponse.result, 16); } else { // TODO: fixme - return '123'; + return 123; } + */ }; public getInfo = async (): Promise => { @@ -897,11 +916,13 @@ class ConnextClient extends SwapClient { const { freeBalanceOnChain } = await this.getBalance(currency); + console.log('getting to the conversion'); const confirmedBalanceAmount = this.unitConverter.unitsToAmount({ currency, units: BigInt(freeBalanceOnChain), }); + console.log('returning from walletBalance', confirmedBalanceAmount); return { totalBalance: confirmedBalanceAmount, confirmedBalance: confirmedBalanceAmount, @@ -1064,7 +1085,9 @@ class ConnextClient extends SwapClient { this.logger.error(`could not send on-chain transfer: ${JSON.stringify(e)}`); }, }); + console.log('sendTransaction$', sendTransaction$); const transaction = await sendTransaction$.toPromise(); + console.log('transaction is', transaction); return transaction.hash; }; diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index 7b19575b7..9ad1cab0a 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -61,6 +61,7 @@ const getEthprovider = (host: string, port: number, name: string, chainId: numbe const provider = getProvider(host, port, name, chainId); const signer = getSigner(provider, seed); return { + getEthBalance: () => from(signer.getBalance()), getContract: getContract(signer), onChainSendERC20: onChainSendERC20(signer), }; diff --git a/lib/service/Service.ts b/lib/service/Service.ts index cfbe4e36d..ff07ce720 100644 --- a/lib/service/Service.ts +++ b/lib/service/Service.ts @@ -241,6 +241,7 @@ class Service extends EventEmitter { }); } }); + console.log('returning from service'); return balances; }; From 5533309e48c15f085a05198f5f704df5c738677f Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 12:06:46 +0000 Subject: [PATCH 11/31] add getEthBalance --- lib/connextclient/ConnextClient.ts | 36 +++++------------------------- lib/connextclient/types.ts | 9 -------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index d05f708a6..0c6d81a62 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -22,7 +22,6 @@ import { UnitConverter } from '../utils/UnitConverter'; import { parseResponseBody } from '../utils/utils'; import errors, { errorCodes } from './errors'; import { - ConnextBalanceResponse, ConnextBlockNumberResponse, ConnextChannelBalanceResponse, ConnextChannelDetails, @@ -776,37 +775,14 @@ class ConnextClient extends SwapClient { return gweiGasPrice; }; - private getBalanceForAddress = async (address: string, _assetId: string) => { - /* - assert(this.ethProvider, 'Cannot get balance without ethProvider'); - const ethBalance$ = this.ethProvider.getEthBalance(); - const ethBalance = await ethBalance$.toPromise(); - console.log('ethBalance new', ethBalance.toString(), typeof ethBalance.toString()); - */ - // const val = BigInt(ethBalance.toString()); - // old - const res = await this.sendRequest(`/ethprovider/${CHAIN_IDENTIFIERS[this.network]}`, 'POST', { - method: 'eth_getBalance', - params: [address, 'latest'], - }); - const getBalanceResponse = await parseResponseBody(res); - const returnBalanec = parseInt(getBalanceResponse.result, 16); - console.log('old balance to return', returnBalanec, typeof returnBalanec); - return returnBalanec; - /* - this.ethProvider?.getEthBalance().subscribe((bal) => { - console.log('balance is', bal.toString()); - }); - console.log('returnBalanec', returnBalanec); - return returnBalanec; - */ - /* - if (assetId === this.getTokenAddress('ETH')) { + private getBalanceForAddress = async (_address: string, assetId: string) => { + if (assetId === this.tokenAddresses.get('ETH')) { + assert(this.ethProvider, 'Cannot get balance without ethProvider'); + const ethBalance$ = this.ethProvider.getEthBalance(); + return BigInt(await ethBalance$.toPromise()); } else { - // TODO: fixme - return 123; + return 0; } - */ }; public getInfo = async (): Promise => { diff --git a/lib/connextclient/types.ts b/lib/connextclient/types.ts index a75b7de7e..9cd0b3503 100644 --- a/lib/connextclient/types.ts +++ b/lib/connextclient/types.ts @@ -121,15 +121,6 @@ export type ConnextChannelBalanceResponse = { freeBalanceOnChain: string; }; -/** - * The response for ethprovider eth_getBalance call. - */ -export type ConnextBalanceResponse = { - id: number; - jsonrpc: string; - result: string; -}; - export type GetBlockByNumberResponse = { result: { difficulty: string; From de38181bd3dbbb8a072ae5f076375e2477739733 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 12:52:38 +0000 Subject: [PATCH 12/31] getERC20Balance --- lib/connextclient/ConnextClient.ts | 8 ++++++-- lib/connextclient/ethprovider.ts | 28 +++++++++------------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 0c6d81a62..6455011b5 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -776,12 +776,16 @@ class ConnextClient extends SwapClient { }; private getBalanceForAddress = async (_address: string, assetId: string) => { + assert(this.ethProvider, 'Cannot get balance without ethProvider'); if (assetId === this.tokenAddresses.get('ETH')) { - assert(this.ethProvider, 'Cannot get balance without ethProvider'); const ethBalance$ = this.ethProvider.getEthBalance(); return BigInt(await ethBalance$.toPromise()); } else { - return 0; + const contract = this.ethProvider.getContract(assetId); + const erc20balance$ = this.ethProvider.getERC20Balance(contract); + const erc20balance = (await erc20balance$.toPromise()); + console.log(`erc20balance for ${assetId} is ${erc20balance}`); + return BigInt(erc20balance); } }; diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index 9ad1cab0a..cf9eeeab4 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -1,22 +1,7 @@ -import { BigNumber, ethers } from 'ethers'; +import { ethers } from 'ethers'; +import { curry } from 'ramda'; import { from, Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; -import { curry } from 'ramda'; - -type OnChainTransaction = { - nonce: number; - gasPrice: BigNumber; - gasLimit: BigNumber; - to: string; - value: BigNumber; - data: string; - chainId: number; - v: number; - r: string; - s: string; - from: string; - hash: string; -}; const getProvider = (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { return new ethers.providers.JsonRpcProvider( @@ -45,24 +30,29 @@ const onChainSendERC20 = curry( contract: ethers.Contract, destinationAddress: string, units: string, - ): Observable => { + ): Observable => { // get the gas price return from(signer.provider.getGasPrice()).pipe( mergeMap( (gasPrice) => // then send the transaction - from(contract.transfer(destinationAddress, units, { gasPrice })) as Observable, + from(contract.transfer(destinationAddress, units, { gasPrice })) as Observable, ), ); }, ); +const getERC20Balance = curry((signer: ethers.Wallet, contract: ethers.Contract): Observable => { + return from(contract.balanceOf(signer.address)) as Observable; +}); + const getEthprovider = (host: string, port: number, name: string, chainId: number, seed: string) => { const provider = getProvider(host, port, name, chainId); const signer = getSigner(provider, seed); return { getEthBalance: () => from(signer.getBalance()), getContract: getContract(signer), + getERC20Balance: getERC20Balance(signer), onChainSendERC20: onChainSendERC20(signer), }; }; From 572e4667bc3b2b9b32d4fcd1758b5f4f3780a0ca Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 14:03:05 +0000 Subject: [PATCH 13/31] getERC20BalanceByAddress, getEthBalanceByAddress --- lib/connextclient/ConnextClient.ts | 87 +++++++++++++++++------------- lib/connextclient/ethprovider.ts | 16 ++++-- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 6455011b5..9426229b9 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -21,6 +21,7 @@ import { XudError } from '../types'; import { UnitConverter } from '../utils/UnitConverter'; import { parseResponseBody } from '../utils/utils'; import errors, { errorCodes } from './errors'; +import { EthProvider, getEthprovider } from './ethprovider'; import { ConnextBlockNumberResponse, ConnextChannelBalanceResponse, @@ -41,7 +42,6 @@ import { ProvidePreimageEvent, TransferReceivedEvent, } from './types'; -import { EthProvider, getEthprovider } from './ethprovider'; interface ConnextClient { on(event: 'preimage', listener: (preimageRequest: ProvidePreimageEvent) => void): void; @@ -391,38 +391,47 @@ class ConnextClient extends SwapClient { if (this._reconcileDepositSubscriber) { this._reconcileDepositSubscriber.unsubscribe(); } - const ethBalance$ = interval(30000).pipe( - mergeMap(() => from(this.getBalanceForAddress(this.channelAddress!, this.getTokenAddress('ETH')))), - // only emit new ETH balance events when the balance changes - distinctUntilChanged(), - ); - this._reconcileDepositSubscriber = ethBalance$ - // when ETH balance changes - .pipe( - mergeMap(() => { - if (this.status === ClientStatus.ConnectionVerified) { - return defer(() => { - // create new commitment transaction - return from( - this.sendRequest('/deposit', 'POST', { - channelAddress: this.channelAddress, - publicIdentifier: this.publicIdentifier, - assetId: '0x0000000000000000000000000000000000000000', // TODO: multi currency support - }), - ); - }); - } - return throwError('stopping deposit calls because client is no longer connected'); - }), - ) - .subscribe({ - next: () => { - this.logger.trace('deposit successfully reconciled'); - }, - error: (e) => { - this.logger.trace(`stopped deposit calls because: ${JSON.stringify(e)}`); - }, - }); + const getBalance$ = (assetId: string, pollInterval: number) => { + return interval(pollInterval).pipe( + mergeMap(() => from(this.getBalanceForAddress(assetId, this.channelAddress))), + // only emit new balance events when the balance changes + distinctUntilChanged(), + ); + }; + /// const ethBalance$ = getBalance$(this.getTokenAddress('ETH'), 30000); + + const reconcileForAsset = (assetId: string, balance$: ReturnType) => { + return ( + balance$ + // when ETH balance changes + .pipe( + mergeMap(() => { + if (this.status === ClientStatus.ConnectionVerified) { + return defer(() => { + // create new commitment transaction + return from( + this.sendRequest('/deposit', 'POST', { + channelAddress: this.channelAddress, + publicIdentifier: this.publicIdentifier, + assetId, + }), + ); + }); + } + return throwError('stopping deposit calls because client is no longer connected'); + }), + ) + ); + }; + const assetId = this.getTokenAddress('ETH'); + this._reconcileDepositSubscriber = reconcileForAsset(assetId, getBalance$(assetId, 30000)).subscribe({ + next: () => { + this.logger.trace('deposit successfully reconciled'); + }, + error: (e) => { + this.logger.trace(`stopped deposit calls because: ${JSON.stringify(e)}`); + }, + }); }; public sendSmallestAmount = async () => { @@ -775,15 +784,17 @@ class ConnextClient extends SwapClient { return gweiGasPrice; }; - private getBalanceForAddress = async (_address: string, assetId: string) => { + private getBalanceForAddress = async (assetId: string, address?: string) => { assert(this.ethProvider, 'Cannot get balance without ethProvider'); if (assetId === this.tokenAddresses.get('ETH')) { - const ethBalance$ = this.ethProvider.getEthBalance(); + const ethBalance$ = address ? this.ethProvider.getEthBalanceByAddress(address) : this.ethProvider.getEthBalance(); return BigInt(await ethBalance$.toPromise()); } else { const contract = this.ethProvider.getContract(assetId); - const erc20balance$ = this.ethProvider.getERC20Balance(contract); - const erc20balance = (await erc20balance$.toPromise()); + const erc20balance$ = address + ? this.ethProvider.getERC20BalanceByAddress(address, contract) + : this.ethProvider.getERC20Balance(contract); + const erc20balance = await erc20balance$.toPromise(); console.log(`erc20balance for ${assetId} is ${erc20balance}`); return BigInt(erc20balance); } @@ -920,7 +931,7 @@ class ConnextClient extends SwapClient { const tokenAddress = this.getTokenAddress(currency); getBalancePromise = Promise.all([ this.sendRequest(`/${this.publicIdentifier}/channels/${this.channelAddress}`, 'GET'), - this.getBalanceForAddress(this.signerAddress!, tokenAddress), + this.getBalanceForAddress(tokenAddress), ]) .then(async ([channelDetailsRes, onChainBalance]) => { const channelDetails = await parseResponseBody(channelDetailsRes); diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index cf9eeeab4..03454d62b 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -42,17 +42,25 @@ const onChainSendERC20 = curry( }, ); -const getERC20Balance = curry((signer: ethers.Wallet, contract: ethers.Contract): Observable => { - return from(contract.balanceOf(signer.address)) as Observable; -}); +const getEthBalanceByAddress = curry((provider: ethers.providers.JsonRpcProvider, address: string) => + from(provider.getBalance(address)), +); + +const getERC20Balance = curry( + (address: string, contract: ethers.Contract): Observable => { + return from(contract.balanceOf(address)) as Observable; + }, +); const getEthprovider = (host: string, port: number, name: string, chainId: number, seed: string) => { const provider = getProvider(host, port, name, chainId); const signer = getSigner(provider, seed); return { getEthBalance: () => from(signer.getBalance()), + getEthBalanceByAddress: getEthBalanceByAddress(provider), getContract: getContract(signer), - getERC20Balance: getERC20Balance(signer), + getERC20Balance: getERC20Balance(signer.address), + getERC20BalanceByAddress: getERC20Balance, onChainSendERC20: onChainSendERC20(signer), }; }; From f827ee633d0482c7ece8dcd66c1f441c434653e3 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 14:42:19 +0000 Subject: [PATCH 14/31] monitor deposits for all token addresses --- lib/connextclient/ConnextClient.ts | 38 +++++++++++++----------------- lib/service/Service.ts | 1 - 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 9426229b9..2468a6192 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -162,7 +162,7 @@ class ConnextClient extends SwapClient { private requestCollateralPromises = new Map>(); private outboundAmounts = new Map(); private inboundAmounts = new Map(); - private _reconcileDepositSubscriber: Subscription | undefined; + private _reconcileDepositSubscriptions: Subscription[] = []; /** Channel multisig address */ private channelAddress: string | undefined; @@ -388,9 +388,7 @@ class ConnextClient extends SwapClient { }; private reconcileDeposit = () => { - if (this._reconcileDepositSubscriber) { - this._reconcileDepositSubscriber.unsubscribe(); - } + this._reconcileDepositSubscriptions.forEach((subscription) => subscription.unsubscribe()); const getBalance$ = (assetId: string, pollInterval: number) => { return interval(pollInterval).pipe( mergeMap(() => from(this.getBalanceForAddress(assetId, this.channelAddress))), @@ -398,8 +396,6 @@ class ConnextClient extends SwapClient { distinctUntilChanged(), ); }; - /// const ethBalance$ = getBalance$(this.getTokenAddress('ETH'), 30000); - const reconcileForAsset = (assetId: string, balance$: ReturnType) => { return ( balance$ @@ -423,14 +419,18 @@ class ConnextClient extends SwapClient { ) ); }; - const assetId = this.getTokenAddress('ETH'); - this._reconcileDepositSubscriber = reconcileForAsset(assetId, getBalance$(assetId, 30000)).subscribe({ - next: () => { - this.logger.trace('deposit successfully reconciled'); - }, - error: (e) => { - this.logger.trace(`stopped deposit calls because: ${JSON.stringify(e)}`); - }, + this.tokenAddresses.forEach((assetId) => { + const subscription = reconcileForAsset(assetId, getBalance$(assetId, 30000)).subscribe({ + next: () => { + this.logger.trace(`deposit successfully reconciled for ${this.getCurrencyByTokenaddress(assetId)}`); + }, + error: (e) => { + this.logger.trace( + `stopped ${this.getCurrencyByTokenaddress(assetId)} deposit calls because: ${JSON.stringify(e)}`, + ); + }, + }); + this._reconcileDepositSubscriptions.push(subscription); }); }; @@ -794,9 +794,7 @@ class ConnextClient extends SwapClient { const erc20balance$ = address ? this.ethProvider.getERC20BalanceByAddress(address, contract) : this.ethProvider.getERC20Balance(contract); - const erc20balance = await erc20balance$.toPromise(); - console.log(`erc20balance for ${assetId} is ${erc20balance}`); - return BigInt(erc20balance); + return BigInt(await erc20balance$.toPromise()); } }; @@ -907,13 +905,11 @@ class ConnextClient extends SwapClient { const { freeBalanceOnChain } = await this.getBalance(currency); - console.log('getting to the conversion'); const confirmedBalanceAmount = this.unitConverter.unitsToAmount({ currency, units: BigInt(freeBalanceOnChain), }); - console.log('returning from walletBalance', confirmedBalanceAmount); return { totalBalance: confirmedBalanceAmount, confirmedBalance: confirmedBalanceAmount, @@ -1084,9 +1080,7 @@ class ConnextClient extends SwapClient { /** Connext client specific cleanup. */ protected disconnect = async () => { - if (this._reconcileDepositSubscriber) { - this._reconcileDepositSubscriber.unsubscribe(); - } + this._reconcileDepositSubscriptions.forEach((subscription) => subscription.unsubscribe()); this.setStatus(ClientStatus.Disconnected); for (const req of this.pendingRequests) { diff --git a/lib/service/Service.ts b/lib/service/Service.ts index ff07ce720..cfbe4e36d 100644 --- a/lib/service/Service.ts +++ b/lib/service/Service.ts @@ -241,7 +241,6 @@ class Service extends EventEmitter { }); } }); - console.log('returning from service'); return balances; }; From 3a424a275ac10dac3b8f9abfbefdd61abf40061b Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 15:57:46 +0000 Subject: [PATCH 15/31] remove unnecessary defer --- lib/connextclient/ConnextClient.ts | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 2468a6192..de233ccd2 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -403,16 +403,14 @@ class ConnextClient extends SwapClient { .pipe( mergeMap(() => { if (this.status === ClientStatus.ConnectionVerified) { - return defer(() => { - // create new commitment transaction - return from( - this.sendRequest('/deposit', 'POST', { - channelAddress: this.channelAddress, - publicIdentifier: this.publicIdentifier, - assetId, - }), - ); - }); + // create new commitment transaction + return from( + this.sendRequest('/deposit', 'POST', { + channelAddress: this.channelAddress, + publicIdentifier: this.publicIdentifier, + assetId, + }), + ); } return throwError('stopping deposit calls because client is no longer connected'); }), @@ -1072,9 +1070,7 @@ class ConnextClient extends SwapClient { this.logger.error(`could not send on-chain transfer: ${JSON.stringify(e)}`); }, }); - console.log('sendTransaction$', sendTransaction$); const transaction = await sendTransaction$.toPromise(); - console.log('transaction is', transaction); return transaction.hash; }; From ec37094d6bc352555c4ab1782a03798d745821e4 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 16:01:38 +0000 Subject: [PATCH 16/31] add comment for getBalanceForAddress --- lib/connextclient/ConnextClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index de233ccd2..eede493d6 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -782,6 +782,10 @@ class ConnextClient extends SwapClient { return gweiGasPrice; }; + /** + * Returns the on-chain balance for a given assetId and address. + * Address defaults to signer address. + */ private getBalanceForAddress = async (assetId: string, address?: string) => { assert(this.ethProvider, 'Cannot get balance without ethProvider'); if (assetId === this.tokenAddresses.get('ETH')) { From d54d92564ce09db282e6425e5762b042e738560c Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 16:03:15 +0000 Subject: [PATCH 17/31] assert ethProvider --- lib/connextclient/ConnextClient.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index eede493d6..307fddac1 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -1060,10 +1060,7 @@ class ConnextClient extends SwapClient { throw new Error('either all must be true or amount must be non-zero'); } - if (!this.ethProvider) { - throw new Error('cannot send transaction without ethProvider'); - } - + assert(this.ethProvider, 'cannot send transaction without ethProvider'); const contract = this.ethProvider.getContract(this.getTokenAddress(currency)); const sendTransaction$ = this.ethProvider.onChainSendERC20(contract, destination, unitsStr); sendTransaction$.subscribe({ From b10ee707bfcdf05770c1af819e9e789b5ed033db Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Wed, 16 Dec 2020 16:10:13 +0000 Subject: [PATCH 18/31] add comments --- lib/connextclient/ethprovider.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index 03454d62b..e1d4b2421 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -2,6 +2,7 @@ import { ethers } from 'ethers'; import { curry } from 'ramda'; import { from, Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; +// This file will be a separate module with the above dependencies. const getProvider = (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { return new ethers.providers.JsonRpcProvider( @@ -24,6 +25,8 @@ const getContract = curry( }, ); +// we curry the functions below so that arguments +// can be provided 1 by 1 if necessary const onChainSendERC20 = curry( ( signer: ethers.Wallet, @@ -57,6 +60,8 @@ const getEthprovider = (host: string, port: number, name: string, chainId: numbe const signer = getSigner(provider, seed); return { getEthBalance: () => from(signer.getBalance()), + // before exposing the functions we provide signer and provider + // when required getEthBalanceByAddress: getEthBalanceByAddress(provider), getContract: getContract(signer), getERC20Balance: getERC20Balance(signer.address), From 4c2b311b704238a943bab6e01963ce8d1bee9aac Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 09:45:36 +0000 Subject: [PATCH 19/31] fix duplicate tx broadcast --- lib/connextclient/ConnextClient.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 307fddac1..12ce35aa2 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -1032,6 +1032,8 @@ class ConnextClient extends SwapClient { // Withdraw on-chain funds public withdraw = async ({ all, currency, amount, destination, fee }: WithdrawArguments): Promise => { + assert(this.ethProvider, 'cannot send transaction without ethProvider'); + if (fee) { // TODO: allow overwriting gas price throw Error('setting fee for Ethereum withdrawals is not supported yet'); @@ -1060,18 +1062,14 @@ class ConnextClient extends SwapClient { throw new Error('either all must be true or amount must be non-zero'); } - assert(this.ethProvider, 'cannot send transaction without ethProvider'); + if (currency === 'ETH') { + throw new Error('sending eth not supported, yet'); + } + const contract = this.ethProvider.getContract(this.getTokenAddress(currency)); const sendTransaction$ = this.ethProvider.onChainSendERC20(contract, destination, unitsStr); - sendTransaction$.subscribe({ - next: (transaction) => { - this.logger.info(`on-chain transfer sent, transaction hash: ${transaction.hash}`); - }, - error: (e) => { - this.logger.error(`could not send on-chain transfer: ${JSON.stringify(e)}`); - }, - }); const transaction = await sendTransaction$.toPromise(); + this.logger.info(`on-chain transfer sent, transaction hash: ${transaction.hash}`); return transaction.hash; }; From f93811ee75ca4271d23ff87362b954e79d748e73 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 10:43:46 +0000 Subject: [PATCH 20/31] fix ETH signer transfer --- lib/connextclient/ConnextClient.ts | 9 +++++---- lib/connextclient/ethprovider.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 12ce35aa2..466abfcac 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -1062,12 +1062,13 @@ class ConnextClient extends SwapClient { throw new Error('either all must be true or amount must be non-zero'); } + let sendTransaction$; if (currency === 'ETH') { - throw new Error('sending eth not supported, yet'); + sendTransaction$ = this.ethProvider.onChainSendETH(destination, unitsStr); + } else { + const contract = this.ethProvider.getContract(this.getTokenAddress(currency)); + sendTransaction$ = this.ethProvider.onChainSendERC20(contract, destination, unitsStr); } - - const contract = this.ethProvider.getContract(this.getTokenAddress(currency)); - const sendTransaction$ = this.ethProvider.onChainSendERC20(contract, destination, unitsStr); const transaction = await sendTransaction$.toPromise(); this.logger.info(`on-chain transfer sent, transaction hash: ${transaction.hash}`); return transaction.hash; diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index e1d4b2421..2355c5161 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -45,6 +45,22 @@ const onChainSendERC20 = curry( }, ); +const onChainSendETH = curry( + (signer: ethers.Wallet, destinationAddress: string, units: string): Observable => { + return from(signer.provider.getGasPrice()).pipe( + mergeMap((gasPrice) => { + const ether = ethers.utils.formatEther(units); + const value = ethers.utils.parseEther(ether); + return signer.sendTransaction({ + to: destinationAddress, + value, + gasPrice, + }); + }), + ); + }, +); + const getEthBalanceByAddress = curry((provider: ethers.providers.JsonRpcProvider, address: string) => from(provider.getBalance(address)), ); @@ -67,6 +83,7 @@ const getEthprovider = (host: string, port: number, name: string, chainId: numbe getERC20Balance: getERC20Balance(signer.address), getERC20BalanceByAddress: getERC20Balance, onChainSendERC20: onChainSendERC20(signer), + onChainSendETH: onChainSendETH(signer), }; }; From 727319065ddc4f7708a6f0444a8126639ee58900 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 11:32:07 +0000 Subject: [PATCH 21/31] only expose onChainTransfer --- lib/connextclient/ConnextClient.ts | 8 +------- lib/connextclient/ethprovider.ts | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index 466abfcac..ff27ced69 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -1062,13 +1062,7 @@ class ConnextClient extends SwapClient { throw new Error('either all must be true or amount must be non-zero'); } - let sendTransaction$; - if (currency === 'ETH') { - sendTransaction$ = this.ethProvider.onChainSendETH(destination, unitsStr); - } else { - const contract = this.ethProvider.getContract(this.getTokenAddress(currency)); - sendTransaction$ = this.ethProvider.onChainSendERC20(contract, destination, unitsStr); - } + const sendTransaction$ = this.ethProvider.onChainTransfer(this.getTokenAddress(currency), destination, unitsStr); const transaction = await sendTransaction$.toPromise(); this.logger.info(`on-chain transfer sent, transaction hash: ${transaction.hash}`); return transaction.hash; diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index 2355c5161..f1c189786 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -74,16 +74,28 @@ const getERC20Balance = curry( const getEthprovider = (host: string, port: number, name: string, chainId: number, seed: string) => { const provider = getProvider(host, port, name, chainId); const signer = getSigner(provider, seed); + const getERC20BalanceWithSigner = getERC20Balance(signer.address); + const getContractWithSigner = getContract(signer); + const onChainSendERC20WithSigner = onChainSendERC20(signer); + const onChainSendETHWithSigner = onChainSendETH(signer); + const getEthBalanceByAddressWithProvider = getEthBalanceByAddress(provider); + const onChainTransfer = (contractAddress: string, destinationAddress: string, units: string) => { + if (contractAddress === ethers.constants.AddressZero) { + return onChainSendETHWithSigner(destinationAddress, units); + } else { + const contract = getContractWithSigner(contractAddress); + return onChainSendERC20WithSigner(contract, destinationAddress, units); + } + }; return { getEthBalance: () => from(signer.getBalance()), // before exposing the functions we provide signer and provider // when required - getEthBalanceByAddress: getEthBalanceByAddress(provider), - getContract: getContract(signer), - getERC20Balance: getERC20Balance(signer.address), + getEthBalanceByAddress: getEthBalanceByAddressWithProvider, + getContract: getContractWithSigner, + getERC20Balance: getERC20BalanceWithSigner, getERC20BalanceByAddress: getERC20Balance, - onChainSendERC20: onChainSendERC20(signer), - onChainSendETH: onChainSendETH(signer), + onChainTransfer, }; }; From f1f72169592cb317273d562c1e1ac1cb661e1f7e Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 12:06:43 +0000 Subject: [PATCH 22/31] test(jest): fix config for watch mode --- jest.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index aa8cdfd14..1c83ac6ff 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,8 +1,10 @@ module.exports = { preset: 'ts-jest', + testMatch: ["/test/jest/**/*.ts"], globals: { 'ts-jest': { - tsConfig: '/tsconfig.json' + tsconfig: '/tsconfig.json' + } }, testEnvironment: 'node', From 104339ab6de9eed533d5526f3279d258abd52cef Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 13:57:39 +0000 Subject: [PATCH 23/31] ramda as dev dependency --- npm-shrinkwrap.json | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f9ba30c9e..28897f72f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2690,6 +2690,7 @@ "version": "0.27.33", "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.33.tgz", "integrity": "sha512-uStXyYPPe9VxOe++ymWugHoMGh8SlCFGVJxuZPZG+S0WHEqE3T9hKuFAkTNo5j1Cb3lWnfxMesT2SPyx333cGQ==", + "dev": true, "requires": { "ts-toolbelt": "^6.15.1" } @@ -17015,7 +17016,8 @@ "ts-toolbelt": { "version": "6.15.5", "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", - "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==", + "dev": true }, "tsconfig-paths": { "version": "3.9.0", diff --git a/package.json b/package.json index e975ec7c5..dad969594 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,6 @@ "dependencies": { "@ethersproject/solidity": "5.0.3", "@exchangeunion/grpc-dynamic-gateway": "^0.3.10", - "@types/ramda": "0.27.33", "bip39": "3.0.2", "body-parser": "^1.19.0", "chalk": "^2.4.2", @@ -201,6 +200,7 @@ "@types/mocha": "^8.2.0", "@types/node": "^14.14.12", "@types/node-forge": "^0.8.7", + "@types/ramda": "0.27.33", "@types/secp256k1": "^3.5.0", "@types/semver": "^6.2.2", "@types/sinon": "^9.0.9", From e5e96572d9a972a4f524d17ed4f07ca3bd18e7b1 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 14:10:21 +0000 Subject: [PATCH 24/31] fix ConnextClient.withdraw tests --- test/jest/Connext.spec.ts | 72 +++++++++++++++------------------------ 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/test/jest/Connext.spec.ts b/test/jest/Connext.spec.ts index 52dadfe75..f0db2da08 100644 --- a/test/jest/Connext.spec.ts +++ b/test/jest/Connext.spec.ts @@ -5,6 +5,8 @@ import { CurrencyInstance } from '../../lib/db/types'; import Logger from '../../lib/Logger'; import { PaymentState } from '../../lib/swaps/SwapClient'; import { getUnitConverter } from '../utils'; +import { EthProvider } from '../../lib/connextclient/ethprovider'; +import { of } from 'rxjs'; const MOCK_TX_HASH = '0x5544332211'; jest.mock('../../lib/utils/utils', () => { @@ -93,6 +95,7 @@ describe('ConnextClient', () => { const MOCK_ETH_FREE_BALANCE_ON_CHAIN = 10n ** 18n; // 1 ETH const MOCK_USDT_FREE_BALANCE_ON_CHAIN = 10000n * 10n ** 6n; // 10000 USDT const DESTINATION_ADDRESS = '0x12345'; + const onChainTransfer = jest.fn(() => of(Promise.resolve({ hash: MOCK_TX_HASH }))); beforeEach(() => { connext['getBalance'] = jest.fn().mockImplementation((currency: string) => { @@ -105,25 +108,28 @@ describe('ConnextClient', () => { return { freeBalanceOnChain: 0 }; } }); - connext['sendRequest'] = jest.fn(); + connext['ethProvider'] = ({ + onChainTransfer, + } as unknown) as EthProvider; }); afterEach(() => { jest.clearAllMocks(); - }), - it('fails with custom fee', async () => { - expect.assertions(1); - try { - await connext.withdraw({ - currency: 'ETH', - destination: DESTINATION_ADDRESS, - amount: 123, - fee: 1, - }); - } catch (e) { - expect(e).toMatchSnapshot(); - } - }); + }); + + it('fails with custom fee', async () => { + expect.assertions(1); + try { + await connext.withdraw({ + currency: 'ETH', + destination: DESTINATION_ADDRESS, + amount: 123, + fee: 1, + }); + } catch (e) { + expect(e).toMatchSnapshot(); + } + }); it('fails to withdraw all ETH', async () => { expect.assertions(1); @@ -158,16 +164,8 @@ describe('ConnextClient', () => { destination: DESTINATION_ADDRESS, all: true, }); - expect(connext['sendRequest']).toHaveBeenCalledTimes(1); - expect(connext['sendRequest']).toHaveBeenCalledWith( - '/onchain-transfer', - 'POST', - expect.objectContaining({ - assetId: USDT_ASSET_ID, - amount: MOCK_USDT_FREE_BALANCE_ON_CHAIN, - recipient: DESTINATION_ADDRESS, - }), - ); + expect(onChainTransfer).toHaveBeenCalledTimes(1); + expect(onChainTransfer).toHaveBeenCalledWith(USDT_ASSET_ID, DESTINATION_ADDRESS, MOCK_USDT_FREE_BALANCE_ON_CHAIN); expect(txhash).toEqual(MOCK_TX_HASH); }); @@ -178,16 +176,8 @@ describe('ConnextClient', () => { destination: DESTINATION_ADDRESS, amount: 5000 * 10 ** 8, }); - expect(connext['sendRequest']).toHaveBeenCalledTimes(1); - expect(connext['sendRequest']).toHaveBeenCalledWith( - '/onchain-transfer', - 'POST', - expect.objectContaining({ - assetId: USDT_ASSET_ID, - amount: '5000000000', - recipient: DESTINATION_ADDRESS, - }), - ); + expect(onChainTransfer).toHaveBeenCalledTimes(1); + expect(onChainTransfer).toHaveBeenCalledWith(USDT_ASSET_ID, DESTINATION_ADDRESS, '5000000000'); expect(txhash).toEqual(MOCK_TX_HASH); }); @@ -198,16 +188,8 @@ describe('ConnextClient', () => { destination: DESTINATION_ADDRESS, amount: 1 * 10 ** 2, }); - expect(connext['sendRequest']).toHaveBeenCalledTimes(1); - expect(connext['sendRequest']).toHaveBeenCalledWith( - '/onchain-transfer', - 'POST', - expect.objectContaining({ - assetId: ETH_ASSET_ID, - amount: '1000000000000', - recipient: DESTINATION_ADDRESS, - }), - ); + expect(onChainTransfer).toHaveBeenCalledTimes(1); + expect(onChainTransfer).toHaveBeenCalledWith(ETH_ASSET_ID, DESTINATION_ADDRESS, '1000000000000'); expect(txhash).toEqual(MOCK_TX_HASH); }); }); From 5b5416535dd457767aa2e7bd6de9f3ed4129dbd8 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 14:13:14 +0000 Subject: [PATCH 25/31] modify comment --- lib/connextclient/ConnextClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connextclient/ConnextClient.ts b/lib/connextclient/ConnextClient.ts index ff27ced69..308abb82e 100644 --- a/lib/connextclient/ConnextClient.ts +++ b/lib/connextclient/ConnextClient.ts @@ -399,7 +399,7 @@ class ConnextClient extends SwapClient { const reconcileForAsset = (assetId: string, balance$: ReturnType) => { return ( balance$ - // when ETH balance changes + // when balance changes .pipe( mergeMap(() => { if (this.status === ClientStatus.ConnectionVerified) { From e7dc1696ce723519c31a20cfd988b7d2a6c56346 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 14:15:02 +0000 Subject: [PATCH 26/31] enable BTC/USDT pair for simnet --- lib/db/seeds/simnet.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/db/seeds/simnet.ts b/lib/db/seeds/simnet.ts index 0c2e8545f..e6538c59d 100644 --- a/lib/db/seeds/simnet.ts +++ b/lib/db/seeds/simnet.ts @@ -2,7 +2,6 @@ import * as db from '../types'; import { SwapClientType } from '../../constants/enums'; const nodes = [ - /* { nodePubKey: '03ece33a30db1dbce4b62fa96a5e9541138a24997ef5672eebed2d332270e39542', addresses: [ @@ -23,7 +22,6 @@ const nodes = [ }, ], }, - */ ] as db.NodeAttributes[]; const currencies = [ @@ -58,12 +56,12 @@ const currencies = [ ] as db.CurrencyAttributes[]; const pairs = [ - // { baseCurrency: 'BTC', quoteCurrency: 'DAI' }, - // { baseCurrency: 'BTC', quoteCurrency: 'USDT' }, { baseCurrency: 'ETH', quoteCurrency: 'BTC' }, + { baseCurrency: 'LTC', quoteCurrency: 'BTC' }, + { baseCurrency: 'BTC', quoteCurrency: 'USDT' }, + // { baseCurrency: 'BTC', quoteCurrency: 'DAI' }, // { baseCurrency: 'ETH', quoteCurrency: 'DAI' }, // { baseCurrency: 'ETH', quoteCurrency: 'USDT' }, - { baseCurrency: 'LTC', quoteCurrency: 'BTC' }, // { baseCurrency: 'LTC', quoteCurrency: 'DAI' }, // { baseCurrency: 'LTC', quoteCurrency: 'USDT' }, // { baseCurrency: 'USDT', quoteCurrency: 'DAI' }, From 59e8e78e3e9244e7a33aa494b4288840d294a924 Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 15:17:27 +0000 Subject: [PATCH 27/31] remove blank link --- jest.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 1c83ac6ff..eb841bcc1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,7 +4,6 @@ module.exports = { globals: { 'ts-jest': { tsconfig: '/tsconfig.json' - } }, testEnvironment: 'node', From 26de49832b06edcb69136ba21b3bc2a50e3a965d Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 16:27:37 +0000 Subject: [PATCH 28/31] remove object-curly-newline rule --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3228d04be..370fe370c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,6 @@ module.exports = { }, rules: { 'no-empty': 'off', - "object-curly-newline": 0, }, plugins: [ '@typescript-eslint', From f1ef6690d3af658bed33c2ce7554efd9e89df9fe Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Thu, 17 Dec 2020 16:30:51 +0000 Subject: [PATCH 29/31] fix test:jest:watch script --- jest.config.js | 1 - package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index eb841bcc1..d0e188f02 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,5 @@ module.exports = { preset: 'ts-jest', - testMatch: ["/test/jest/**/*.ts"], globals: { 'ts-jest': { tsconfig: '/tsconfig.json' diff --git a/package.json b/package.json index dad969594..a1bc3de2e 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "test:sim:logs": "cd test/simulation && ./logs.sh", "test:jest": "jest --testMatch='/test/jest/**/*.spec.[jt]s?(x)' --unhandled-rejections=strict --forceExit", "test:seedutil": "jest --testMatch='/seedutil/*.spec.[jt]s?(x)'", - "test:jest:watch": "jest --watch", + "test:jest:watch": "jest --watch --testMatch='/test/jest/**/*.spec.[jt]s?(x)'", "typedoc": "typedoc --out typedoc --module commonjs --target es6 lib --readme none", "preversion": "npm run lintNoFix && npm test && npm run test:sim:build && npm run test:sim", "postversion": "npm run compile", From 0e40c48ad41bf6652a814a9f62458e54479b5a0d Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Sat, 19 Dec 2020 10:30:50 +0000 Subject: [PATCH 30/31] enable USDT/DAI pair --- lib/db/seeds/simnet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/db/seeds/simnet.ts b/lib/db/seeds/simnet.ts index e6538c59d..e11c3b736 100644 --- a/lib/db/seeds/simnet.ts +++ b/lib/db/seeds/simnet.ts @@ -59,12 +59,12 @@ const pairs = [ { baseCurrency: 'ETH', quoteCurrency: 'BTC' }, { baseCurrency: 'LTC', quoteCurrency: 'BTC' }, { baseCurrency: 'BTC', quoteCurrency: 'USDT' }, + { baseCurrency: 'USDT', quoteCurrency: 'DAI' }, // { baseCurrency: 'BTC', quoteCurrency: 'DAI' }, // { baseCurrency: 'ETH', quoteCurrency: 'DAI' }, // { baseCurrency: 'ETH', quoteCurrency: 'USDT' }, // { baseCurrency: 'LTC', quoteCurrency: 'DAI' }, // { baseCurrency: 'LTC', quoteCurrency: 'USDT' }, - // { baseCurrency: 'USDT', quoteCurrency: 'DAI' }, // { baseCurrency: 'XUC', quoteCurrency: 'BTC' }, // { baseCurrency: 'XUC', quoteCurrency: 'ETH' }, // { baseCurrency: 'XUC', quoteCurrency: 'DAI' }, From ccaaa581e0b28ab6913e7debd494630b2f90591c Mon Sep 17 00:00:00 2001 From: Karl Ranna Date: Tue, 22 Dec 2020 10:58:40 +0000 Subject: [PATCH 31/31] add comments to ethprovider --- lib/connextclient/ethprovider.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/connextclient/ethprovider.ts b/lib/connextclient/ethprovider.ts index f1c189786..9b0f6d52b 100644 --- a/lib/connextclient/ethprovider.ts +++ b/lib/connextclient/ethprovider.ts @@ -4,6 +4,7 @@ import { from, Observable } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; // This file will be a separate module with the above dependencies. +// gets the Ethereum provider object to read data from the chain const getProvider = (host: string, port: number, name: string, chainId: number): ethers.providers.JsonRpcProvider => { return new ethers.providers.JsonRpcProvider( { url: `http://${host}:${port}/ethprovider/${chainId}` }, @@ -14,19 +15,26 @@ const getProvider = (host: string, port: number, name: string, chainId: number): ); }; +// gets the signer object necessary for write access (think unlock wallet) const getSigner = (provider: ethers.providers.JsonRpcProvider, seed: string): ethers.Wallet => { return ethers.Wallet.fromMnemonic(seed).connect(provider); }; +// We curry getContract so that we can provide its arguments one at a time. +// This allows us to provide some of the necessary arguments (that we already have) before we export the function. +// Read more: https://ramdajs.com/docs/#curry const getContract = curry( (signer: ethers.Wallet, contractAddress: string): ethers.Contract => { + // we use the minimum viable contract ABI for ERC20 tokens + // for full contract ABI we should compile it from the solidity source const erc20abi = ['function balanceOf(address) view returns (uint)', 'function transfer(address to, uint amount)']; return new ethers.Contract(contractAddress, erc20abi, signer); }, ); -// we curry the functions below so that arguments -// can be provided 1 by 1 if necessary +// Sends on-chain ERC20 transfer +// We also curry this function, just like the previous one. +// All the functions that we export out of the package will be curried const onChainSendERC20 = curry( ( signer: ethers.Wallet, @@ -45,6 +53,7 @@ const onChainSendERC20 = curry( }, ); +// Sends on-chain ETH transfer const onChainSendETH = curry( (signer: ethers.Wallet, destinationAddress: string, units: string): Observable => { return from(signer.provider.getGasPrice()).pipe( @@ -61,19 +70,26 @@ const onChainSendETH = curry( }, ); +// returns ETH on-chain balance for the address in wei const getEthBalanceByAddress = curry((provider: ethers.providers.JsonRpcProvider, address: string) => from(provider.getBalance(address)), ); +// returns ERC20 on-chain balance for the contract address in the smallest unit const getERC20Balance = curry( (address: string, contract: ethers.Contract): Observable => { return from(contract.balanceOf(address)) as Observable; }, ); +// This is the main function that has to be called before this package exposes more functions. +// Think of it as a constructor where we create the interal state of the module before +// we export more functionality to the consumer. const getEthprovider = (host: string, port: number, name: string, chainId: number, seed: string) => { + // create the internal state const provider = getProvider(host, port, name, chainId); const signer = getSigner(provider, seed); + // because the functions below are curried we can only provide some of the arguments const getERC20BalanceWithSigner = getERC20Balance(signer.address); const getContractWithSigner = getContract(signer); const onChainSendERC20WithSigner = onChainSendERC20(signer); @@ -87,10 +103,9 @@ const getEthprovider = (host: string, port: number, name: string, chainId: numbe return onChainSendERC20WithSigner(contract, destinationAddress, units); } }; + // expose functionality to the consumer return { getEthBalance: () => from(signer.getBalance()), - // before exposing the functions we provide signer and provider - // when required getEthBalanceByAddress: getEthBalanceByAddressWithProvider, getContract: getContractWithSigner, getERC20Balance: getERC20BalanceWithSigner,