From 8df2c4e4f754e7f084b3598af62dc51bae150de0 Mon Sep 17 00:00:00 2001 From: EDolganov Date: Wed, 21 Aug 2024 18:30:02 +0300 Subject: [PATCH] Libs update + prettier formating --- .eslintrc.js | 6 +++ package-lock.json | 33 +++++++++++--- package.json | 2 +- src/Web3Common.test.ts | 70 ++++++++++++++++-------------- src/Web3Common.ts | 97 ++++++++++++++++++++++++++---------------- tsconfig.json | 3 +- 6 files changed, 132 insertions(+), 79 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fa321c6..67d5a0f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,12 @@ module.exports = { 'prettier', ], rules: { + '@typescript-eslint/return-await': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/ban-tslint-comment': 'off', // caused by no way to remove this from generated code 'object-property-newline': [ diff --git a/package-lock.json b/package-lock.json index 1295acf..5a02d07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "ethers": "^6.13.2", - "smartypay-client-model": "^2.30.0" + "smartypay-client-model": "^2.34.0" }, "devDependencies": { "@types/jest": "^29.5.12", @@ -2497,6 +2497,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6622,9 +6630,12 @@ } }, "node_modules/smartypay-client-model": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/smartypay-client-model/-/smartypay-client-model-2.30.0.tgz", - "integrity": "sha512-eg2JF4QwLnIyScxVg2ktlHTEVJfXjAAJfR778HcKAu+dalBMjLBvmb649PFWdnwimqrTjhHuBYkwy+evCyg4FA==" + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/smartypay-client-model/-/smartypay-client-model-2.34.0.tgz", + "integrity": "sha512-suAZlnyz8PRIblknzSlvCjTdcFLocseFEnO0VFTB1tcVdg+mzbEy2Nja7RMw/TGzn8XHAiBQqS8SNupGp4g39Q==", + "dependencies": { + "bignumber.js": "^9.1.2" + } }, "node_modules/source-map": { "version": "0.6.1", @@ -9307,6 +9318,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -12279,9 +12295,12 @@ "dev": true }, "smartypay-client-model": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/smartypay-client-model/-/smartypay-client-model-2.30.0.tgz", - "integrity": "sha512-eg2JF4QwLnIyScxVg2ktlHTEVJfXjAAJfR778HcKAu+dalBMjLBvmb649PFWdnwimqrTjhHuBYkwy+evCyg4FA==" + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/smartypay-client-model/-/smartypay-client-model-2.34.0.tgz", + "integrity": "sha512-suAZlnyz8PRIblknzSlvCjTdcFLocseFEnO0VFTB1tcVdg+mzbEy2Nja7RMw/TGzn8XHAiBQqS8SNupGp4g39Q==", + "requires": { + "bignumber.js": "^9.1.2" + } }, "source-map": { "version": "0.6.1", diff --git a/package.json b/package.json index 235a800..d3b91d4 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "ethers": "^6.13.2", - "smartypay-client-model": "^2.30.0" + "smartypay-client-model": "^2.34.0" }, "devDependencies": { "@types/jest": "^29.5.12", diff --git a/src/Web3Common.test.ts b/src/Web3Common.test.ts index 4fe22bf..39d5ee4 100644 --- a/src/Web3Common.test.ts +++ b/src/Web3Common.test.ts @@ -4,14 +4,14 @@ */ import { Web3Common } from './Web3Common'; -import { Assets } from 'smartypay-client-model'; -import { BigNumber, ethers } from 'ethers'; +import { Assets, util } from 'smartypay-client-model'; +import { ethers } from 'ethers'; describe('Web3Common', () => { const address = '0x14186C8215985f33845722730c6382443Bf9EC65'; const randomAddress = '0x790ACC251534cb02975A0c61dA94D200bB5833A5'; // from https://vanity-eth.tk/ - const zero = BigNumber.from(0); + const zero = util.bigMath.toBigNumber(0); describe('toNumberFromHex', () => { test('should be correct number', () => { @@ -23,8 +23,12 @@ describe('Web3Common', () => { describe('toHexString', () => { test('should be correct hex', () => { - expect(Web3Common.toHexString(1)).toBe('0x01'); - expect(Web3Common.toHexString(15)).toBe('0x0f'); + expect(Web3Common.toHexString(0)).toBe('0x0'); + expect(Web3Common.toHexString(1)).toBe('0x1'); + expect(Web3Common.toHexString(15)).toBe('0xf'); + expect(Web3Common.toHexString(16)).toBe('0x10'); + expect(Web3Common.toHexString(255)).toBe('0xff'); + expect(Web3Common.toHexString(256)).toBe('0x100'); }); }); @@ -37,70 +41,70 @@ describe('Web3Common', () => { describe('getTokenBalance', () => { test('should support btBUSD', async () => { const doubleForm = await Web3Common.getTokenBalance(Assets.btBUSD, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.btBUSD.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.btBUSD.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); test('should support btMNXe', async () => { const doubleForm = await Web3Common.getTokenBalance(Assets.btMNXe, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.btMNXe.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.btMNXe.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); test('should support btUSDTv2', async () => { const doubleForm = await Web3Common.getTokenBalance(Assets.btUSDTv2, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.btUSDTv2.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.btUSDTv2.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); test('should support asUSDC', async () => { const doubleForm = await Web3Common.getTokenBalance(Assets.asUSDC, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.asUSDC.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.asUSDC.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); test('should support asUSDT', async () => { const doubleForm = await Web3Common.getTokenBalance(Assets.asUSDT, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.asUSDT.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.asUSDT.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); - test('should support pmUSDC', async () => { - const doubleForm = await Web3Common.getTokenBalance(Assets.pmUSDC, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.pmUSDC.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + test('should support paUSDC', async () => { + const doubleForm = await Web3Common.getTokenBalance(Assets.paUSDC, address); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.paUSDC.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); - test('should support pmUSDT', async () => { - const doubleForm = await Web3Common.getTokenBalance(Assets.pmUSDT, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.pmUSDT.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + test('should support paUSDT', async () => { + const doubleForm = await Web3Common.getTokenBalance(Assets.paUSDT, address); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.paUSDT.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); test('should support sUSDC', async () => { const doubleForm = await Web3Common.getTokenBalance(Assets.sUSDC, address); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.sUSDC.decimals); - expect(absoluteForm.gt(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.sUSDC.decimals); + expect(util.bigMath.isGreaterThan(absoluteForm, zero)).toBe(true); }); }); describe('getTokenAllowance', () => { test('should support btBUSD', async () => { const doubleForm = await Web3Common.getTokenAllowance(Assets.btBUSD, address, randomAddress); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.btBUSD.decimals); - expect(absoluteForm.eq(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.btBUSD.decimals); + expect(util.bigMath.isEqualTo(absoluteForm, zero)).toBe(true); }); test('should support atUSDC', async () => { const doubleForm = await Web3Common.getTokenAllowance(Assets.asUSDC, address, randomAddress); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.asUSDC.decimals); - expect(absoluteForm.eq(zero)).toBe(true); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.asUSDC.decimals); + expect(util.bigMath.isEqualTo(absoluteForm, zero)).toBe(true); }); - test('should support pmUSDC', async () => { - const doubleForm = await Web3Common.getTokenAllowance(Assets.pmUSDC, address, randomAddress); - const absoluteForm = ethers.utils.parseUnits(doubleForm, Assets.pmUSDC.decimals); - expect(absoluteForm.eq(zero)).toBe(true); + test('should support paUSDC', async () => { + const doubleForm = await Web3Common.getTokenAllowance(Assets.paUSDC, address, randomAddress); + const absoluteForm = ethers.parseUnits(doubleForm, Assets.paUSDC.decimals); + expect(util.bigMath.isEqualTo(absoluteForm, zero)).toBe(true); }); }); }); diff --git a/src/Web3Common.ts b/src/Web3Common.ts index 96531c3..bbfe895 100644 --- a/src/Web3Common.ts +++ b/src/Web3Common.ts @@ -3,18 +3,32 @@ @author Evgeny Dolganov */ -import { BigNumber, ethers } from 'ethers'; -import { abi, Assets, Blockchains } from 'smartypay-client-model'; +import { BrowserProvider, ethers } from 'ethers'; +import { abi, Assets, Blockchains, util } from 'smartypay-client-model'; import { UseLogs } from './util'; import { JsonProvidersManager } from './util/JsonProvidersManager'; import type { TxReqProp } from './types'; import type { Web3Api } from './web3-api'; +import type { BigNumberish } from 'ethers'; import type { Currency, Network, Token } from 'smartypay-client-model'; const DefaultTxConfirms = 1; +let lazyCache: util.SimpleTtlCache; + +function getLazyCache() { + if (!lazyCache) { + lazyCache = new util.SimpleTtlCache(); + } + return lazyCache; +} + +interface GetTokenBalanceOpt { + cacheTtl?: number; +} + /** * Common API for all blockchains and wallets */ @@ -23,16 +37,35 @@ export const Web3Common = { jsonProvidersManager: JsonProvidersManager, - async getTokenBalance(token: Token, ownerAddress: string): Promise { + async getTokenBalance(token: Token, ownerAddress: string, opt?: GetTokenBalanceOpt): Promise { const { network, tokenId, decimals } = token; - const { rpc } = Blockchains[network]; + const cacheKey = `Web3Common_getTokenBalance_${ownerAddress}_${tokenId})`; - // readonly methods can be called without wallet - const provider = JsonProvidersManager.getProvider(rpc); + // multi-same calls will wait for a single result + return await util.withSingleCall(cacheKey, async () => { + const cache = getLazyCache(); + const cached = cache.get(cacheKey); - const contract = new ethers.Contract(tokenId, abi.Erc20ABI, provider); - const balance = await contract.balanceOf(ownerAddress); - return ethers.utils.formatUnits(balance, decimals); + // no real call + if (cached) { + return cached; + } + + // readonly methods can be called without wallet + const { rpc } = Blockchains[network]; + const provider = JsonProvidersManager.getProvider(rpc); + + const contract = new ethers.Contract(tokenId, abi.Erc20ABI, provider); + const balance: BigNumberish = await contract.balanceOf(ownerAddress); + const out = ethers.formatUnits(balance, decimals); + + // save to cache if needed + if (opt?.cacheTtl && opt.cacheTtl > 0) { + cache.set(cacheKey, out, opt.cacheTtl); + } + + return out; + }); }, async getTokenAllowance(token: Token, ownerAddress: string, spenderAddress: string): Promise { @@ -43,20 +76,20 @@ export const Web3Common = { const provider = JsonProvidersManager.getProvider(rpc); const contract = new ethers.Contract(tokenId, abi.Erc20ABI, provider); - const allowance = await contract.allowance(ownerAddress, spenderAddress); - return ethers.utils.formatUnits(allowance, decimals); + const allowance: BigNumberish = await contract.allowance(ownerAddress, spenderAddress); + return ethers.formatUnits(allowance, decimals); }, - async getContractForWallet(web3Api: Web3Api, contractAddress: string, abi: any) { - const walletAddress = await web3Api.getAddress(); - const provider = new ethers.providers.Web3Provider(web3Api.getRawProvider() as any); - return new ethers.Contract(contractAddress, abi, provider.getSigner(walletAddress)); + async getContractForWallet(web3Api: Web3Api, contractAddress: string, abiDef: any) { + const provider = new BrowserProvider(web3Api.getRawProvider() as any); + const signer = await provider.getSigner(); + return new ethers.Contract(contractAddress, abiDef, signer); }, async walletTokenApprove( web3Api: Web3Api, token: Token, - _ownerAddress: string, // deprecated: using "web3Api.getAddress()" + ownerAddress: string, spenderAddress: string, approveAbsoluteAmount: string, prop?: TxReqProp, @@ -65,16 +98,14 @@ export const Web3Common = { const { tokenId } = token; - const walletAddress = await web3Api.getAddress(); - const provider = new ethers.providers.Web3Provider(web3Api.getRawProvider() as any); - const contract = new ethers.Contract(tokenId, abi.Erc20ABI, provider.getSigner(walletAddress)); + const provider = new BrowserProvider(web3Api.getRawProvider() as any); + const signer = await provider.getSigner(ownerAddress); + const contract = new ethers.Contract(tokenId, abi.Erc20ABI, signer); // call tx - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const txResp = await contract.approve(spenderAddress, approveAbsoluteAmount); - const { transactionHash } = await txResp.wait(prop?.waitConfirms || DefaultTxConfirms); - - return transactionHash; + const { hash } = await txResp.wait(prop?.waitConfirms ?? DefaultTxConfirms); + return hash; }, async switchWalletToAssetNetwork(web3Api: Web3Api, token: Token) { @@ -97,7 +128,6 @@ export const Web3Common = { // try to switch to existing network in wallet: try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const result = await provider.request({ method: 'wallet_switchEthereumChain', params: [ @@ -113,7 +143,6 @@ export const Web3Common = { } return; } catch (e: any) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment const msg: string = e.message || e.toString() || ''; // no need to continue @@ -123,7 +152,6 @@ export const Web3Common = { } // next: try to add network to wallet: - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const result = await provider.request({ method: 'wallet_addEthereumChain', params: [ @@ -147,7 +175,6 @@ export const Web3Common = { }, async addCurrencyTokenToWallet(web3Api: Web3Api, currency: Currency) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const token = (Assets as any)[currency] as Token | undefined; if (token) { await this.addTokenToWallet(web3Api, token); @@ -165,7 +192,6 @@ export const Web3Common = { try { await provider.request({ method: 'wallet_watchAsset', - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment params: { type: 'ERC20', options: { @@ -181,7 +207,6 @@ export const Web3Common = { } } catch (e: any) { if (UseLogs.useLogs()) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call console.log('cannot add token', e.message || e.toString()); } } @@ -196,26 +221,26 @@ export const Web3Common = { * */ getNormalAddress(address: string): string { - return ethers.utils.getAddress(address); + return ethers.getAddress(address); }, - toAbsoluteForm(amount: string, token: Token) { - return ethers.utils.parseUnits(amount, token.decimals); + toAbsoluteForm(amount: string, token: Token): string { + return ethers.parseUnits(amount, token.decimals).toString(); }, toBigNumber(value: any) { - return BigNumber.from(value); + return util.bigMath.toBigNumber(value); }, toDecimalForm(amount: any, token: Token): string { - return ethers.utils.formatUnits(amount, token.decimals); + return ethers.formatUnits(amount, token.decimals); }, toHexString(value: any): string { - return BigNumber.from(value).toHexString(); + return util.hex.toHexString(value); }, toNumberFromHex(value: string): number { - return BigNumber.from(value).toNumber(); + return util.hex.toNumberFromHex(value); }, }; diff --git a/tsconfig.json b/tsconfig.json index 9fde089..3bd413b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -32,7 +32,6 @@ "src/**/*.ts" ], "exclude": [ - "node_modules", - "**/*.test.ts" + "node_modules" ] }