diff --git a/__mocks__/ethers.ts b/__mocks__/ethers.ts index 7da501f0f23..bdf627aea50 100644 --- a/__mocks__/ethers.ts +++ b/__mocks__/ethers.ts @@ -3,6 +3,7 @@ const ethers = { ...jest.requireActual('ethers').ethers, providers: { JsonRpcProvider: jest.fn(), + JsonRpcBatchProvider: jest.fn(), }, Contract: jest.fn().mockImplementation(address => ({ decimals: () => { diff --git a/package.json b/package.json index d78ff532191..d1a5347fb40 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "embla-carousel-react": "^7.0.5", "envalid": "^7.3.1", "eth-url-parser": "^1.0.4", - "ethers": "^5.5.3", + "ethers": "^5.7.2", "framer-motion": "^6.3.11", "friendly-challenge": "0.9.2", "grapheme-splitter": "^1.0.4", diff --git a/packages/asset-service/package.json b/packages/asset-service/package.json index b93b0b4912c..b742328d827 100644 --- a/packages/asset-service/package.json +++ b/packages/asset-service/package.json @@ -29,7 +29,6 @@ "js-pixel-fonts": "^1.5.0" }, "devDependencies": { - "@ethersproject/providers": "^5.5.3", "@yfi/sdk": "^1.2.0", "colorthief": "^2.3.2" } diff --git a/packages/asset-service/src/generateAssetData/ethereum/yearnVaults.ts b/packages/asset-service/src/generateAssetData/ethereum/yearnVaults.ts index 17603da3c27..fc74776baec 100644 --- a/packages/asset-service/src/generateAssetData/ethereum/yearnVaults.ts +++ b/packages/asset-service/src/generateAssetData/ethereum/yearnVaults.ts @@ -1,7 +1,7 @@ -import { JsonRpcProvider } from '@ethersproject/providers' import { ethChainId as chainId, toAssetId } from '@shapeshiftoss/caip' import type { Token, Vault } from '@yfi/sdk' import { Yearn } from '@yfi/sdk' +import { ethers } from 'ethers' import toLower from 'lodash/toLower' import type { Asset } from '../../service/AssetService' @@ -9,7 +9,7 @@ import { ethereum } from '../baseAssets' import { colorMap } from '../colorMap' const network = 1 // 1 for mainnet -const provider = new JsonRpcProvider(process.env.ETHEREUM_NODE_URL) +const provider = new ethers.providers.JsonRpcBatchProvider(process.env.ETHEREUM_NODE_URL) export const yearnSdk = new Yearn(network, { provider }) const explorerData = { diff --git a/packages/caip/src/adapters/yearn/utils.ts b/packages/caip/src/adapters/yearn/utils.ts index bd2f7a5aff4..9882d69cb09 100644 --- a/packages/caip/src/adapters/yearn/utils.ts +++ b/packages/caip/src/adapters/yearn/utils.ts @@ -1,7 +1,7 @@ /* eslint-disable @shapeshiftoss/logger/no-native-console */ -import { JsonRpcProvider } from '@ethersproject/providers' import type { Token, Vault } from '@yfi/sdk' import { Yearn } from '@yfi/sdk' +import { ethers } from 'ethers' import fs from 'fs' import toLower from 'lodash/toLower' import uniqBy from 'lodash/uniqBy' @@ -11,7 +11,7 @@ import { toChainId } from '../../chainId/chainId' import { CHAIN_NAMESPACE, CHAIN_REFERENCE } from '../../constants' const network = 1 // 1 for mainnet -const provider = new JsonRpcProvider(process.env.REACT_APP_ETHEREUM_NODE_URL) +const provider = new ethers.providers.JsonRpcBatchProvider(process.env.REACT_APP_ETHEREUM_NODE_URL) const yearnSdk = new Yearn(network, { provider }) export const writeFiles = async (data: Record>) => { diff --git a/packages/investor-foxy/package.json b/packages/investor-foxy/package.json index 8b1fe8ec98f..26bf7a9cb21 100644 --- a/packages/investor-foxy/package.json +++ b/packages/investor-foxy/package.json @@ -21,14 +21,11 @@ "cli": "yarn build && yarn node dist/foxycli.js" }, "dependencies": { - "@ethersproject/providers": "^5.5.3", "@shapeshiftoss/caip": "workspace:^", "@shapeshiftoss/chain-adapters": "workspace:^", "@shapeshiftoss/logger": "workspace:^", "@shapeshiftoss/types": "workspace:^", - "readline-sync": "^1.4.10", - "web3-core": "1.7.4", - "web3-utils": "1.7.4" + "readline-sync": "^1.4.10" }, "devDependencies": { "@types/readline-sync": "^1.4.4" diff --git a/packages/investor-foxy/src/abi/erc20-abi.ts b/packages/investor-foxy/src/abi/erc20-abi.ts index bc1723c6421..ae7e19c78ef 100644 --- a/packages/investor-foxy/src/abi/erc20-abi.ts +++ b/packages/investor-foxy/src/abi/erc20-abi.ts @@ -1,6 +1,6 @@ -import type { AbiItem } from 'web3-utils' +import type { ContractInterface } from 'ethers' -export const erc20Abi: AbiItem[] = [ +export const erc20Abi: ContractInterface = [ { constant: true, inputs: [], diff --git a/packages/investor-foxy/src/abi/foxy-abi.ts b/packages/investor-foxy/src/abi/foxy-abi.ts index cc8e6ccdbcb..99522a88a09 100644 --- a/packages/investor-foxy/src/abi/foxy-abi.ts +++ b/packages/investor-foxy/src/abi/foxy-abi.ts @@ -1,6 +1,6 @@ -import type { AbiItem } from 'web3-utils' +import type { ContractInterface } from 'ethers' -export const foxyAbi: AbiItem[] = [ +export const foxyAbi: ContractInterface = [ { inputs: [], stateMutability: 'nonpayable', diff --git a/packages/investor-foxy/src/abi/foxy-staking-abi.ts b/packages/investor-foxy/src/abi/foxy-staking-abi.ts index 44d3ee93bed..0d62f0aae71 100644 --- a/packages/investor-foxy/src/abi/foxy-staking-abi.ts +++ b/packages/investor-foxy/src/abi/foxy-staking-abi.ts @@ -1,6 +1,6 @@ -import type { AbiItem } from 'web3-utils' +import type { ContractInterface } from 'ethers' -export const foxyStakingAbi: AbiItem[] = [ +export const foxyStakingAbi: ContractInterface = [ { inputs: [ { diff --git a/packages/investor-foxy/src/abi/liquidity-reserve-abi.ts b/packages/investor-foxy/src/abi/liquidity-reserve-abi.ts index b2e9615dece..4cea3c40602 100644 --- a/packages/investor-foxy/src/abi/liquidity-reserve-abi.ts +++ b/packages/investor-foxy/src/abi/liquidity-reserve-abi.ts @@ -1,6 +1,6 @@ -import type { AbiItem } from 'web3-utils' +import type { ContractInterface } from 'ethers' -export const liquidityReserveAbi: AbiItem[] = [ +export const liquidityReserveAbi: ContractInterface = [ { inputs: [ { diff --git a/packages/investor-foxy/src/abi/toke-manager-abi.ts b/packages/investor-foxy/src/abi/toke-manager-abi.ts index c6ee537cef2..a4a5250015e 100644 --- a/packages/investor-foxy/src/abi/toke-manager-abi.ts +++ b/packages/investor-foxy/src/abi/toke-manager-abi.ts @@ -1,6 +1,6 @@ -import type { AbiItem } from 'web3-utils' +import type { ContractInterface } from 'ethers' -export const tokeManagerAbi: AbiItem[] = [ +export const tokeManagerAbi: ContractInterface = [ { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, { anonymous: false, diff --git a/packages/investor-foxy/src/abi/toke-pool-abi.ts b/packages/investor-foxy/src/abi/toke-pool-abi.ts index c8c9018fa3a..4ef28248b05 100644 --- a/packages/investor-foxy/src/abi/toke-pool-abi.ts +++ b/packages/investor-foxy/src/abi/toke-pool-abi.ts @@ -1,6 +1,6 @@ -import type { AbiItem } from 'web3-utils' +import type { ContractInterface } from 'ethers' -export const tokePoolAbi: AbiItem[] = [ +export const tokePoolAbi: ContractInterface = [ { anonymous: false, inputs: [ diff --git a/packages/investor-foxy/src/abi/toke-reward-hash-abi.ts b/packages/investor-foxy/src/abi/toke-reward-hash-abi.ts index 758f382dcf1..c2d62f9274c 100644 --- a/packages/investor-foxy/src/abi/toke-reward-hash-abi.ts +++ b/packages/investor-foxy/src/abi/toke-reward-hash-abi.ts @@ -1,6 +1,6 @@ -import type { AbiItem } from 'web3-utils' +import type { ContractInterface } from 'ethers' -export const tokeRewardHashAbi: AbiItem[] = [ +export const tokeRewardHashAbi: ContractInterface = [ { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, { anonymous: false, diff --git a/packages/investor-foxy/src/api/api.ts b/packages/investor-foxy/src/api/api.ts index d77125ce5cb..d10fefc058e 100644 --- a/packages/investor-foxy/src/api/api.ts +++ b/packages/investor-foxy/src/api/api.ts @@ -1,16 +1,12 @@ -import { JsonRpcProvider } from '@ethersproject/providers' import type { ChainReference } from '@shapeshiftoss/caip' import { CHAIN_NAMESPACE, CHAIN_REFERENCE, toAssetId } from '@shapeshiftoss/caip' -import type { ChainAdapter } from '@shapeshiftoss/chain-adapters' +import type { EvmBaseAdapter, FeeDataEstimate } from '@shapeshiftoss/chain-adapters' import { Logger } from '@shapeshiftoss/logger' import { KnownChainIds, WithdrawType } from '@shapeshiftoss/types' import axios from 'axios' import type { BigNumber } from 'bignumber.js' +import { ethers } from 'ethers' import { toLower } from 'lodash' -import Web3 from 'web3' -import type { HttpProvider, TransactionReceipt } from 'web3-core/types' -import type { Contract } from 'web3-eth-contract' -import { numberToHex } from 'web3-utils' import { erc20Abi } from '../abi/erc20-abi' import { foxyAbi } from '../abi/foxy-abi' @@ -26,7 +22,7 @@ import { tokePoolAddress, tokeRewardHashAddress, } from '../constants' -import { bn, bnOrZero, buildTxToSign } from '../utils' +import { bn, bnOrZero } from '../utils' import type { AllowanceInput, ApproveInput, @@ -34,8 +30,9 @@ import type { CanClaimWithdrawParams, ClaimWithdrawal, ContractAddressInput, - EstimateGasApproveInput, - EstimateGasTxInput, + EstimateApproveFeesInput, + EstimateFeesTxInput, + EstimateWithdrawFeesInput, FoxyAddressesType, FoxyOpportunityInputData, GetTokeRewardAmount, @@ -49,7 +46,6 @@ import type { TxInputWithoutAmount, TxInputWithoutAmountAndWallet, TxReceipt, - WithdrawEstimateGasInput, WithdrawInfo, WithdrawInput, } from './foxy-types' @@ -64,7 +60,7 @@ type EthereumChainReference = | typeof CHAIN_REFERENCE.EthereumRopsten export type ConstructorArgs = { - adapter: ChainAdapter + adapter: EvmBaseAdapter providerUrl: string foxyAddresses: FoxyAddressesType chainReference?: EthereumChainReference @@ -88,13 +84,12 @@ export const transformData = ({ tvl, apy, expired, ...contractData }: FoxyOpport const TOKE_IPFS_URL = 'https://ipfs.tokemaklabs.xyz/ipfs' export class FoxyApi { - public adapter: ChainAdapter - public provider: HttpProvider + public adapter: EvmBaseAdapter + public provider: ethers.providers.JsonRpcBatchProvider private providerUrl: string - public jsonRpcProvider: JsonRpcProvider - public web3: Web3 - private foxyStakingContracts: Contract[] - private liquidityReserveContracts: Contract[] + public jsonRpcProvider: ethers.providers.JsonRpcBatchProvider + private foxyStakingContracts: ethers.Contract[] + private liquidityReserveContracts: ethers.Contract[] private readonly ethereumChainReference: ChainReference private foxyAddresses: FoxyAddressesType @@ -105,14 +100,14 @@ export class FoxyApi { chainReference = CHAIN_REFERENCE.EthereumMainnet, }: ConstructorArgs) { this.adapter = adapter - this.provider = new Web3.providers.HttpProvider(providerUrl) - this.jsonRpcProvider = new JsonRpcProvider(providerUrl) - this.web3 = new Web3(this.provider) + this.provider = new ethers.providers.JsonRpcBatchProvider(providerUrl) + this.jsonRpcProvider = new ethers.providers.JsonRpcBatchProvider(providerUrl) this.foxyStakingContracts = foxyAddresses.map( - addresses => new this.web3.eth.Contract(foxyStakingAbi, addresses.staking), + addresses => new ethers.Contract(addresses.staking, foxyStakingAbi, this.provider), ) this.liquidityReserveContracts = foxyAddresses.map( - addresses => new this.web3.eth.Contract(liquidityReserveAbi, addresses.liquidityReserve), + addresses => + new ethers.Contract(addresses.liquidityReserve, liquidityReserveAbi, this.provider), ) this.ethereumChainReference = chainReference this.providerUrl = providerUrl @@ -124,24 +119,42 @@ export class FoxyApi { * to exponential notation ('1.6e+21') in javascript. * @param amount */ - private normalizeAmount(amount: BigNumber) { - return this.web3.utils.toBN(amount.toFixed()) + private normalizeAmount(amount: BigNumber): ethers.BigNumber { + return ethers.BigNumber.from(amount.toFixed()) } + // TODO(gomes): This is rank and should really belong in web for sanity sake. private async signAndBroadcastTx(input: SignAndBroadcastTx): Promise { const { payload, wallet, dryRun } = input - const txToSign = buildTxToSign(payload) + + const { + chainSpecific: { gasPrice, gasLimit, maxFeePerGas, maxPriorityFeePerGas }, + } = payload.estimatedFees.fast + const shouldUseEIP1559Fees = + (await wallet.ethSupportsEIP1559()) && + maxFeePerGas !== undefined && + maxPriorityFeePerGas !== undefined + + const { txToSign } = await this.adapter.buildCustomTx({ + to: payload.to, + value: payload.value, + gasLimit, + wallet, + data: payload.data, + accountNumber: payload.bip44Params.accountNumber, + ...(shouldUseEIP1559Fees ? { maxFeePerGas, maxPriorityFeePerGas } : { gasPrice }), + }) if (wallet.supportsOfflineSigning()) { const signedTx = await this.adapter.signTransaction({ txToSign, wallet }) if (dryRun) return signedTx try { if (this.providerUrl.includes('localhost') || this.providerUrl.includes('127.0.0.1')) { - const sendSignedTx = await this.web3.eth.sendSignedTransaction(signedTx) - return sendSignedTx?.blockHash + const sendSignedTx = await this.provider.sendTransaction(signedTx) + return sendSignedTx?.blockHash ?? '' } return this.adapter.broadcastTransaction(signedTx) - } catch (err) { - throw new Error(`Failed to broadcast: ${err}`) + } catch (e) { + throw new Error(`Failed to broadcast: ${e}`) } } else if (wallet.supportsBroadcast() && this.adapter.signAndBroadcastTransaction) { if (dryRun) { @@ -154,71 +167,52 @@ export class FoxyApi { } checksumAddress(address: string): string { - return this.web3.utils.toChecksumAddress(address) + // ethers always returns checksum addresses from getAddress() calls + return ethers.utils.getAddress(address) } private verifyAddresses(addresses: string[]) { - try { - addresses.forEach(address => { - this.checksumAddress(address) - }) - } catch (err) { - throw new Error(`Verify Address: ${err}`) - } + addresses.forEach(address => { + this.checksumAddress(address) + }) } - private getStakingContract(contractAddress: string): Contract { + private getStakingContract(contractAddress: string): ethers.Contract { const stakingContract = this.foxyStakingContracts.find( - item => toLower(item.options.address) === toLower(contractAddress), + item => toLower(item.address) === toLower(contractAddress), ) if (!stakingContract) throw new Error('Not a valid contract address') return stakingContract } - private getLiquidityReserveContract(liquidityReserveAddress: string): Contract { + private getLiquidityReserveContract(liquidityReserveAddress: string): ethers.Contract { const liquidityReserveContract = this.liquidityReserveContracts.find( - item => toLower(item.options.address) === toLower(liquidityReserveAddress), + item => toLower(item.address) === toLower(liquidityReserveAddress), ) if (!liquidityReserveContract) throw new Error('Not a valid reserve contract address') return liquidityReserveContract } - private async getGasPriceAndNonce(userAddress: string) { - let nonce: number - try { - nonce = await this.web3.eth.getTransactionCount(userAddress) - } catch (err) { - throw new Error(`Get nonce Error: ${err}`) - } - let gasPrice: string - try { - gasPrice = await this.web3.eth.getGasPrice() - } catch (err) { - throw new Error(`Get gasPrice Error: ${err}`) - } - return { nonce: String(nonce), gasPrice } - } - async getFoxyOpportunities() { try { const opportunities = await Promise.all( this.foxyAddresses.map(async addresses => { const stakingContract = this.foxyStakingContracts.find( - item => toLower(item.options.address) === toLower(addresses.staking), + item => toLower(item.address) === toLower(addresses.staking), ) try { - const expired = await stakingContract?.methods.pauseStaking().call() + const expired = await stakingContract?.pauseStaking() const tvl = await this.tvl({ tokenContractAddress: addresses.foxy }) const apy = this.apy() return transformData({ ...addresses, expired, tvl, apy }) - } catch (err) { - throw new Error(`Failed to get contract data ${err}`) + } catch (e) { + throw new Error(`Failed to get contract data ${e}`) } }), ) return opportunities - } catch (err) { - throw new Error(`getFoxyOpportunities Error: ${err}`) + } catch (e) { + throw new Error(`getFoxyOpportunities Error: ${e}`) } } @@ -232,26 +226,23 @@ export class FoxyApi { const stakingContract = this.getStakingContract(addresses.staking) try { - const expired = await stakingContract.methods.pauseStaking().call() + const expired = await stakingContract.pauseStaking() const tvl = await this.tvl({ tokenContractAddress: addresses.foxy }) const apy = this.apy() return transformData({ ...addresses, tvl, apy, expired }) - } catch (err) { - throw new Error(`Failed to get contract data ${err}`) + } catch (e) { + throw new Error(`Failed to get contract data ${e}`) } } - async getGasPrice() { - const gasPrice = await this.web3.eth.getGasPrice() - return bnOrZero(gasPrice) - } - - getTxReceipt({ txid }: TxReceipt): Promise { + getTxReceipt({ txid }: TxReceipt): Promise { if (!txid) throw new Error('Must pass txid') - return this.web3.eth.getTransactionReceipt(txid) + return this.provider.getTransactionReceipt(txid) } - async estimateClaimWithdrawGas(input: ClaimWithdrawal): Promise { + async estimateClaimWithdrawFees( + input: ClaimWithdrawal, + ): Promise> { const { claimAddress, userAddress, contractAddress } = input const addressToClaim = claimAddress ?? userAddress this.verifyAddresses([addressToClaim, userAddress, contractAddress]) @@ -259,34 +250,62 @@ export class FoxyApi { const stakingContract = this.getStakingContract(contractAddress) try { - const estimatedGas = await stakingContract.methods.claimWithdraw(addressToClaim).estimateGas({ - from: userAddress, + const data = stakingContract.interface.encodeFunctionData('claimWithdraw', [addressToClaim]) + const feeData = await this.adapter.getFeeData({ + to: contractAddress, + value: '0', + chainSpecific: { + contractData: data, + from: userAddress, + }, }) - return bnOrZero(estimatedGas) - } catch (err) { - throw new Error(`Failed to get gas ${err}`) + + const { + chainSpecific: { gasLimit: gasLimitBase }, + } = feeData.fast + const safeGasLimit = bnOrZero(gasLimitBase).times('1.05').toFixed(0) + feeData.fast.chainSpecific.gasLimit = safeGasLimit + + return feeData + } catch (e) { + throw new Error(`Failed to get gas ${e}`) } } - async estimateSendWithdrawalRequestsGas( + async estimateSendWithdrawalRequestsFees( input: TxInputWithoutAmountAndWallet, - ): Promise { + ): Promise> { const { userAddress, contractAddress } = input this.verifyAddresses([userAddress, contractAddress]) const stakingContract = this.getStakingContract(contractAddress) try { - const estimatedGas = await stakingContract.methods.sendWithdrawalRequests().estimateGas({ - from: userAddress, + const data = stakingContract.interface.encodeFunctionData('sendWithdrawalRequests', []) + const feeData = await this.adapter.getFeeData({ + to: contractAddress, + value: '0', + chainSpecific: { + contractData: data, + from: userAddress, + }, }) - return bnOrZero(estimatedGas) - } catch (err) { - throw new Error(`Failed to get gas ${err}`) + + const { + chainSpecific: { gasLimit: gasLimitBase }, + } = feeData.fast + const safeGasLimit = bnOrZero(gasLimitBase).times('1.05').toFixed(0) + feeData.fast.chainSpecific.gasLimit = safeGasLimit + + return feeData + } catch (e) { + throw new Error(`Failed to get gas ${e}`) } } - async estimateAddLiquidityGas(input: EstimateGasTxInput): Promise { + async estimateAddLiquidityFees( + input: EstimateFeesTxInput, + ): Promise> { const { amountDesired, userAddress, contractAddress } = input this.verifyAddresses([userAddress, contractAddress]) if (!amountDesired.gt(0)) throw new Error('Must send valid amount') @@ -294,18 +313,33 @@ export class FoxyApi { const liquidityReserveContract = this.getLiquidityReserveContract(contractAddress) try { - const estimatedGas = await liquidityReserveContract.methods - .addLiquidity(this.normalizeAmount(amountDesired)) - .estimateGas({ + const data = liquidityReserveContract.interface.encodeFunctionData('addLiquidity', [ + this.normalizeAmount(amountDesired), + ]) + const feeData = await this.adapter.getFeeData({ + to: contractAddress, + value: '0', + chainSpecific: { + contractData: data, from: userAddress, - }) - return bnOrZero(estimatedGas) - } catch (err) { - throw new Error(`Failed to get gas ${err}`) + }, + }) + + const { + chainSpecific: { gasLimit: gasLimitBase }, + } = feeData.fast + const safeGasLimit = bnOrZero(gasLimitBase).times('1.05').toFixed(0) + feeData.fast.chainSpecific.gasLimit = safeGasLimit + + return feeData + } catch (e) { + throw new Error(`Failed to get gas ${e}`) } } - async estimateRemoveLiquidityGas(input: EstimateGasTxInput): Promise { + async estimateRemoveLiquidityFees( + input: EstimateFeesTxInput, + ): Promise> { const { amountDesired, userAddress, contractAddress } = input this.verifyAddresses([userAddress, contractAddress]) if (!amountDesired.gt(0)) throw new Error('Must send valid amount') @@ -313,18 +347,34 @@ export class FoxyApi { const liquidityReserveContract = this.getLiquidityReserveContract(contractAddress) try { - const estimatedGas = await liquidityReserveContract.methods - .removeLiquidity(this.normalizeAmount(amountDesired)) - .estimateGas({ + const data = liquidityReserveContract.encodeFunctionData('removeLiquidity', [ + this.normalizeAmount(amountDesired), + ]) + + const feeData = await this.adapter.getFeeData({ + to: contractAddress, + value: '0', + chainSpecific: { + contractData: data, from: userAddress, - }) - return bnOrZero(estimatedGas) - } catch (err) { - throw new Error(`Failed to get gas ${err}`) + }, + }) + + const { + chainSpecific: { gasLimit: gasLimitBase }, + } = feeData.fast + const safeGasLimit = bnOrZero(gasLimitBase).times('1.05').toFixed(0) + feeData.fast.chainSpecific.gasLimit = safeGasLimit + + return feeData + } catch (e) { + throw new Error(`Failed to get gas ${e}`) } } - async estimateWithdrawGas(input: WithdrawEstimateGasInput): Promise { + async estimateWithdrawFees( + input: EstimateWithdrawFeesInput, + ): Promise> { const { amountDesired, userAddress, contractAddress, type } = input this.verifyAddresses([userAddress, contractAddress]) @@ -334,40 +384,71 @@ export class FoxyApi { if (isDelayed && !amountDesired.gt(0)) throw new Error('Must send valid amount') try { - const estimatedGas = isDelayed - ? await stakingContract.methods - .unstake(this.normalizeAmount(amountDesired), true) - .estimateGas({ - from: userAddress, - }) - : await stakingContract.methods.instantUnstake(true).estimateGas({ - from: userAddress, - }) - return bnOrZero(estimatedGas) - } catch (err) { - throw new Error(`Failed to get gas ${err}`) + const data = isDelayed + ? stakingContract.interface.encodeFunctionData('unstake(uint256,bool)', [ + this.normalizeAmount(amountDesired), + true, + ]) + : stakingContract.interface.encodeFunctionData('instantUnstake', [true]) + + const feeData = await this.adapter.getFeeData({ + to: contractAddress, + value: '0', + chainSpecific: { + contractData: data, + from: userAddress, + }, + }) + + const { + chainSpecific: { gasLimit: gasLimitBase }, + } = feeData.fast + const safeGasLimit = bnOrZero(gasLimitBase).times('1.05').toFixed(0) + feeData.fast.chainSpecific.gasLimit = safeGasLimit + + return feeData + } catch (e) { + throw new Error(`Failed to get gas ${e}`) } } - async estimateApproveGas(input: EstimateGasApproveInput): Promise { + async estimateApproveFees( + input: EstimateApproveFeesInput, + ): Promise> { const { userAddress, tokenContractAddress, contractAddress } = input this.verifyAddresses([userAddress, contractAddress, tokenContractAddress]) - const depositTokenContract = new this.web3.eth.Contract(erc20Abi, tokenContractAddress) + const depositTokenContract = new ethers.Contract(tokenContractAddress, erc20Abi, this.provider) try { - const estimatedGas = await depositTokenContract.methods - .approve(contractAddress, MAX_ALLOWANCE) - .estimateGas({ + const data = depositTokenContract.interface.encodeFunctionData('approve', [ + contractAddress, + MAX_ALLOWANCE, + ]) + const feeData = await this.adapter.getFeeData({ + to: tokenContractAddress, + value: '0', + chainSpecific: { + contractData: data, from: userAddress, - }) - return bnOrZero(estimatedGas) - } catch (err) { - throw new Error(`Failed to get gas ${err}`) + }, + }) + + const { + chainSpecific: { gasLimit: gasLimitBase }, + } = feeData.fast + const safeGasLimit = bnOrZero(gasLimitBase).times('1.05').toFixed(0) + feeData.fast.chainSpecific.gasLimit = safeGasLimit + + return feeData + } catch (e) { + throw new Error(`Failed to get gas ${e}`) } } - async estimateDepositGas(input: EstimateGasTxInput): Promise { + async estimateDepositFees( + input: EstimateFeesTxInput, + ): Promise> { const { amountDesired, userAddress, contractAddress } = input this.verifyAddresses([userAddress, contractAddress]) if (!amountDesired.gt(0)) throw new Error('Must send valid amount') @@ -375,14 +456,28 @@ export class FoxyApi { const stakingContract = this.getStakingContract(contractAddress) try { - const estimatedGas = await stakingContract.methods - .stake(this.normalizeAmount(amountDesired), userAddress) - .estimateGas({ + const data = stakingContract.interface.encodeFunctionData('stake(uint256)', [ + this.normalizeAmount(amountDesired), + ]) + + const feeData = await this.adapter.getFeeData({ + to: contractAddress, + value: '0', + chainSpecific: { + contractData: data, from: userAddress, - }) - return bnOrZero(estimatedGas) - } catch (err) { - throw new Error(`Failed to get gas ${err}`) + }, + }) + + const { + chainSpecific: { gasLimit: gasLimitBase }, + } = feeData.fast + const safeGasLimit = bnOrZero(gasLimitBase).times('1.05').toFixed(0) + feeData.fast.chainSpecific.gasLimit = safeGasLimit + + return feeData + } catch (e) { + throw new Error(`Failed to get gas ${e}`) } } @@ -399,32 +494,19 @@ export class FoxyApi { this.verifyAddresses([userAddress, contractAddress, tokenContractAddress]) if (!wallet) throw new Error('Missing inputs') - let estimatedGasBN: BigNumber - try { - estimatedGasBN = await this.estimateApproveGas(input) - } catch (err) { - throw new Error(`Estimate Gas Error: ${err}`) - } - const depositTokenContract = new this.web3.eth.Contract(erc20Abi, tokenContractAddress) - const data: string = depositTokenContract.methods - .approve( - contractAddress, - amount ? numberToHex(this.normalizeAmount(bnOrZero(amount))) : MAX_ALLOWANCE, - ) - .encodeABI({ - from: userAddress, - }) + const estimatedFees = await this.estimateApproveFees(input) + const depositTokenContract = new ethers.Contract(tokenContractAddress, erc20Abi, this.provider) + const data: string = depositTokenContract.interface.encodeFunctionData('approve', [ + contractAddress, + amount ? this.normalizeAmount(bnOrZero(amount)) : MAX_ALLOWANCE, + ]) - const { nonce, gasPrice } = await this.getGasPriceAndNonce(userAddress) const chainReferenceAsNumber = Number(this.ethereumChainReference) - const estimatedGas = estimatedGasBN.toString() const payload = { bip44Params, chainId: chainReferenceAsNumber, data, - estimatedGas, - gasPrice, - nonce, + estimatedFees, to: tokenContractAddress, value: '0', } @@ -435,18 +517,14 @@ export class FoxyApi { const { userAddress, tokenContractAddress, contractAddress } = input this.verifyAddresses([userAddress, contractAddress, tokenContractAddress]) - const depositTokenContract: Contract = new this.web3.eth.Contract( - erc20Abi, + const depositTokenContract: ethers.Contract = new ethers.Contract( tokenContractAddress, + erc20Abi, + this.provider, ) - let allowance - try { - allowance = await depositTokenContract.methods.allowance(userAddress, contractAddress).call() - } catch (err) { - throw new Error(`Failed to get allowance ${err}`) - } - return allowance + const allowance = await depositTokenContract.allowance(userAddress, contractAddress) + return allowance.toString() } async deposit(input: TxInput): Promise { @@ -462,33 +540,21 @@ export class FoxyApi { if (!amountDesired.gt(0)) throw new Error('Must send valid amount') if (!wallet) throw new Error('Missing inputs') - let estimatedGasBN: BigNumber - try { - estimatedGasBN = await this.estimateDepositGas(input) - } catch (err) { - throw new Error(`Estimate Gas Error: ${err}`) - } + const estimatedFees = await this.estimateDepositFees(input) const stakingContract = this.getStakingContract(contractAddress) - const userChecksum = this.web3.utils.toChecksumAddress(userAddress) - const data: string = await stakingContract.methods - .stake(this.normalizeAmount(amountDesired), userAddress) - .encodeABI({ - value: 0, - from: userChecksum, - }) + const data = stakingContract.interface.encodeFunctionData('stake(uint256,address)', [ + this.normalizeAmount(amountDesired), + userAddress, + ]) - const { nonce, gasPrice } = await this.getGasPriceAndNonce(userAddress) - const estimatedGas = estimatedGasBN.toString() const chainReferenceAsNumber = Number(this.ethereumChainReference) const payload = { bip44Params, chainId: chainReferenceAsNumber, data, - estimatedGas, - gasPrice, - nonce, + estimatedFees, to: contractAddress, value: '0', } @@ -508,36 +574,26 @@ export class FoxyApi { this.verifyAddresses([userAddress, contractAddress]) if (!wallet) throw new Error('Missing inputs') - let estimatedGasBN: BigNumber - try { - estimatedGasBN = await this.estimateWithdrawGas(input) - } catch (err) { - throw new Error(`Estimate Gas Error: ${err}`) - } + const estimatedFees = await this.estimateWithdrawFees(input) const stakingContract = this.getStakingContract(contractAddress) const isDelayed = type === WithdrawType.DELAYED && amountDesired if (isDelayed && !amountDesired.gt(0)) throw new Error('Must send valid amount') - const data: string = isDelayed - ? stakingContract.methods.unstake(this.normalizeAmount(amountDesired), true).encodeABI({ - from: userAddress, - }) - : stakingContract.methods.instantUnstake(true).encodeABI({ - from: userAddress, - }) + const stakingContractCallInput: Parameters< + typeof stakingContract.interface.encodeFunctionData + > = isDelayed + ? ['unstake(uint256,bool)', [this.normalizeAmount(amountDesired), true]] + : ['instantUnstake', ['true']] + const data: string = stakingContract.interface.encodeFunctionData(...stakingContractCallInput) - const { nonce, gasPrice } = await this.getGasPriceAndNonce(userAddress) - const estimatedGas = estimatedGasBN.toString() const chainReferenceAsNumber = Number(this.ethereumChainReference) const payload = { bip44Params, chainId: chainReferenceAsNumber, data, - estimatedGas, - gasPrice, - nonce, + estimatedFees, to: contractAddress, value: '0', } @@ -546,54 +602,58 @@ export class FoxyApi { async canClaimWithdraw(input: CanClaimWithdrawParams): Promise { const { userAddress, contractAddress } = input - const tokeManagerContract = new this.web3.eth.Contract(tokeManagerAbi, tokeManagerAddress) - const tokePoolContract = new this.web3.eth.Contract(tokePoolAbi, tokePoolAddress) + const tokeManagerContract = new ethers.Contract( + tokeManagerAddress, + tokeManagerAbi, + this.provider, + ) + const tokePoolContract = new ethers.Contract(tokePoolAddress, tokePoolAbi, this.provider) const stakingContract = this.getStakingContract(contractAddress) const coolDownInfo = await (async () => { try { - const coolDown = await stakingContract.methods.coolDownInfo(userAddress).call() + const coolDown = await stakingContract.coolDownInfo(userAddress) return { ...coolDown, endEpoch: coolDown.expiry, } - } catch (err) { - logger.error(err, 'failed to get coolDowninfo') + } catch (e) { + logger.error(e, 'failed to get coolDowninfo') } })() const epoch = await (() => { try { - return stakingContract.methods.epoch().call() - } catch (err) { - logger.error(err, 'failed to get epoch') + return stakingContract.epoch() + } catch (e) { + logger.error(e, 'failed to get epoch') return {} } })() const requestedWithdrawals = await (() => { try { - return tokePoolContract.methods.requestedWithdrawals(stakingContract.options.address).call() - } catch (err) { - logger.error(err, 'failed to get requestedWithdrawals') + return tokePoolContract.requestedWithdrawals(stakingContract.address) + } catch (e) { + logger.error(e, 'failed to get requestedWithdrawals') return {} } })() const currentCycleIndex = await (() => { try { - return tokeManagerContract.methods.getCurrentCycleIndex().call() - } catch (err) { - logger.error(err, 'failed to get currentCycleIndex') + return tokeManagerContract.getCurrentCycleIndex() + } catch (e) { + logger.error(e, 'failed to get currentCycleIndex') return 0 } })() const withdrawalAmount = await (() => { try { - return stakingContract.methods.withdrawalAmount().call() - } catch (err) { - logger.error(err, 'failed to get currentCycleIndex') + return stakingContract.withdrawalAmount() + } catch (e) { + logger.error(e, 'failed to get currentCycleIndex') return 0 } })() @@ -627,32 +687,23 @@ export class FoxyApi { this.verifyAddresses([userAddress, contractAddress, addressToClaim]) if (!wallet) throw new Error('Missing inputs') - let estimatedGasBN: BigNumber - try { - estimatedGasBN = await this.estimateClaimWithdrawGas(input) - } catch (err) { - throw new Error(`Estimate Gas Error: ${err}`) - } + const estimatedFees = await this.estimateClaimWithdrawFees(input) const stakingContract = this.getStakingContract(contractAddress) const canClaim = await this.canClaimWithdraw({ userAddress, contractAddress }) if (!canClaim) throw new Error('Not ready to claim') - const data: string = stakingContract.methods.claimWithdraw(addressToClaim).encodeABI({ - from: userAddress, - }) + const data: string = stakingContract.interface.encodeFunctionData('claimWithdraw', [ + addressToClaim, + ]) - const { nonce, gasPrice } = await this.getGasPriceAndNonce(userAddress) - const estimatedGas = estimatedGasBN.toString() const chainReferenceAsNumber = Number(this.ethereumChainReference) const payload = { bip44Params, chainId: chainReferenceAsNumber, data, - estimatedGas, - gasPrice, - nonce, + estimatedFees, to: contractAddress, value: '0', } @@ -661,22 +712,26 @@ export class FoxyApi { async canSendWithdrawalRequest(input: StakingContract): Promise { const { stakingContract } = input - const tokeManagerContract = new this.web3.eth.Contract(tokeManagerAbi, tokeManagerAddress) + const tokeManagerContract = new ethers.Contract( + tokeManagerAddress, + tokeManagerAbi, + this.provider, + ) const requestWithdrawalAmount = await (() => { try { - return stakingContract.methods.requestWithdrawalAmount().call() - } catch (err) { - logger.error(err, 'failed to get requestWithdrawalAmount') + return stakingContract.requestWithdrawalAmount() + } catch (e) { + logger.error(e, 'failed to get requestWithdrawalAmount') return 0 } })() const timeLeftToRequestWithdrawal = await (() => { try { - return stakingContract.methods.timeLeftToRequestWithdrawal().call() - } catch (err) { - logger.error(err, 'failed to get timeLeftToRequestWithdrawal') + return stakingContract.timeLeftToRequestWithdrawal() + } catch (e) { + logger.error(e, 'failed to get timeLeftToRequestWithdrawal') return 0 } })() @@ -692,35 +747,35 @@ export class FoxyApi { const duration = await (() => { try { - return tokeManagerContract.methods.getCycleDuration().call() - } catch (err) { - logger.error(err, 'failed to get cycleDuration') + return tokeManagerContract.getCycleDuration() + } catch (e) { + logger.error(e, 'failed to get cycleDuration') return 0 } })() const currentCycleIndex = await (() => { try { - return tokeManagerContract.methods.getCurrentCycleIndex().call() - } catch (err) { - logger.error(err, 'failed to get currentCycleIndex') + return tokeManagerContract.getCurrentCycleIndex() + } catch (e) { + logger.error(e, 'failed to get currentCycleIndex') return 0 } })() const currentCycleStart = await (() => { try { - return tokeManagerContract.methods.getCurrentCycle().call() - } catch (err) { - logger.error(err, 'failed to get currentCycle') + return tokeManagerContract.getCurrentCycle() + } catch (e) { + logger.error(e, 'failed to get currentCycle') return 0 } })() const nextCycleStart = bnOrZero(currentCycleStart).plus(duration) - const blockNumber = await this.web3.eth.getBlockNumber() - const timestamp = (await this.web3.eth.getBlock(blockNumber)).timestamp + const blockNumber = await this.provider.getBlockNumber() + const timestamp = (await this.provider.getBlock(blockNumber)).timestamp const isTimeToRequest = bnOrZero(timestamp) .plus(timeLeftToRequestWithdrawal) @@ -736,32 +791,20 @@ export class FoxyApi { this.verifyAddresses([userAddress, contractAddress]) if (!wallet || !contractAddress) throw new Error('Missing inputs') - let estimatedGasBN: BigNumber - try { - estimatedGasBN = await this.estimateSendWithdrawalRequestsGas(input) - } catch (err) { - throw new Error(`Estimate Gas Error: ${err}`) - } + const estimatedFees = await this.estimateSendWithdrawalRequestsFees(input) const stakingContract = this.getStakingContract(contractAddress) const canSendRequest = await this.canSendWithdrawalRequest({ stakingContract }) if (!canSendRequest) throw new Error('Not ready to send request') - const data: string = stakingContract.methods.sendWithdrawalRequests().encodeABI({ - from: userAddress, - }) - - const { nonce, gasPrice } = await this.getGasPriceAndNonce(userAddress) - const estimatedGas = estimatedGasBN.toString() + const data: string = stakingContract.interface.encodeFunctionData('sendWithdrawalRequests') const chainReferenceAsNumber = Number(this.ethereumChainReference) const payload = { bip44Params, chainId: chainReferenceAsNumber, data, - estimatedGas, - gasPrice, - nonce, + estimatedFees, to: contractAddress, value: '0', } @@ -784,31 +827,19 @@ export class FoxyApi { if (!wallet) throw new Error('Missing inputs') - let estimatedGasBN: BigNumber - try { - estimatedGasBN = await this.estimateAddLiquidityGas(input) - } catch (err) { - throw new Error(`Estimate Gas Error: ${err}`) - } - + const estimatedFees = await this.estimateAddLiquidityFees(input) const liquidityReserveContract = this.getLiquidityReserveContract(contractAddress) - const data: string = liquidityReserveContract.methods - .addLiquidity(this.normalizeAmount(amountDesired)) - .encodeABI({ - from: userAddress, - }) + const data: string = liquidityReserveContract.interface.encodeFunctionData('addLiquidity', [ + this.normalizeAmount(amountDesired), + ]) - const { nonce, gasPrice } = await this.getGasPriceAndNonce(userAddress) - const estimatedGas = estimatedGasBN.toString() const chainReferenceAsNumber = Number(this.ethereumChainReference) const payload = { bip44Params, chainId: chainReferenceAsNumber, data, - estimatedGas, - gasPrice, - nonce, + estimatedFees, to: contractAddress, value: '0', } @@ -830,31 +861,20 @@ export class FoxyApi { if (!amountDesired.gt(0)) throw new Error('Must send valid amount') if (!wallet) throw new Error('Missing inputs') - let estimatedGasBN: BigNumber - try { - estimatedGasBN = await this.estimateRemoveLiquidityGas(input) - } catch (err) { - throw new Error(`Estimate Gas Error: ${err}`) - } + const estimatedFees = await this.estimateRemoveLiquidityFees(input) const liquidityReserveContract = this.getLiquidityReserveContract(contractAddress) - const data: string = liquidityReserveContract.methods - .removeLiquidity(this.normalizeAmount(amountDesired)) - .encodeABI({ - from: userAddress, - }) + const data: string = liquidityReserveContract.interface.encodeFunctionData('removeLiquidity', [ + this.normalizeAmount(amountDesired), + ]) - const { nonce, gasPrice } = await this.getGasPriceAndNonce(userAddress) - const estimatedGas = estimatedGasBN.toString() const chainReferenceAsNumber = Number(this.ethereumChainReference) const payload = { bip44Params, chainId: chainReferenceAsNumber, data, - estimatedGas, - gasPrice, - nonce, + estimatedFees, to: contractAddress, value: '0', } @@ -870,25 +890,25 @@ export class FoxyApi { let coolDownInfo try { - const coolDown = await stakingContract.methods.coolDownInfo(userAddress).call() + const coolDown = await stakingContract.coolDownInfo(userAddress) coolDownInfo = { ...coolDown, endEpoch: coolDown.expiry, } - } catch (err) { - throw new Error(`Failed to get coolDowninfo: ${err}`) + } catch (e) { + throw new Error(`Failed to get coolDowninfo: ${e}`) } let epoch try { - epoch = await stakingContract.methods.epoch().call() - } catch (err) { - throw new Error(`Failed to get epoch: ${err}`) + epoch = await stakingContract.epoch() + } catch (e) { + throw new Error(`Failed to get epoch: ${e}`) } let currentBlock try { - currentBlock = await this.web3.eth.getBlockNumber() - } catch (err) { - throw new Error(`Failed to get block number: ${err}`) + currentBlock = await this.provider.getBlockNumber() + } catch (e) { + throw new Error(`Failed to get block number: ${e}`) } const epochsLeft = bnOrZero(coolDownInfo.endEpoch).minus(epoch.number) // epochs left until can claim const blocksLeftInCurrentEpoch = @@ -908,12 +928,12 @@ export class FoxyApi { const { tokenContractAddress, userAddress } = input this.verifyAddresses([userAddress, tokenContractAddress]) - const contract = new this.web3.eth.Contract(erc20Abi, tokenContractAddress) + const contract = new ethers.Contract(tokenContractAddress, erc20Abi, this.provider) try { - const balance = await contract.methods.balanceOf(userAddress).call() + const balance = await contract.balanceOf(userAddress) return bnOrZero(balance) - } catch (err) { - throw new Error(`Failed to get balance: ${err}`) + } catch (e) { + throw new Error(`Failed to get balance: ${e}`) } } @@ -924,28 +944,28 @@ export class FoxyApi { let liquidityReserveAddress try { - liquidityReserveAddress = await stakingContract.methods.LIQUIDITY_RESERVE().call() - } catch (err) { - throw new Error(`Failed to get liquidityReserve address ${err}`) + liquidityReserveAddress = await stakingContract.LIQUIDITY_RESERVE() + } catch (e) { + throw new Error(`Failed to get liquidityReserve address ${e}`) } const liquidityReserveContract = this.getLiquidityReserveContract(liquidityReserveAddress) try { - const feeInBasisPoints = await liquidityReserveContract.methods.fee().call() + const feeInBasisPoints = await liquidityReserveContract.fee() return bnOrZero(feeInBasisPoints).div(10000) // convert from basis points to decimal percentage - } catch (err) { - throw new Error(`Failed to get instantUnstake fee ${err}`) + } catch (e) { + throw new Error(`Failed to get instantUnstake fee ${e}`) } } async totalSupply({ tokenContractAddress }: TokenAddressInput): Promise { this.verifyAddresses([tokenContractAddress]) - const contract = new this.web3.eth.Contract(erc20Abi, tokenContractAddress) + const contract = new ethers.Contract(tokenContractAddress, erc20Abi, this.provider) try { - const totalSupply = await contract.methods.totalSupply().call() + const totalSupply = await contract.totalSupply() return bnOrZero(totalSupply) - } catch (err) { - throw new Error(`Failed to get totalSupply: ${err}`) + } catch (e) { + throw new Error(`Failed to get totalSupply: ${e}`) } } @@ -961,13 +981,13 @@ export class FoxyApi { async tvl(input: TokenAddressInput): Promise { const { tokenContractAddress } = input this.verifyAddresses([tokenContractAddress]) - const contract = new this.web3.eth.Contract(foxyAbi, tokenContractAddress) + const contract = new ethers.Contract(tokenContractAddress, foxyAbi, this.provider) try { - const balance = await contract.methods.circulatingSupply().call() + const balance = await contract.circulatingSupply() return bnOrZero(balance) - } catch (err) { - throw new Error(`Failed to get tvl: ${err}`) + } catch (e) { + throw new Error(`Failed to get tvl: ${e}`) } } @@ -976,39 +996,49 @@ export class FoxyApi { this.verifyAddresses([userAddress, contractAddress]) const stakingContract = this.getStakingContract(contractAddress) - let coolDownInfo + let coolDownInfo: [amount: string, gons: string, expiry: string] try { - coolDownInfo = await stakingContract.methods.coolDownInfo(userAddress).call() - } catch (err) { - throw new Error(`Failed to get coolDowninfo: ${err}`) + coolDownInfo = (await stakingContract.coolDownInfo(userAddress)).map( + (info: ethers.BigNumber) => info.toString(), + ) + } catch (e) { + throw new Error(`Failed to get coolDowninfo: ${e}`) } let releaseTime try { releaseTime = await this.getTimeUntilClaimable(input) - } catch (err) { - throw new Error(`Failed to getTimeUntilClaimable: ${err}`) + } catch (e) { + throw new Error(`Failed to getTimeUntilClaimable: ${e}`) } + + const [amount, gons, expiry] = coolDownInfo return { - ...coolDownInfo, + amount, + gons, + expiry, releaseTime, } } async getClaimFromTokemakArgs(input: ContractAddressInput): Promise { const { contractAddress } = input - const rewardHashContract = new this.web3.eth.Contract(tokeRewardHashAbi, tokeRewardHashAddress) + const rewardHashContract = new ethers.Contract( + tokeRewardHashAddress, + tokeRewardHashAbi, + this.provider, + ) const latestCycleIndex = await (() => { try { - return rewardHashContract.methods.latestCycleIndex().call() - } catch (err) { - throw new Error(`Failed to get latestCycleIndex, ${err}`) + return rewardHashContract.latestCycleIndex() + } catch (e) { + throw new Error(`Failed to get latestCycleIndex, ${e}`) } })() const cycleHashes = await (() => { try { - return rewardHashContract.methods.cycleHashes(latestCycleIndex).call() - } catch (err) { - throw new Error(`Failed to get latestCycleIndex, ${err}`) + return rewardHashContract.cycleHashes(latestCycleIndex) + } catch (e) { + throw new Error(`Failed to get latestCycleIndex, ${e}`) } })() @@ -1030,8 +1060,8 @@ export class FoxyApi { s, recipient: payload, } - } catch (err) { - throw new Error(`Failed to get information from Tokemak ipfs ${err}`) + } catch (e) { + throw new Error(`Failed to get information from Tokemak ipfs ${e}`) } } @@ -1039,20 +1069,19 @@ export class FoxyApi { const { tokenContractAddress, userAddress } = input this.verifyAddresses([tokenContractAddress]) - const foxyContract = new this.web3.eth.Contract(foxyAbi, tokenContractAddress) + const foxyContract = new ethers.Contract(tokenContractAddress, foxyAbi, this.provider) const fromBlock = 14381454 // genesis rebase const rebaseEvents = await (async () => { try { - const events = ( - await foxyContract.getPastEvents('LogRebase', { - fromBlock, - toBlock: 'latest', - }) - ).filter(rebase => rebase.returnValues.rebase !== '0') - return events - } catch (err) { - logger.error(err, 'failed to get rebase events') + const filter = foxyContract.filters.LogRebase() + const events = await foxyContract.queryFilter(filter, fromBlock, 'latest') + const filteredEvents = events.filter( + rebase => rebase.args?.rebase && !rebase.args.rebase.isZero(), + ) + return filteredEvents + } catch (e) { + logger.error(e, 'failed to get rebase events') return undefined } })() @@ -1061,22 +1090,17 @@ export class FoxyApi { const transferEvents = await (async () => { try { - const events = await foxyContract.getPastEvents('Transfer', { - fromBlock, - toBlock: 'latest', - }) + const filter = foxyContract.filters.Transfer() + const events = await foxyContract.queryFilter(filter, fromBlock, 'latest') return events - } catch (err) { - logger.error(err, 'failed to get transfer events') + } catch (e) { + logger.error(e, 'failed to get transfer events') return undefined } })() const events: RebaseEvent[] = rebaseEvents.map(rebaseEvent => { - const { - blockNumber, - returnValues: { epoch }, - } = rebaseEvent + const { blockNumber, args: { epoch } = { epoch: '' } } = rebaseEvent return { blockNumber, epoch, @@ -1097,23 +1121,20 @@ export class FoxyApi { // check transfer events to see if a user triggered a rebase through unstake or stake const unstakedTransferInfo = transferEvents?.filter( e => - e.blockNumber === event.blockNumber && - e.returnValues.from.toLowerCase() === userAddress, + e.blockNumber === event.blockNumber && e.args?.from.toLowerCase() === userAddress, ) - const unstakedTransferAmount = unstakedTransferInfo?.[0]?.returnValues?.value ?? 0 + const unstakedTransferAmount = unstakedTransferInfo?.[0]?.args?.value ?? 0 const stakedTransferInfo = transferEvents?.filter( - e => - e.blockNumber === event.blockNumber && - e.returnValues.to.toLowerCase() === userAddress, + e => e.blockNumber === event.blockNumber && e.args?.to.toLowerCase() === userAddress, ) - const stakedTransferAmount = stakedTransferInfo?.[0]?.returnValues?.value ?? 0 + const stakedTransferAmount = stakedTransferInfo?.[0]?.args?.value ?? 0 - const postRebaseBalanceResult = await foxyContract.methods - .balanceOf(userAddress) - .call(null, event.blockNumber) - const unadjustedPreRebaseBalance = await foxyContract.methods - .balanceOf(userAddress) - .call(null, event.blockNumber - 1) + const postRebaseBalanceResult = await foxyContract.balanceOf(userAddress, { + blockTag: event.blockNumber, + }) + const unadjustedPreRebaseBalance = await foxyContract.balanceOf(userAddress, { + blockTag: event.blockNumber - 1, + }) // unstake events can trigger rebases, if they do, adjust the amount to not include that unstake's transfer amount const preRebaseBalanceResult = bnOrZero(unadjustedPreRebaseBalance) @@ -1125,8 +1146,8 @@ export class FoxyApi { preRebaseBalance: preRebaseBalanceResult, postRebaseBalance: postRebaseBalanceResult, } - } catch (err) { - logger.error(err, 'failed to get balance of address') + } catch (e) { + logger.error(e, 'failed to get balance of address') return { preRebaseBalance: bn(0).toString(), postRebaseBalance: bn(0).toString(), @@ -1136,10 +1157,10 @@ export class FoxyApi { const blockTime = await (async () => { try { - const block = await this.web3.eth.getBlock(event.blockNumber) + const block = await this.provider.getBlock(event.blockNumber) return bnOrZero(block.timestamp).toNumber() - } catch (err) { - logger.error(err, 'failed to get timestamp of block') + } catch (e) { + logger.error(e, 'failed to get timestamp of block') return 0 } })() diff --git a/packages/investor-foxy/src/api/foxy-types.ts b/packages/investor-foxy/src/api/foxy-types.ts index ef81dde1806..ce166d5ef06 100644 --- a/packages/investor-foxy/src/api/foxy-types.ts +++ b/packages/investor-foxy/src/api/foxy-types.ts @@ -1,8 +1,9 @@ import type { AssetId } from '@shapeshiftoss/caip' -import type { HDWallet } from '@shapeshiftoss/hdwallet-core' -import type { BIP44Params, WithdrawType } from '@shapeshiftoss/types' +import type { FeeDataEstimate } from '@shapeshiftoss/chain-adapters' +import type { ETHWallet } from '@shapeshiftoss/hdwallet-core' +import type { BIP44Params, KnownChainIds, WithdrawType } from '@shapeshiftoss/types' import type { BigNumber } from 'bignumber.js' -import type { Contract } from 'web3-eth-contract' +import type { Contract } from 'ethers' export type FoxyAddressesType = { staking: string @@ -27,10 +28,10 @@ export type ApproveInput = { tokenContractAddress: string contractAddress: string userAddress: string - wallet: HDWallet + wallet: ETHWallet } -export type EstimateGasApproveInput = Pick< +export type EstimateApproveFeesInput = Pick< ApproveInput, 'userAddress' | 'tokenContractAddress' | 'contractAddress' > @@ -41,7 +42,7 @@ export type TxInput = { tokenContractAddress?: string userAddress: string contractAddress: string - wallet: HDWallet + wallet: ETHWallet amountDesired: BigNumber } @@ -57,7 +58,7 @@ export type WithdrawInput = Omit & { amountDesired?: BigNumber } -export type WithdrawEstimateGasInput = Omit +export type EstimateWithdrawFeesInput = Omit export type FoxyOpportunityInputData = { tvl: BigNumber @@ -69,7 +70,7 @@ export type FoxyOpportunityInputData = { liquidityReserve: string } -export type EstimateGasTxInput = Pick< +export type EstimateFeesTxInput = Pick< TxInput, 'tokenContractAddress' | 'contractAddress' | 'userAddress' | 'amountDesired' > @@ -102,16 +103,14 @@ export type SignAndBroadcastPayload = { bip44Params: BIP44Params chainId: number data: string - estimatedGas: string - gasPrice: string - nonce: string + estimatedFees: FeeDataEstimate to: string value: string } export type SignAndBroadcastTx = { payload: SignAndBroadcastPayload - wallet: HDWallet + wallet: ETHWallet dryRun: boolean } diff --git a/packages/investor-foxy/src/utils/buildTxToSign.ts b/packages/investor-foxy/src/utils/buildTxToSign.ts deleted file mode 100644 index 37c3843b67f..00000000000 --- a/packages/investor-foxy/src/utils/buildTxToSign.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { toAddressNList } from '@shapeshiftoss/chain-adapters' -import type { ETHSignTx } from '@shapeshiftoss/hdwallet-core' -import type { BIP44Params } from '@shapeshiftoss/types' -import { numberToHex } from 'web3-utils' - -type BuildTxToSignInput = { - bip44Params: BIP44Params - chainId: number - data: string - estimatedGas: string - gasPrice: string - nonce: string - value: string - to: string -} - -export const buildTxToSign = ({ - bip44Params, - chainId = 1, - data, - estimatedGas, - gasPrice, - nonce, - to, - value, -}: BuildTxToSignInput): ETHSignTx => ({ - addressNList: toAddressNList(bip44Params), - value: numberToHex(value), - to, - chainId, // TODO: implement for multiple chains - data, - nonce: numberToHex(nonce), - gasPrice: numberToHex(gasPrice), - gasLimit: numberToHex(estimatedGas), -}) diff --git a/packages/investor-foxy/src/utils/index.ts b/packages/investor-foxy/src/utils/index.ts index dfd9ab73da5..fcd8f6c533e 100644 --- a/packages/investor-foxy/src/utils/index.ts +++ b/packages/investor-foxy/src/utils/index.ts @@ -1,2 +1 @@ export * from './bignumber' -export * from './buildTxToSign' diff --git a/packages/investor-idle/package.json b/packages/investor-idle/package.json index 6c498e03ec4..62d528edaa0 100644 --- a/packages/investor-idle/package.json +++ b/packages/investor-idle/package.json @@ -21,7 +21,6 @@ "cli": "yarn build && yarn node dist/idlecli.js" }, "dependencies": { - "@ethersproject/providers": "^5.5.3", "@shapeshiftoss/caip": "workspace:^", "@shapeshiftoss/chain-adapters": "workspace:^", "@shapeshiftoss/investor": "workspace:^", diff --git a/packages/investor-yearn/package.json b/packages/investor-yearn/package.json index 3ff0b9af293..94156bb281e 100644 --- a/packages/investor-yearn/package.json +++ b/packages/investor-yearn/package.json @@ -21,7 +21,6 @@ "cli": "yarn build && yarn node dist/yearncli.js" }, "dependencies": { - "@ethersproject/providers": "^5.5.3", "@shapeshiftoss/caip": "workspace:^", "@shapeshiftoss/chain-adapters": "workspace:^", "@shapeshiftoss/investor": "workspace:^", diff --git a/packages/investor-yearn/src/YearnInvestor.ts b/packages/investor-yearn/src/YearnInvestor.ts index f31329da6ab..f0dc75c1497 100644 --- a/packages/investor-yearn/src/YearnInvestor.ts +++ b/packages/investor-yearn/src/YearnInvestor.ts @@ -1,8 +1,8 @@ -import { JsonRpcProvider } from '@ethersproject/providers' import type { ChainAdapter } from '@shapeshiftoss/chain-adapters' import type { Investor } from '@shapeshiftoss/investor' import type { KnownChainIds } from '@shapeshiftoss/types' import { type ChainId, type VaultMetadata, Yearn } from '@yfi/sdk' +import { ethers } from 'ethers' import { find } from 'lodash' import filter from 'lodash/filter' import Web3 from 'web3' @@ -31,7 +31,7 @@ export class YearnInvestor implements Investor implements SubParser { - provider: ethers.providers.JsonRpcProvider + provider: ethers.providers.JsonRpcBatchProvider readonly chainId: ChainId readonly abiInterface = new ethers.utils.Interface(bep20) diff --git a/packages/unchained-client/src/evm/ethereum/parser/uniV2.ts b/packages/unchained-client/src/evm/ethereum/parser/uniV2.ts index 4006b88c755..02929d69afb 100644 --- a/packages/unchained-client/src/evm/ethereum/parser/uniV2.ts +++ b/packages/unchained-client/src/evm/ethereum/parser/uniV2.ts @@ -23,12 +23,11 @@ export interface TxMetadata extends BaseTxMetadata { export interface ParserArgs { chainId: ChainId - provider: ethers.providers.JsonRpcProvider + provider: ethers.providers.JsonRpcBatchProvider } export class Parser implements SubParser { - provider: ethers.providers.JsonRpcProvider - + provider: ethers.providers.JsonRpcBatchProvider readonly chainId: ChainId readonly wethContract: string readonly abiInterface = new ethers.utils.Interface(UNIV2_ABI) diff --git a/packages/unchained-client/src/evm/ethereum/parser/weth.ts b/packages/unchained-client/src/evm/ethereum/parser/weth.ts index 1310dd86e12..704cc270d00 100644 --- a/packages/unchained-client/src/evm/ethereum/parser/weth.ts +++ b/packages/unchained-client/src/evm/ethereum/parser/weth.ts @@ -16,12 +16,11 @@ export interface TxMetadata extends BaseTxMetadata { export interface ParserArgs { chainId: ChainId - provider: ethers.providers.JsonRpcProvider + provider: ethers.providers.JsonRpcBatchProvider } export class Parser implements SubParser { - provider: ethers.providers.JsonRpcProvider - + provider: ethers.providers.JsonRpcBatchProvider readonly chainId: ChainId readonly wethContract: string readonly abiInterface = new ethers.utils.Interface(WETH_ABI) diff --git a/packages/unchained-client/src/evm/parser/erc20.ts b/packages/unchained-client/src/evm/parser/erc20.ts index 7f799af86a7..51a17cfa4b6 100644 --- a/packages/unchained-client/src/evm/parser/erc20.ts +++ b/packages/unchained-client/src/evm/parser/erc20.ts @@ -16,11 +16,11 @@ export interface TxMetadata extends BaseTxMetadata { interface ParserArgs { chainId: ChainId - provider: ethers.providers.JsonRpcProvider + provider: ethers.providers.JsonRpcBatchProvider } export class Parser implements SubParser { - provider: ethers.providers.JsonRpcProvider + provider: ethers.providers.JsonRpcBatchProvider readonly chainId: ChainId readonly abiInterface = new ethers.utils.Interface(ERC20_ABI) diff --git a/packages/unchained-client/src/evm/parser/index.ts b/packages/unchained-client/src/evm/parser/index.ts index 9c1c0d1c269..982c8f1e801 100644 --- a/packages/unchained-client/src/evm/parser/index.ts +++ b/packages/unchained-client/src/evm/parser/index.ts @@ -21,14 +21,13 @@ export class BaseTransactionParser { chainId: ChainId assetId: AssetId - protected readonly provider: ethers.providers.JsonRpcProvider - + protected readonly provider: ethers.providers.JsonRpcBatchProvider private parsers: SubParser[] = [] constructor(args: TransactionParserArgs) { this.chainId = args.chainId this.assetId = args.assetId - this.provider = new ethers.providers.JsonRpcProvider(args.rpcUrl) + this.provider = new ethers.providers.JsonRpcBatchProvider(args.rpcUrl) } /** diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx index 25414b8f7f1..ba4c110db25 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx @@ -2,6 +2,7 @@ import { Center } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' import { toAssetId } from '@shapeshiftoss/caip' import { KnownChainIds } from '@shapeshiftoss/types' +import { ethers } from 'ethers' import { DefiModalContent } from 'features/defi/components/DefiModal/DefiModalContent' import { DefiModalHeader } from 'features/defi/components/DefiModal/DefiModalHeader' import type { @@ -53,9 +54,14 @@ export const FoxyDeposit: React.FC<{ const translate = useTranslate() const [state, dispatch] = useReducer(reducer, initialState) const { query, history, location } = useBrowserRouter() - const { chainId, contractAddress, assetReference, assetNamespace } = query + const { + chainId, + contractAddress: foxyContractAddress, + assetReference: foxyStakingContractAddress, + assetNamespace, + } = query // ContractAssetId - const assetId = toAssetId({ chainId, assetNamespace, assetReference }) + const assetId = toAssetId({ chainId, assetNamespace, assetReference: foxyStakingContractAddress }) const opportunityMetadataFilter = useMemo(() => ({ stakingId: assetId as StakingId }), [assetId]) const opportunityMetadata = useAppSelector(state => @@ -83,7 +89,7 @@ export const FoxyDeposit: React.FC<{ if ( !( walletState.wallet && - contractAddress && + foxyStakingContractAddress && !isFoxyAprLoading && chainAdapter && foxyApi && @@ -91,7 +97,9 @@ export const FoxyDeposit: React.FC<{ ) ) return - const foxyOpportunity = await foxyApi.getFoxyOpportunityByStakingAddress(contractAddress) + const foxyOpportunity = await foxyApi.getFoxyOpportunityByStakingAddress( + ethers.utils.getAddress(foxyStakingContractAddress), + ) dispatch({ type: FoxyDepositActionType.SET_OPPORTUNITY, payload: { ...foxyOpportunity, apy: foxyAprData?.foxyApr ?? '' }, @@ -105,10 +113,11 @@ export const FoxyDeposit: React.FC<{ foxyApi, bip44Params, chainAdapterManager, - contractAddress, + foxyContractAddress, walletState.wallet, foxyAprData?.foxyApr, isFoxyAprLoading, + foxyStakingContractAddress, ]) const handleBack = () => { @@ -136,7 +145,7 @@ export const FoxyDeposit: React.FC<{ label: translate('defi.steps.approve.title'), component: ownProps => , props: { - contractAddress, + contractAddress: foxyContractAddress, }, }, [DefiStep.Confirm]: { @@ -148,7 +157,7 @@ export const FoxyDeposit: React.FC<{ component: Status, }, } - }, [accountId, handleAccountIdChange, contractAddress, translate, stakingAsset.symbol]) + }, [accountId, handleAccountIdChange, foxyContractAddress, translate, stakingAsset.symbol]) if (loading || !stakingAsset || !marketData) { return ( diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Approve.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Approve.tsx index a65168ab6fc..c30474b284d 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Approve.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Approve.tsx @@ -1,6 +1,7 @@ import { useToast } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' import { fromAccountId } from '@shapeshiftoss/caip' +import { supportsETH } from '@shapeshiftoss/hdwallet-core' import { Approve as ReusableApprove } from 'features/defi/components/Approve/Approve' import { ApprovePreFooter } from 'features/defi/components/Approve/ApprovePreFooter' import type { DepositValues } from 'features/defi/components/Deposit/Deposit' @@ -68,17 +69,18 @@ export const Approve: React.FC = ({ accountId, onNext }) => { async (deposit: DepositValues) => { if (!accountAddress || !assetReference || !foxyApi) return try { - const [gasLimit, gasPrice] = await Promise.all([ - foxyApi.estimateDepositGas({ - tokenContractAddress: assetReference, - contractAddress, - amountDesired: bnOrZero(deposit.cryptoAmount) - .times(bn(10).pow(asset.precision)) - .decimalPlaces(0), - userAddress: accountAddress, - }), - foxyApi.getGasPrice(), - ]) + const feeDataEstimate = await foxyApi.estimateDepositFees({ + tokenContractAddress: assetReference, + contractAddress, + amountDesired: bnOrZero(deposit.cryptoAmount) + .times(bn(10).pow(asset.precision)) + .decimalPlaces(0), + userAddress: accountAddress, + }) + + const { + chainSpecific: { gasPrice, gasLimit }, + } = feeDataEstimate.fast return bnOrZero(gasPrice).times(gasLimit).toFixed(0) } catch (error) { moduleLogger.error( @@ -111,6 +113,10 @@ export const Approve: React.FC = ({ accountId, onNext }) => { return try { dispatch({ type: FoxyDepositActionType.SET_LOADING, payload: true }) + + if (!supportsETH(walletState.wallet)) + throw new Error(`handleApprove: wallet does not support ethereum`) + await foxyApi.approve({ tokenContractAddress: assetReference, contractAddress, diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Confirm.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Confirm.tsx index ccea6722c07..d9b89ad4e2c 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Confirm.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Confirm.tsx @@ -1,6 +1,8 @@ import { Alert, AlertIcon, Box, Stack, useToast } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' import { fromAccountId } from '@shapeshiftoss/caip' +import { supportsETH } from '@shapeshiftoss/hdwallet-core' +import type { ethers } from 'ethers' import { Confirm as ReusableConfirm } from 'features/defi/components/Confirm/Confirm' import { Summary } from 'features/defi/components/Summary' import { DefiStep } from 'features/defi/contexts/DefiManagerProvider/DefiCommon' @@ -8,7 +10,6 @@ import { useFoxyQuery } from 'features/defi/providers/foxy/components/FoxyManage import isNil from 'lodash/isNil' import { useCallback, useContext, useMemo } from 'react' import { useTranslate } from 'react-polyglot' -import type { TransactionReceipt } from 'web3-core/types' import { Amount } from 'components/Amount/Amount' import { AssetIcon } from 'components/AssetIcon' import type { StepComponentProps } from 'components/DeFi/components/Steps' @@ -81,33 +82,36 @@ export const Confirm: React.FC = ({ onNext, accountId }) => { return try { dispatch({ type: FoxyDepositActionType.SET_LOADING, payload: true }) - const [txid, gasPrice] = await Promise.all([ - foxyApi.deposit({ - amountDesired: bnOrZero(state?.deposit.cryptoAmount) - .times(bn(10).pow(asset.precision)) - .decimalPlaces(0), - tokenContractAddress: assetReference, - userAddress: accountAddress, - contractAddress, - wallet: walletState.wallet, - bip44Params, - }), - foxyApi.getGasPrice(), - ]) + + if (!supportsETH(walletState.wallet)) + throw new Error(`handleDeposit: wallet does not support ethereum`) + + const txid = await foxyApi.deposit({ + amountDesired: bnOrZero(state?.deposit.cryptoAmount) + .times(bn(10).pow(asset.precision)) + .decimalPlaces(0), + tokenContractAddress: assetReference, + userAddress: accountAddress, + contractAddress, + wallet: walletState.wallet, + bip44Params, + }) dispatch({ type: FoxyDepositActionType.SET_TXID, payload: txid }) onNext(DefiStep.Status) const transactionReceipt = await poll({ fn: () => foxyApi.getTxReceipt({ txid }), - validate: (result: TransactionReceipt) => !isNil(result), + validate: (result: ethers.providers.TransactionReceipt) => !isNil(result), interval: 15000, maxAttempts: 30, }) dispatch({ type: FoxyDepositActionType.SET_DEPOSIT, payload: { - txStatus: transactionReceipt.status === true ? 'success' : 'failed', - usedGasFeeCryptoBaseUnit: bnOrZero(gasPrice).times(transactionReceipt.gasUsed).toFixed(0), + txStatus: transactionReceipt.status ? 'success' : 'failed', + usedGasFeeCryptoBaseUnit: transactionReceipt.effectiveGasPrice + .mul(transactionReceipt.gasUsed) + .toString(), }, }) } catch (error) { diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Deposit.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Deposit.tsx index 2ba9a77c8c8..03082cb70aa 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Deposit.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/components/Deposit.tsx @@ -69,14 +69,16 @@ export const Deposit: React.FC = ({ const getApproveGasEstimate = async () => { if (!accountAddress || !assetReference || !foxyApi) return try { - const [gasLimit, gasPrice] = await Promise.all([ - foxyApi.estimateApproveGas({ - tokenContractAddress: assetReference, - contractAddress, - userAddress: accountAddress, - }), - foxyApi.getGasPrice(), - ]) + const feeDataEstimate = await foxyApi.estimateApproveFees({ + tokenContractAddress: assetReference, + contractAddress, + userAddress: accountAddress, + }) + + const { + chainSpecific: { gasPrice, gasLimit }, + } = feeDataEstimate.fast + return bnOrZero(gasPrice).times(gasLimit).toFixed(0) } catch (error) { moduleLogger.error( @@ -95,17 +97,19 @@ export const Deposit: React.FC = ({ const getDepositGasEstimateCryptoBaseUnit = async (deposit: DepositValues) => { if (!accountAddress || !assetReference || !foxyApi) return try { - const [gasLimit, gasPrice] = await Promise.all([ - foxyApi.estimateDepositGas({ - tokenContractAddress: assetReference, - contractAddress, - amountDesired: bnOrZero(deposit.cryptoAmount) - .times(`1e+${asset.precision}`) - .decimalPlaces(0), - userAddress: accountAddress, - }), - foxyApi.getGasPrice(), - ]) + const feeDataEstimate = await foxyApi.estimateDepositFees({ + tokenContractAddress: assetReference, + contractAddress, + amountDesired: bnOrZero(deposit.cryptoAmount) + .times(`1e+${asset.precision}`) + .decimalPlaces(0), + userAddress: accountAddress, + }) + + const { + chainSpecific: { gasPrice, gasLimit }, + } = feeDataEstimate.fast + return bnOrZero(gasPrice).times(gasLimit).toFixed(0) } catch (error) { moduleLogger.error( diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimConfirm.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimConfirm.tsx index 8ed9f9f8a69..f2f720bd775 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimConfirm.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimConfirm.tsx @@ -10,6 +10,7 @@ import { } from '@chakra-ui/react' import type { AccountId, AssetId, ChainId } from '@shapeshiftoss/caip' import { ASSET_REFERENCE, toAssetId } from '@shapeshiftoss/caip' +import { supportsETH } from '@shapeshiftoss/hdwallet-core' import { KnownChainIds } from '@shapeshiftoss/types' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslate } from 'react-polyglot' @@ -93,6 +94,8 @@ export const ClaimConfirm = ({ if (!(walletState.wallet && contractAddress && userAddress && foxyApi && bip44Params)) return setLoading(true) try { + if (!supportsETH(walletState.wallet)) + throw new Error(`handleConfirm: wallet does not support ethereum`) const txid = await foxyApi.claimWithdraw({ claimAddress: userAddress, userAddress, @@ -140,24 +143,30 @@ export const ClaimConfirm = ({ try { const chainAdapter = await chainAdapterManager.get(KnownChainIds.EthereumMainnet) if (!(walletState.wallet && contractAddress && foxyApi && chainAdapter)) return + if (!supportsETH(walletState.wallet)) + throw new Error(`ClaimConfirm::useEffect: wallet does not support ethereum`) + const { accountNumber } = bip44Params const userAddress = await chainAdapter.getAddress({ wallet: walletState.wallet, accountNumber, }) setUserAddress(userAddress) - const [gasLimit, gasPrice, canClaimWithdraw] = await Promise.all([ - foxyApi.estimateClaimWithdrawGas({ + const [feeDataEstimate, canClaimWithdraw] = await Promise.all([ + foxyApi.estimateClaimWithdrawFees({ claimAddress: userAddress, userAddress, contractAddress, wallet: walletState.wallet, bip44Params, }), - foxyApi.getGasPrice(), foxyApi.canClaimWithdraw({ contractAddress, userAddress }), ]) + const { + chainSpecific: { gasPrice, gasLimit }, + } = feeDataEstimate.fast + setCanClaim(canClaimWithdraw) const gasEstimate = bnOrZero(gasPrice).times(gasLimit).toFixed(0) setEstimatedGas(gasEstimate) diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimStatus.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimStatus.tsx index 8865be1aa0b..9f6c3775d1b 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimStatus.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Overview/Claim/ClaimStatus.tsx @@ -1,13 +1,13 @@ import { Box, Button, Center, Link, ModalBody, ModalFooter, Stack } from '@chakra-ui/react' import type { AccountId, AssetId, ChainId } from '@shapeshiftoss/caip' import { ASSET_REFERENCE, toAssetId } from '@shapeshiftoss/caip' +import type { ethers } from 'ethers' import { DefiProvider, DefiType } from 'features/defi/contexts/DefiManagerProvider/DefiCommon' import isNil from 'lodash/isNil' import { useCallback, useEffect, useState } from 'react' import { FaCheck, FaTimes } from 'react-icons/fa' import { useTranslate } from 'react-polyglot' import { useLocation } from 'react-router' -import type { TransactionReceipt } from 'web3-core/types' import { Amount } from 'components/Amount/Amount' import { AssetIcon } from 'components/AssetIcon' import { CircularProgress } from 'components/CircularProgress/CircularProgress' @@ -44,7 +44,7 @@ enum TxStatus { type ClaimState = { txStatus: TxStatus - usedGasFee?: string + usedGasFeeCryptoBaseUnit?: string } const StatusInfo = { @@ -121,11 +121,10 @@ export const ClaimStatus: React.FC = ({ accountId }) => { try { const transactionReceipt = await poll({ fn: () => foxyApi.getTxReceipt({ txid }), - validate: (result: TransactionReceipt) => !isNil(result), + validate: (result: ethers.providers.TransactionReceipt) => !isNil(result), interval: 15000, maxAttempts: 30, }) - const gasPrice = await foxyApi.getGasPrice() if (transactionReceipt.status) { refetchFoxyBalances() @@ -134,14 +133,16 @@ export const ClaimStatus: React.FC = ({ accountId }) => { setState({ ...state, txStatus: transactionReceipt.status ? TxStatus.SUCCESS : TxStatus.FAILED, - usedGasFee: bnOrZero(gasPrice).times(transactionReceipt.gasUsed).toFixed(0), + usedGasFeeCryptoBaseUnit: transactionReceipt.effectiveGasPrice + .mul(transactionReceipt.gasUsed) + .toString(), }) } catch (error) { moduleLogger.error(error, 'ClaimStatus error') setState({ ...state, txStatus: TxStatus.FAILED, - usedGasFee: estimatedGas, + usedGasFeeCryptoBaseUnit: estimatedGas, }) } })() @@ -220,7 +221,9 @@ export const ClaimStatus: React.FC = ({ accountId }) => { = ({ accountId }) => { () - const { assetReference: contractAddress } = query + const { assetReference: foxyStakingContractAddress } = query const { feeAssetId, underlyingAsset, underlyingAssetId, stakingAsset } = useFoxyQuery() const marketData = useAppSelector(state => selectMarketDataById(state, underlyingAssetId)) @@ -68,12 +69,22 @@ export const FoxyWithdraw: React.FC<{ useEffect(() => { ;(async () => { try { - if (!(walletState.wallet && contractAddress && chainAdapter && foxyApi && bip44Params)) + if ( + !( + walletState.wallet && + foxyStakingContractAddress && + chainAdapter && + foxyApi && + bip44Params + ) + ) return - const foxyOpportunity = await foxyApi.getFoxyOpportunityByStakingAddress(contractAddress) + const foxyOpportunity = await foxyApi.getFoxyOpportunityByStakingAddress( + ethers.utils.getAddress(foxyStakingContractAddress), + ) // Get foxy fee for instant sends const foxyFeePercentage = await foxyApi.instantUnstakeFee({ - contractAddress, + contractAddress: foxyStakingContractAddress, }) dispatch({ @@ -89,7 +100,7 @@ export const FoxyWithdraw: React.FC<{ moduleLogger.error(error, 'FoxyWithdraw error:') } })() - }, [foxyApi, bip44Params, chainAdapter, contractAddress, walletState.wallet]) + }, [foxyApi, bip44Params, chainAdapter, foxyStakingContractAddress, walletState.wallet]) const StepConfig: DefiStepProps = useMemo(() => { return { @@ -105,7 +116,7 @@ export const FoxyWithdraw: React.FC<{ [DefiStep.Approve]: { label: translate('defi.steps.approve.title'), component: ownProps => , - props: { contractAddress }, + props: { contractAddress: foxyStakingContractAddress }, }, [DefiStep.Confirm]: { label: translate('defi.steps.confirm.title'), @@ -116,7 +127,7 @@ export const FoxyWithdraw: React.FC<{ component: ownProps => , }, } - }, [accountId, handleAccountIdChange, contractAddress, translate, stakingAsset.symbol]) + }, [accountId, handleAccountIdChange, foxyStakingContractAddress, translate, stakingAsset.symbol]) const handleBack = () => { history.push({ diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Approve.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Approve.tsx index 0037948171a..f3fb4510d18 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Approve.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Approve.tsx @@ -1,6 +1,7 @@ import { useToast } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' import { fromAccountId } from '@shapeshiftoss/caip' +import { supportsETH } from '@shapeshiftoss/hdwallet-core' import { Approve as ReusableApprove } from 'features/defi/components/Approve/Approve' import { ApprovePreFooter } from 'features/defi/components/Approve/ApprovePreFooter' import type { WithdrawValues } from 'features/defi/components/Withdraw/Withdraw' @@ -67,19 +68,21 @@ export const Approve: React.FC = ({ accountId, onNext }) => { if (!(rewardId && userAddress && state?.withdraw && foxyApi && dispatch && bip44Params)) return try { - const [gasLimit, gasPrice] = await Promise.all([ - foxyApi.estimateWithdrawGas({ - tokenContractAddress: rewardId, - contractAddress, - amountDesired: bnOrZero( - bn(withdraw.cryptoAmount).times(`1e+${asset.precision}`), - ).decimalPlaces(0), - userAddress, - type: state.withdraw.withdrawType, - bip44Params, - }), - foxyApi.getGasPrice(), - ]) + const feeDataEstimate = await foxyApi.estimateWithdrawFees({ + tokenContractAddress: rewardId, + contractAddress, + amountDesired: bnOrZero( + bn(withdraw.cryptoAmount).times(`1e+${asset.precision}`), + ).decimalPlaces(0), + userAddress, + type: state.withdraw.withdrawType, + bip44Params, + }) + + const { + chainSpecific: { gasPrice, gasLimit }, + } = feeDataEstimate.fast + const returVal = bnOrZero(bn(gasPrice).times(gasLimit)).toFixed(0) return returVal } catch (error) { @@ -123,7 +126,10 @@ export const Approve: React.FC = ({ accountId, onNext }) => { ) ) return + try { + if (!supportsETH(walletState.wallet)) + throw new Error(`handleApprove: wallet does not support ethereum`) dispatch({ type: FoxyWithdrawActionType.SET_LOADING, payload: true }) await foxyApi.approve({ tokenContractAddress: rewardId, diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Confirm.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Confirm.tsx index 8ff80bf9699..bdd7b92587f 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Confirm.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/components/Confirm.tsx @@ -1,7 +1,9 @@ import { Alert, AlertIcon, Box, Stack } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' import { fromAccountId } from '@shapeshiftoss/caip' +import { supportsETH } from '@shapeshiftoss/hdwallet-core' import { WithdrawType } from '@shapeshiftoss/types' +import type { ethers } from 'ethers' import { Confirm as ReusableConfirm } from 'features/defi/components/Confirm/Confirm' import { Summary } from 'features/defi/components/Summary' import { DefiStep } from 'features/defi/contexts/DefiManagerProvider/DefiCommon' @@ -9,7 +11,6 @@ import { useFoxyQuery } from 'features/defi/providers/foxy/components/FoxyManage import isNil from 'lodash/isNil' import { useCallback, useContext, useMemo } from 'react' import { useTranslate } from 'react-polyglot' -import type { TransactionReceipt } from 'web3-core/types' import { Amount } from 'components/Amount/Amount' import { AssetIcon } from 'components/AssetIcon' import type { StepComponentProps } from 'components/DeFi/components/Steps' @@ -85,26 +86,27 @@ export const Confirm: React.FC foxyApi.getTxReceipt({ txid }), - validate: (result: TransactionReceipt) => !isNil(result), + validate: (result: ethers.providers.TransactionReceipt) => !isNil(result), interval: 15000, maxAttempts: 30, }) @@ -112,9 +114,9 @@ export const Confirm: React.FC new ethers.providers.JsonRpcProvider(getConfig().REACT_APP_ETHEREUM_NODE_URL), + () => new ethers.providers.JsonRpcBatchProvider(getConfig().REACT_APP_ETHEREUM_NODE_URL), [], ) diff --git a/src/state/apis/foxy/foxyApiSingleton.ts b/src/state/apis/foxy/foxyApiSingleton.ts index d590b36db6e..520f02992d5 100644 --- a/src/state/apis/foxy/foxyApiSingleton.ts +++ b/src/state/apis/foxy/foxyApiSingleton.ts @@ -1,4 +1,4 @@ -import type { ChainAdapter } from '@shapeshiftoss/chain-adapters' +import type { EvmBaseAdapter } from '@shapeshiftoss/chain-adapters' import { foxyAddresses, FoxyApi } from '@shapeshiftoss/investor-foxy' import { KnownChainIds } from '@shapeshiftoss/types' import { getConfig } from 'config' @@ -22,7 +22,7 @@ export const getFoxyApi = (): FoxyApi => { const foxyApi = new FoxyApi({ adapter: getChainAdapterManager().get( KnownChainIds.EthereumMainnet, - ) as ChainAdapter, + ) as unknown as EvmBaseAdapter, providerUrl: getConfig()[RPC_PROVIDER_ENV], foxyAddresses, }) diff --git a/yarn.lock b/yarn.lock index 1f985276cbc..81bea864737 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4180,7 +4180,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:5.7.2, @ethersproject/providers@npm:^5.5.3": +"@ethersproject/providers@npm:5.7.2": version: 5.7.2 resolution: "@ethersproject/providers@npm:5.7.2" dependencies: @@ -5830,7 +5830,6 @@ __metadata: version: 0.0.0-use.local resolution: "@shapeshiftoss/asset-service@workspace:packages/asset-service" dependencies: - "@ethersproject/providers": ^5.5.3 "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/investor-idle": "workspace:^" "@shapeshiftoss/types": "workspace:^" @@ -6140,15 +6139,12 @@ __metadata: version: 0.0.0-use.local resolution: "@shapeshiftoss/investor-foxy@workspace:packages/investor-foxy" dependencies: - "@ethersproject/providers": ^5.5.3 "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/chain-adapters": "workspace:^" "@shapeshiftoss/logger": "workspace:^" "@shapeshiftoss/types": "workspace:^" "@types/readline-sync": ^1.4.4 readline-sync: ^1.4.10 - web3-core: 1.7.4 - web3-utils: 1.7.4 languageName: unknown linkType: soft @@ -6156,7 +6152,6 @@ __metadata: version: 0.0.0-use.local resolution: "@shapeshiftoss/investor-idle@workspace:packages/investor-idle" dependencies: - "@ethersproject/providers": ^5.5.3 "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/chain-adapters": "workspace:^" "@shapeshiftoss/investor": "workspace:^" @@ -6171,7 +6166,6 @@ __metadata: version: 0.0.0-use.local resolution: "@shapeshiftoss/investor-yearn@workspace:packages/investor-yearn" dependencies: - "@ethersproject/providers": ^5.5.3 "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/chain-adapters": "workspace:^" "@shapeshiftoss/investor": "workspace:^" @@ -6201,7 +6195,6 @@ __metadata: version: 0.0.0-use.local resolution: "@shapeshiftoss/market-service@workspace:packages/market-service" dependencies: - "@ethersproject/providers": ^5.5.3 "@shapeshiftoss/caip": "workspace:^" "@shapeshiftoss/chain-adapters": "workspace:^" "@shapeshiftoss/investor-foxy": "workspace:^" @@ -6384,7 +6377,7 @@ __metadata: eslint-plugin-prettier: ^4.0.0 eslint-plugin-simple-import-sort: ^7.0.0 eth-url-parser: ^1.0.4 - ethers: ^5.5.3 + ethers: ^5.7.2 fast-json-stable-stringify: ^2.1.0 framer-motion: ^6.3.11 friendly-challenge: 0.9.2 @@ -15430,7 +15423,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:^5.4.7, ethers@npm:^5.5.3, ethers@npm:^5.6.5, ethers@npm:^5.6.9, ethers@npm:^5.7.0, ethers@npm:^5.7.2": +"ethers@npm:^5.4.7, ethers@npm:^5.6.5, ethers@npm:^5.6.9, ethers@npm:^5.7.0, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: