diff --git a/CHANGELOG.md b/CHANGELOG.md index dc7cde2..1ce9acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 3.1.38 +* Support for ERC-20 token management +* Support additional RPC methods to enable Uniswap transactions * Support for service worker background signing +* Remove clutter from drop down menu * Fix bug validating token decimals before transfer * Modify activity panel color palette diff --git a/package.json b/package.json index d231f3e..2842d7a 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,8 @@ "@metamask/providers": "^17.1.2", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "@unstoppabledomains/config": "0.0.13", - "@unstoppabledomains/ui-components": "0.0.51-browser-extension.6", + "@unstoppabledomains/config": "0.0.17", + "@unstoppabledomains/ui-components": "0.0.51-browser-extension.10", "@unstoppabledomains/ui-kit": "0.3.24", "abitype": "^1.0.6", "async-mutex": "^0.5.0", diff --git a/src/lib/wallet/evm/provider.ts b/src/lib/wallet/evm/provider.ts new file mode 100644 index 0000000..d7b76df --- /dev/null +++ b/src/lib/wallet/evm/provider.ts @@ -0,0 +1,19 @@ +import {Web3} from "web3"; + +import config from "@unstoppabledomains/config"; +import {getProviderUrl} from "@unstoppabledomains/ui-components/lib/wallet/evm/provider"; + +export const getWeb3Provider = (chainId: number) => { + const chainSymbol = Object.keys(config.BLOCKCHAINS).find(k => { + return ( + config.BLOCKCHAINS[k as keyof typeof config.BLOCKCHAINS].CHAIN_ID === + chainId + ); + }); + if (!chainSymbol) { + throw new Error(`Configuration not found for chainId: ${chainId}`); + } + + const providerUrl = getProviderUrl(chainSymbol); + return new Web3(providerUrl); +}; diff --git a/src/lib/wallet/evm/token.test.ts b/src/lib/wallet/evm/token.test.ts deleted file mode 100644 index 0a4ace7..0000000 --- a/src/lib/wallet/evm/token.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {createErc20TransferTx} from "./token"; - -describe("token transactions", () => { - it("should create an erc20 transfer tx", () => { - const tx = createErc20TransferTx( - 80002, - "0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582", - "0xCD0DAdAb45bAF9a06ce1279D1342EcC3F44845af", - "0x8ee1E1d88EBE2B44eAD162777DE787Ef6A2dC2F2", - 1, - ); - expect(tx).toMatchObject({ - chainId: 80002, - to: "0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582", - // expected data generated directly from Polygon scan using MetaMask: - // https://amoy.polygonscan.com/token/0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582?a=0x8ee1e1d88ebe2b44ead162777de787ef6a2dc2f2#writeProxyContract - data: "0xa9059cbb0000000000000000000000008ee1e1d88ebe2b44ead162777de787ef6a2dc2f20000000000000000000000000000000000000000000000000000000000000001", - value: "0", - }); - }); -}); diff --git a/src/lib/wallet/evm/token.ts b/src/lib/wallet/evm/token.ts deleted file mode 100644 index eeb8de0..0000000 --- a/src/lib/wallet/evm/token.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {erc20Abi} from "abitype/abis"; -import {Web3} from "web3"; - -import {CreateTransaction} from "@unstoppabledomains/ui-components"; - -const web3 = new Web3(); - -export const createErc20TransferTx = ( - chainId: number, - tokenAddress: string, - fromAddress: string, - toAddress: string, - amount: number, -): CreateTransaction => { - // ERC-20 contract instance for sending a specific token - const erc20Contract = new web3.eth.Contract(erc20Abi, tokenAddress, { - from: fromAddress, - }); - - // create the transaction that should be signed to execute ERC-20 transfer - return { - chainId, - to: tokenAddress, - data: erc20Contract.methods.transfer(toAddress, amount).encodeABI(), - value: "0", - }; -}; diff --git a/src/lib/wallet/isAscii.test.ts b/src/lib/wallet/isAscii.test.ts new file mode 100644 index 0000000..0965e5f --- /dev/null +++ b/src/lib/wallet/isAscii.test.ts @@ -0,0 +1,7 @@ +import {isAscii} from "./isAscii"; + +describe("isAscii", () => { + it("returns true for ASCII text", () => { + expect(isAscii("abc123")).toBeTruthy(); + }); +}); diff --git a/src/pages/Wallet/Connect.tsx b/src/pages/Wallet/Connect.tsx index efddac6..380b7c8 100644 --- a/src/pages/Wallet/Connect.tsx +++ b/src/pages/Wallet/Connect.tsx @@ -76,7 +76,7 @@ const Connect: React.FC = () => { useEffect(() => { // wait for required fields to be loaded - if (!isMounted() || !preferences || !connections) { + if (!isMounted() || !preferences || !connections || isLoaded) { return; } diff --git a/src/scripts/liteWalletProvider/main.ts b/src/scripts/liteWalletProvider/main.ts index c565c64..de0479f 100644 --- a/src/scripts/liteWalletProvider/main.ts +++ b/src/scripts/liteWalletProvider/main.ts @@ -16,6 +16,7 @@ import {Logger} from "../../lib/logger"; import {isEthAddress} from "../../lib/sherlock/matcher"; import {ResolutionData} from "../../lib/sherlock/types"; import {announceProvider} from "../../lib/wallet/evm/eip6963"; +import {getWeb3Provider} from "../../lib/wallet/evm/provider"; import {EIP_712_KEY, TypedMessage} from "../../types/wallet/eip712"; import {WalletPreferences} from "../../types/wallet/preferences"; import { @@ -172,14 +173,25 @@ class LiteWalletProvider extends EventEmitter { result = await this.handleTypedSign(clone(request.params)); break; // Transaction methods - case "eth_sendTransaction": - result = await this.handleSendTransaction(clone(request.params)); - break; case "eth_blockNumber": + result = await this.handleGetBlockNumber(); + break; + case "eth_call": + result = await this.handleReadOnlyCall(clone(request.params)); + break; + case "eth_estimateGas": + result = await this.handleEstimateGas(clone(request.params)); + break; case "eth_getTransactionByHash": - // not implemented, but stubbed out with "not found" to prevent runtime - // errors on apps that call this method - result = null; + result = await this.handleGetTransactionByHash(clone(request.params)); + break; + case "eth_getTransactionReceipt": + result = await this.handleGetTransactionReceipt( + clone(request.params), + ); + break; + case "eth_sendTransaction": + result = await this.handleSendTransaction(clone(request.params)); break; default: throw new EthereumProviderError( @@ -189,10 +201,15 @@ class LiteWalletProvider extends EventEmitter { } // result is successful - Logger.log( - "Request successful", - JSON.stringify({method: request.method, result}), - ); + try { + Logger.log( + "Request successful", + JSON.stringify({method: request.method, result}), + ); + } catch (e2: any) { + Logger.log("Request successful", request.method); + } + return result; } catch (e: any) { // result is failure @@ -499,6 +516,77 @@ class LiteWalletProvider extends EventEmitter { return [address]; } + private async handleGetBlockNumber() { + // retrieve the web3 provider for connected chain + const chainIdHex = await this.handleGetConnectedChainIds(); + const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number); + + // query the block number + const block = await web3.eth.getBlockNumber(); + return web3utils.numberToHex(block); + } + + private async handleEstimateGas(params: any[]) { + // retrieve the web3 provider for connected chain + const chainIdHex = await this.handleGetConnectedChainIds(); + const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number); + + // validate any Tx parameters have been passed + if (!params || params.length === 0) { + throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError); + } + + // query the estimated gas + return web3utils.numberToHex(await web3.eth.estimateGas(params[0])); + } + + private async handleGetTransactionByHash(params: any[]) { + // retrieve the web3 provider for connected chain + const chainIdHex = await this.handleGetConnectedChainIds(); + const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number); + + // validate any Tx parameters have been passed + if (!params || params.length === 0) { + throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError); + } + + // query the requested transaction + return await web3.eth.getTransaction(params[0]); + } + + private async handleGetTransactionReceipt(params: any[]) { + // retrieve the web3 provider for connected chain + const chainIdHex = await this.handleGetConnectedChainIds(); + const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number); + + // validate any Tx parameters have been passed + if (!params || params.length === 0) { + throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError); + } + + // query the requested transaction + return await web3.eth.getTransactionReceipt(params[0]); + } + + private async handleReadOnlyCall(params: any[]) { + // retrieve the web3 provider for connected chain + const chainIdHex = await this.handleGetConnectedChainIds(); + const web3 = getWeb3Provider(web3utils.hexToNumber(chainIdHex) as number); + + // validate any Tx parameters have been passed + if (!params || params.length === 0) { + throw new EthereumProviderError(PROVIDER_CODE_USER_ERROR, InvalidTxError); + } + + // query the requested transaction + return await web3.eth.call( + // call parameters + params[0], + // optional block number + params.length > 1 ? params[1] : undefined, + ); + } + private async handleGetConnectedChainIds() { if (this.networkVersion) { // return hex formatted network version diff --git a/src/types/wallet/provider.ts b/src/types/wallet/provider.ts index 123390a..5aad0f6 100644 --- a/src/types/wallet/provider.ts +++ b/src/types/wallet/provider.ts @@ -8,6 +8,9 @@ export type ProviderMethod = | "eth_sendTransaction" | "eth_signTypedData_v4" | "eth_blockNumber" + | "eth_getTransactionReceipt" + | "eth_call" + | "eth_estimateGas" | "eth_getTransactionByHash" | "personal_sign" | "wallet_requestPermissions" diff --git a/yarn.lock b/yarn.lock index 1267e7c..aa979d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6271,9 +6271,9 @@ __metadata: languageName: node linkType: hard -"@unstoppabledomains/config@npm:0.0.13, @unstoppabledomains/config@npm:latest": - version: 0.0.13 - resolution: "@unstoppabledomains/config@npm:0.0.13" +"@unstoppabledomains/config@npm:0.0.17, @unstoppabledomains/config@npm:latest": + version: 0.0.17 + resolution: "@unstoppabledomains/config@npm:0.0.17" dependencies: "@bugsnag/browser-performance": ^2.2.0 "@bugsnag/js": ^7.16.2 @@ -6282,7 +6282,7 @@ __metadata: "@types/lodash.merge": ^4.6.7 lodash: ^4.17.21 lodash.merge: ^4.6.2 - checksum: edf0b6a9fe30b61a4f7e731c7a205ac814b6757b74c6bbf164c47b92be308141c1ab397775bc5c6bda900f176f46c09fe04afc30a6429e41d14ccb684d1d35ca + checksum: 0b20b5e6b3ccad71b6ee0ed83efc696e9a4444c945ca63b0ba3f2636758a8de9c009652d859bbc488325c3f19bc74a6b10641ff3775b2d15b52e0a0e233c7b78 languageName: node linkType: hard @@ -6299,9 +6299,9 @@ __metadata: languageName: node linkType: hard -"@unstoppabledomains/ui-components@npm:0.0.51-browser-extension.6": - version: 0.0.51-browser-extension.6 - resolution: "@unstoppabledomains/ui-components@npm:0.0.51-browser-extension.6" +"@unstoppabledomains/ui-components@npm:0.0.51-browser-extension.10": + version: 0.0.51-browser-extension.10 + resolution: "@unstoppabledomains/ui-components@npm:0.0.51-browser-extension.10" dependencies: "@braintree/sanitize-url": ^6.0.4 "@emotion/server": ^11.4.0 @@ -6342,6 +6342,7 @@ __metadata: "@xmtp/content-type-text": ^1.0.0 "@xmtp/proto": ^3.62.1 "@xmtp/xmtp-js": 12.1.0 + abitype: ^1.0.6 bip322-js: ^1.1.0 bitcoin-address-validation: ^2.2.3 bluebird: ^3.7.2 @@ -6400,7 +6401,7 @@ __metadata: "@unstoppabledomains/ui-kit": ^0.3.24 notistack: ^2.0.5 react-query: ^3.39.3 - checksum: 9e57ff6e08a952c9180a45443df228999979b4ec17da7ced8d08918bd07ec084e36e4e00fe9e21715c68dbead776a75c9e74072a69996691c3687fc80016d749 + checksum: 942daaa58b454b844d217096378b4c6ad8f9dc5abca48c3acda479cd98f0125e576c165eabc422fbe4fc0a840d85ad58c3641a7d77d2e8c7fc8910f3e3b88368 languageName: node linkType: hard @@ -22263,8 +22264,8 @@ __metadata: "@types/react-dom": ^17.0.0 "@typescript-eslint/eslint-plugin": ^5.39.0 "@typescript-eslint/parser": ^5.39.0 - "@unstoppabledomains/config": 0.0.13 - "@unstoppabledomains/ui-components": 0.0.51-browser-extension.6 + "@unstoppabledomains/config": 0.0.17 + "@unstoppabledomains/ui-components": 0.0.51-browser-extension.10 "@unstoppabledomains/ui-kit": 0.3.24 abitype: ^1.0.6 assert: ^2.1.0