From f12e606102d5c4a8d8c2fbe61468006aef06e2fa Mon Sep 17 00:00:00 2001 From: Eric Le Ponner Date: Wed, 23 Oct 2024 09:46:43 +0200 Subject: [PATCH] Now detects and accesses wallet using EIP6363 and EIP1193 specs and removed @metamask/provider. Signed-off-by: Eric Le Ponner --- package-lock.json | 235 +------- package.json | 2 - .../values/abi/ContractCallBuilder.ts | 17 +- src/utils/cache/AccountByIdCache.ts | 12 + src/utils/cache/ContractByIdCache.ts | 12 + src/utils/wallet/EIP6963Agent.ts | 53 ++ src/utils/wallet/WalletDriver.ts | 4 +- src/utils/wallet/WalletDriver_Blade.ts | 4 - src/utils/wallet/WalletDriver_Brave.ts | 51 +- src/utils/wallet/WalletDriver_Coinbase.ts | 41 +- src/utils/wallet/WalletDriver_Ethereum.ts | 514 ++++++++++-------- src/utils/wallet/WalletDriver_Hashpack.ts | 4 - src/utils/wallet/WalletDriver_Hedera.ts | 2 +- src/utils/wallet/WalletDriver_Metamask.ts | 33 +- src/utils/wallet/WalletManager.ts | 5 +- src/utils/wallet/eip1193.ts | 181 ++++++ src/utils/wallet/eip6963.ts | 45 ++ tests/unit/staking/WalletDriver_Mock.ts | 6 - 18 files changed, 613 insertions(+), 608 deletions(-) create mode 100644 src/utils/wallet/EIP6963Agent.ts create mode 100644 src/utils/wallet/eip1193.ts create mode 100644 src/utils/wallet/eip6963.ts diff --git a/package-lock.json b/package-lock.json index c4d1f6ead..7441aa1ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@hashgraph/sdk": "^2.52.0", "@hedera-name-service/hns-resolution-sdk": "^2.0.11", "@kabuto-sh/ns": "^0.14.2", - "@metamask/providers": "^18.1.0", "@oruga-ui/oruga-next": "^0.7.0", "@vuepic/vue-datepicker": "^9.0.3", "axios": "^1.7.7", @@ -26,7 +25,6 @@ "base32-encode": "^2.0.0", "bulma": "^0.9.4", "core-js": "^3.38.1", - "crypto-js": "4.1.1", "ethers": "^6.13.4", "file-saver": "^2.0.5", "hashconnect": "^0.1.10", @@ -2512,15 +2510,6 @@ "fsevents": "2.3.3" } }, - "node_modules/@ethereumjs/common": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-3.2.0.tgz", - "integrity": "sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==", - "dependencies": { - "@ethereumjs/util": "^8.1.0", - "crc-32": "^1.2.0" - } - }, "node_modules/@ethereumjs/rlp": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", @@ -2533,33 +2522,6 @@ "node": ">=14" } }, - "node_modules/@ethereumjs/tx": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-4.2.0.tgz", - "integrity": "sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==", - "dependencies": { - "@ethereumjs/common": "^3.2.0", - "@ethereumjs/rlp": "^4.0.1", - "@ethereumjs/util": "^8.1.0", - "ethereum-cryptography": "^2.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", - "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", - "dependencies": { - "@ethereumjs/rlp": "^4.0.1", - "ethereum-cryptography": "^2.0.0", - "micro-ftch": "^0.3.1" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -3594,128 +3556,6 @@ "@lit-labs/ssr-dom-shim": "^1.0.0" } }, - "node_modules/@metamask/json-rpc-engine": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-10.0.0.tgz", - "integrity": "sha512-10GzJR3G+MM1uS9tLEOw67fc8/kstCSwVoSqaL3fxYaWfUrM6RJWAq1jnMdVrLgyItDguC0d8fsW1FTmF856rQ==", - "dependencies": { - "@metamask/rpc-errors": "^7.0.0", - "@metamask/safe-event-emitter": "^3.0.0", - "@metamask/utils": "^9.1.0" - }, - "engines": { - "node": "^18.18 || >=20" - } - }, - "node_modules/@metamask/json-rpc-middleware-stream": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-8.0.4.tgz", - "integrity": "sha512-diUtPYcOA5rC2L8mZtq6jLM63/G33Uucl9fDc9cK133qm5MROIp0ynfJPk1GTMLvovS2QOhqMjJZdXR+sLauBg==", - "dependencies": { - "@metamask/json-rpc-engine": "^10.0.0", - "@metamask/safe-event-emitter": "^3.0.0", - "@metamask/utils": "^9.1.0", - "readable-stream": "^3.6.2" - }, - "engines": { - "node": "^18.18 || >=20" - } - }, - "node_modules/@metamask/object-multiplex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz", - "integrity": "sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==", - "license": "ISC", - "dependencies": { - "once": "^1.4.0", - "readable-stream": "^3.6.2" - }, - "engines": { - "node": "^16.20 || ^18.16 || >=20" - } - }, - "node_modules/@metamask/providers": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-18.1.0.tgz", - "integrity": "sha512-k/R9EJUx4cAqzWCzSDLBTg44XJlhQQgAucFPP9QCdz9Qi9hg2D8YwWJ28bnxEjl/HKc39xLL1s4VvPk0g9QR0Q==", - "dependencies": { - "@metamask/json-rpc-engine": "^10.0.0", - "@metamask/json-rpc-middleware-stream": "^8.0.4", - "@metamask/object-multiplex": "^2.0.0", - "@metamask/rpc-errors": "^7.0.0", - "@metamask/safe-event-emitter": "^3.1.1", - "@metamask/utils": "^9.0.0", - "detect-browser": "^5.2.0", - "extension-port-stream": "^4.1.0", - "fast-deep-equal": "^3.1.3", - "is-stream": "^2.0.0", - "readable-stream": "^3.6.2" - }, - "engines": { - "node": "^18.18 || >=20" - }, - "peerDependencies": { - "webextension-polyfill": "^0.10.0 || ^0.11.0 || ^0.12.0" - } - }, - "node_modules/@metamask/rpc-errors": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-7.0.0.tgz", - "integrity": "sha512-KDkqwL+MgGMOex6KHntbMQsHGlW29QeH5vpaG/bzovsf1r8xFwxk5f5vnP7/AGpzR9EojNhP8aKeBSJ44rvDMw==", - "dependencies": { - "@metamask/utils": "^9.0.0", - "fast-safe-stringify": "^2.0.6" - }, - "engines": { - "node": "^18.20 || ^20.17 || >=22" - } - }, - "node_modules/@metamask/safe-event-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz", - "integrity": "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@metamask/superstruct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@metamask/superstruct/-/superstruct-3.1.0.tgz", - "integrity": "sha512-N08M56HdOgBfRKkrgCMZvQppkZGcArEop3kixNEtVbJKm6P9Cfg0YkI6X0s1g78sNrj2fWUwvJADdZuzJgFttA==", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/utils": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", - "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.1.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/utils/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@motionone/animation": { "version": "10.18.0", "resolved": "https://registry.npmjs.org/@motionone/animation/-/animation-10.18.0.tgz", @@ -3867,17 +3707,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4862,14 +4691,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -4904,11 +4725,6 @@ "license": "MIT", "peer": true }, - "node_modules/@types/ms": { - "version": "0.7.34", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" - }, "node_modules/@types/node": { "version": "22.7.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", @@ -8419,6 +8235,7 @@ "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -9666,21 +9483,6 @@ "dev": true, "license": "MIT" }, - "node_modules/extension-port-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-4.2.0.tgz", - "integrity": "sha512-i5IgiPVMVrHN+Zx8PRjvFsOw8L1A3sboVwPZghDjW9Yp1BMmBDE6mCcTNu4xMXPYduBOwI3CBK7wd72LcOyD6g==", - "license": "ISC", - "dependencies": { - "readable-stream": "^3.6.2 || ^4.4.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "webextension-polyfill": "^0.10.0 || ^0.11.0 || ^0.12.0" - } - }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -9722,6 +9524,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -11222,6 +11025,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12575,11 +12379,6 @@ "node": ">= 8" } }, - "node_modules/micro-ftch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", - "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==" - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -12743,6 +12542,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/muggle-string": { @@ -14256,14 +14056,6 @@ "node": ">=10.13.0" } }, - "node_modules/pony-cause": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", - "integrity": "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -17115,18 +16907,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -18349,13 +18129,6 @@ "npm": ">=6.12.0" } }, - "node_modules/webextension-polyfill": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz", - "integrity": "sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==", - "license": "MPL-2.0", - "peer": true - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index 3720d8cad..b7ff3d450 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@hashgraph/sdk": "^2.52.0", "@hedera-name-service/hns-resolution-sdk": "^2.0.11", "@kabuto-sh/ns": "^0.14.2", - "@metamask/providers": "^18.1.0", "@oruga-ui/oruga-next": "^0.7.0", "@vuepic/vue-datepicker": "^9.0.3", "axios": "^1.7.7", @@ -39,7 +38,6 @@ "base32-encode": "^2.0.0", "bulma": "^0.9.4", "core-js": "^3.38.1", - "crypto-js": "4.1.1", "ethers": "^6.13.4", "file-saver": "^2.0.5", "hashconnect": "^0.1.10", diff --git a/src/components/values/abi/ContractCallBuilder.ts b/src/components/values/abi/ContractCallBuilder.ts index dea6c6373..96811ebaa 100644 --- a/src/components/values/abi/ContractCallBuilder.ts +++ b/src/components/values/abi/ContractCallBuilder.ts @@ -25,6 +25,7 @@ import {ContractCallRequest, ContractCallResponse} from "@/schemas/HederaSchemas import {walletManager} from "@/router"; import axios from "axios"; import {ABIController} from "@/components/contract/ABIController"; +import {ContractByIdCache} from "@/utils/cache/ContractByIdCache"; export class ContractCallBuilder { @@ -104,16 +105,15 @@ export class ContractCallBuilder { public async execute(): Promise { const contractId = this.abiController.abiAnalyzer.contractAnalyzer.contractId.value - const contractAddress = this.abiController.abiAnalyzer.contractAnalyzer.contractAddress.value const itf = this.abiController.targetInterface.value const functionData = this.functionData.value - if (contractId !== null && contractAddress !== null && itf !== null && functionData !== null) { + if (contractId !== null && itf !== null && functionData !== null) { try { let response: string | null if (this.isReadOnly()) { - response = await ContractCallBuilder.executeWithMirrorNode(contractAddress, functionData) + response = await ContractCallBuilder.executeWithMirrorNode(contractId, functionData) } else { - response = await ContractCallBuilder.executeWithWallet(contractId, contractAddress, functionData) + response = await ContractCallBuilder.executeWithWallet(contractId, functionData) } this.lastValue.value = response !== null ? itf.decodeFunctionResult(this.fragment, response) : null this.lastError.value = null @@ -140,8 +140,9 @@ export class ContractCallBuilder { // Private // - private static async executeWithMirrorNode(contractAddress: string, functionData: string): Promise { + private static async executeWithMirrorNode(contractId: string, functionData: string): Promise { const url = "api/v1/contracts/call" + const contractAddress = await ContractByIdCache.instance.findContractAddress(contractId) ?? contractId const body: ContractCallRequest = { data: functionData, to: contractAddress, @@ -151,8 +152,8 @@ export class ContractCallBuilder { } - private static async executeWithWallet(contractId: string, contractAddress: string, functionData: string): Promise { - const callResult = await walletManager.callContract(contractId, contractAddress, functionData) + private static async executeWithWallet(contractId: string, functionData: string): Promise { + const callResult = await walletManager.callContract(contractId, functionData) return typeof callResult == "string" ? null : callResult.call_result } @@ -183,4 +184,4 @@ export class ContractParamBuilder { this.encodingError.value = null } } -} \ No newline at end of file +} diff --git a/src/utils/cache/AccountByIdCache.ts b/src/utils/cache/AccountByIdCache.ts index 7a694b263..2e61dbd9c 100644 --- a/src/utils/cache/AccountByIdCache.ts +++ b/src/utils/cache/AccountByIdCache.ts @@ -23,6 +23,7 @@ import {EntityCache} from "@/utils/cache/base/EntityCache"; import axios from "axios"; import {AccountByAddressCache} from "@/utils/cache/AccountByAddressCache"; import {AccountByAliasCache} from "@/utils/cache/AccountByAliasCache"; +import {EntityID} from "@/utils/EntityID"; export class AccountByIdCache extends EntityCache { @@ -32,6 +33,17 @@ export class AccountByIdCache extends EntityCache { + let result: string|null + const accountInfo = await this.lookup(accountId) + if (accountInfo !== null) { + result = accountInfo.evm_address ?? EntityID.parse(accountId)?.toAddress() ?? null + } else { + result = null + } + return result + } + public updateWithAccountInfo(accountInfo: AccountBalanceTransactions): void { if (accountInfo.account) { this.forget(accountInfo.account) diff --git a/src/utils/cache/ContractByIdCache.ts b/src/utils/cache/ContractByIdCache.ts index 09666d458..4eed220aa 100644 --- a/src/utils/cache/ContractByIdCache.ts +++ b/src/utils/cache/ContractByIdCache.ts @@ -22,6 +22,7 @@ import {ContractResponse} from "@/schemas/HederaSchemas"; import {EntityCache} from "@/utils/cache/base/EntityCache"; import axios from "axios"; import {ContractByAddressCache} from "@/utils/cache/ContractByAddressCache"; +import {EntityID} from "@/utils/EntityID"; export class ContractByIdCache extends EntityCache { @@ -31,6 +32,17 @@ export class ContractByIdCache extends EntityCache { + let result: string|null + const contractInfo = await this.lookup(contractId) + if (contractInfo !== null) { + result = contractInfo.evm_address ?? EntityID.parse(contractId)?.toAddress() ?? null + } else { + result = null + } + return result + } + public updateWithContractResponse(contractResponse: ContractResponse): void { if (contractResponse.contract_id) { this.forget(contractResponse.contract_id) diff --git a/src/utils/wallet/EIP6963Agent.ts b/src/utils/wallet/EIP6963Agent.ts new file mode 100644 index 000000000..3c6f1ca4b --- /dev/null +++ b/src/utils/wallet/EIP6963Agent.ts @@ -0,0 +1,53 @@ +/*- + * + * Hedera Mirror Node Explorer + * + * Copyright (C) 2021 - 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import {shallowRef} from "vue"; +import {EIP6963AnnounceProviderEvent, EIP6963ProviderDetail} from "@/utils/wallet/eip6963"; + + +export class EIP6963Agent { + + public static readonly instance = new EIP6963Agent() + + private readonly providers = shallowRef([]) + + public findProviderDetails(rdns: string): EIP6963ProviderDetail|null { + return this.providers.value.find((d: EIP6963ProviderDetail) => d.info.rdns === rdns) ?? null + } + + + // + // Private + // + + private constructor() { + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-6963.md#dapp-implementation + window.addEventListener("eip6963:announceProvider", this.handleAnnounceProvider) + window.dispatchEvent(new Event("eip6963:requestProvider")) + } + + private readonly handleAnnounceProvider = (event: EIP6963AnnounceProviderEvent): void => { + const newUUID = event.detail.info.uuid + const found = this.providers.value.find((d: EIP6963ProviderDetail) => d.info.uuid == newUUID) + if (!found) { + this.providers.value.push(event.detail) + } + } +} diff --git a/src/utils/wallet/WalletDriver.ts b/src/utils/wallet/WalletDriver.ts index 5baee3d2e..b996ae771 100644 --- a/src/utils/wallet/WalletDriver.ts +++ b/src/utils/wallet/WalletDriver.ts @@ -52,12 +52,10 @@ export abstract class WalletDriver { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async callContract(contractId: string, contractAddress: string, functionData: string, payerId: string): Promise { + public async callContract(contractId: string, functionData: string, payerId: string): Promise { throw this.toBeImplemented("callContract") } - public abstract isConnected(): boolean - // // Public (utilities) // diff --git a/src/utils/wallet/WalletDriver_Blade.ts b/src/utils/wallet/WalletDriver_Blade.ts index 7b39e7f23..9c9db1e48 100644 --- a/src/utils/wallet/WalletDriver_Blade.ts +++ b/src/utils/wallet/WalletDriver_Blade.ts @@ -100,10 +100,6 @@ export class WalletDriver_Blade extends WalletDriver_Hedera { } } - public isConnected(): boolean { - return this.connector !== null - } - // // WalletDriver_Hedera // diff --git a/src/utils/wallet/WalletDriver_Brave.ts b/src/utils/wallet/WalletDriver_Brave.ts index 92435e9d0..423094731 100644 --- a/src/utils/wallet/WalletDriver_Brave.ts +++ b/src/utils/wallet/WalletDriver_Brave.ts @@ -18,14 +18,10 @@ * */ -import {BaseProvider} from '@metamask/providers'; import {WalletDriver_Ethereum} from "@/utils/wallet/WalletDriver_Ethereum"; -import {WalletDriverError} from "@/utils/wallet/WalletDriverError"; export class WalletDriver_Brave extends WalletDriver_Ethereum { - private braveProvider: BaseProvider | null = null - // // Public // @@ -33,50 +29,7 @@ export class WalletDriver_Brave extends WalletDriver_Ethereum { public constructor() { super("Brave Wallet", "https://brave.com/static-assets/images/optimized/brave-branding-assets/images/brave-logo-color-RGB_reversed.png", - "https://brave.com/static-assets/images/brave-logo-no-shadow.png") - } - - // - // WalletDriver_Ethereum - // - - public async isExpectedProvider(provider: object): Promise { - // https://wallet-docs.brave.com/ethereum/wallet-detection/ - const clientVersion = await (provider as any).request({method: "web3_clientVersion"}) - const isBraveWallet = clientVersion.split('/')[0] === 'BraveWallet' - this.braveProvider = isBraveWallet ? provider as BaseProvider : null - return this.braveProvider !== null - } - - public async connect(network: string): Promise { - const result = await super.connect(network) - this.braveProvider?.on('chainChanged', this.handleDisconnect) - return Promise.resolve(result) - } - - public async disconnect(): Promise { - this.braveProvider?.removeListener("chainChanged", this.handleDisconnect) - this.braveProvider = null - return super.disconnect() - } - - // - // WalletDriver - // - - public extensionNotFound(): WalletDriverError { - // Brave wallet is builtin to Brave browser => we adapt wording - const message = "Cannot access to " + this.name - const extra = "Make sure to use Brave browser and enable wallet in browser settings." - return new WalletDriverError(message, extra) + "https://brave.com/static-assets/images/brave-logo-no-shadow.png", + "com.brave.wallet") } - - - // - // Private - // - - private readonly handleDisconnect = () => this.disconnect() - - } diff --git a/src/utils/wallet/WalletDriver_Coinbase.ts b/src/utils/wallet/WalletDriver_Coinbase.ts index f595d4656..866fd8f80 100644 --- a/src/utils/wallet/WalletDriver_Coinbase.ts +++ b/src/utils/wallet/WalletDriver_Coinbase.ts @@ -19,9 +19,6 @@ */ import {WalletDriver_Ethereum} from "@/utils/wallet/WalletDriver_Ethereum"; -import {BrowserProvider} from "ethers"; -import {AccountByAddressCache} from "@/utils/cache/AccountByAddressCache"; -import {WalletDriverCancelError} from "@/utils/wallet/WalletDriverError"; export class WalletDriver_Coinbase extends WalletDriver_Ethereum { @@ -36,42 +33,8 @@ export class WalletDriver_Coinbase extends WalletDriver_Ethereum { public constructor() { super("Coinbase Wallet", "https://logodownload.org/wp-content/uploads/2021/04/coinbase-logo-1-2048x430.png", - "https://logosarchive.com/wp-content/uploads/2021/12/Coinbase-icon-symbol-1.svg") - } - - // - // WalletDriver_Ethereum - // - - public async isExpectedProvider(provider: object): Promise { - const result = "isCoinbaseWallet" in provider && provider.isCoinbaseWallet == true - return Promise.resolve(result) - } - - protected async fetchAccountIds(provider: BrowserProvider): Promise { - const result: string[] = [] - - try { - // Coinbase does not support wallet_requestPermissions request - // => we run eth_requestAccounts only - const accountResponse = await provider.send("eth_requestAccounts", []) - - // Fetches info for each return accounts - for (const address of accountResponse ?? []) { - const accountInfo = address ? await AccountByAddressCache.instance.lookup(address) : null - if (accountInfo?.account) { - result.push(accountInfo.account) - } - } - } catch (reason) { - if (this.isCancelError(reason)) { - throw new WalletDriverCancelError() - } else { - throw this.connectFailure("Check " + this.name + " extension for details") - } - } - - return Promise.resolve(result) + "https://logosarchive.com/wp-content/uploads/2021/12/Coinbase-icon-symbol-1.svg", + "com.coinbase.wallet") } diff --git a/src/utils/wallet/WalletDriver_Ethereum.ts b/src/utils/wallet/WalletDriver_Ethereum.ts index be37f7a51..f5eaca86e 100644 --- a/src/utils/wallet/WalletDriver_Ethereum.ts +++ b/src/utils/wallet/WalletDriver_Ethereum.ts @@ -21,21 +21,36 @@ import {HederaLogo, WalletDriver} from "@/utils/wallet/WalletDriver"; import {routeManager} from "@/router"; import {EntityID} from "@/utils/EntityID"; -import {BrowserProvider, ethers} from "ethers"; -import {NetworkEntry} from "@/schemas/NetworkRegistry"; +import {ethers} from "ethers"; import {WalletDriverCancelError, WalletDriverError} from "@/utils/wallet/WalletDriverError"; import {AccountByAddressCache} from "@/utils/cache/AccountByAddressCache"; import {ContractResultDetails, Transaction} from "@/schemas/HederaSchemas"; import {waitFor} from "@/utils/TimerUtils"; import {ContractResultByHashCache} from "@/utils/cache/ContractResultByHashCache"; import {TransactionByTsCache} from "@/utils/cache/TransactionByTsCache"; -import {markRaw} from "vue"; import {TokenInfoCache} from "@/utils/cache/TokenInfoCache"; import {makeTokenSymbol} from "@/schemas/HederaUtils"; +import { + AddEthereumChainParameter, + EIP1193Provider, + eth_chainId, + eth_isUnrecognizedChainId, + eth_isUnsupportedMethod, + eth_isUserReject, + eth_requestAccounts, + wallet_addEthereumChain, + wallet_revokePermissions, + wallet_switchEthereumChain, + wallet_watchAsset, + WatchAssetParameters +} from "@/utils/wallet/eip1193"; +import {EIP6963Agent} from "@/utils/wallet/EIP6963Agent"; +import {AccountByIdCache} from "@/utils/cache/AccountByIdCache"; +import {ContractByIdCache} from "@/utils/cache/ContractByIdCache"; export abstract class WalletDriver_Ethereum extends WalletDriver { - protected provider: BrowserProvider | null = null + protected providerDN: string // // Public @@ -43,10 +58,10 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { public async watchToken(accountId: string, tokenId: string, serialNumber?: string): Promise { const tokenAddress = EntityID.parse(tokenId)?.toAddress() ?? null - if (accountId !== null && tokenAddress !== null && this.provider !== null) { + if (accountId !== null && tokenAddress !== null) { const tokenInfo = await TokenInfoCache.instance.lookup(tokenId) const symbol = makeTokenSymbol(tokenInfo, 11) - let params = {} + let params: WatchAssetParameters if (serialNumber) { params = { @@ -64,14 +79,14 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { "options": { "address": `0x${tokenAddress}`, "symbol": symbol, - "decimals": tokenInfo?.decimals, + "decimals": tokenInfo?.decimals ? Number(tokenInfo?.decimals) : 0, "image": HederaLogo } } } try { - await this.provider.send("wallet_watchAsset", params) + await wallet_watchAsset(this.fetchProvider(), params) } catch (reason) { throw this.makeCallFailure(reason, `${(reason as ethers.EthersError).error?.message || `Unknown Error`}.`) } @@ -81,21 +96,11 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { return Promise.resolve() } - // - // Public (to be subclassed) - // - - public async isExpectedProvider(provider: object): Promise { - console.log("provider=" + JSON.stringify(provider)) - throw this.toBeImplemented("isExpectedProvider()") - } - // // WalletDriver // public async connect(network: string): Promise { - let result: string[] // Sanity check const networkEntry = routeManager.currentNetworkEntry.value @@ -103,258 +108,116 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { throw this.connectFailure("Network inconsistency: bug") } - let provider: BrowserProvider | null = null - for (const p of this.fetchEthereumProviders()) { - if (await this.isExpectedProvider(p)) { - provider = new BrowserProvider(p as ethers.Eip1193Provider) - break - } - } - - if (provider !== null) { - - // Switch wallet to network if needed - await this.switchToNetwork(provider, networkEntry) - - // Collects account ids - result = await this.fetchAccountIds(provider) - if (result.length == 0) { - // Did the user cancel connection from the wallet ? - throw this.connectFailure("No account found") - } - - this.provider = markRaw(provider) - // await this.provider.once('chainChanged', this.handleDisconnect); - } else { - throw this.extensionNotFound() + // Collects account ids + const result = await this.fetchAccountIds() + if (result.length == 0) { + // Did the user cancel connection from the wallet ? + throw this.connectFailure("No account found") } return Promise.resolve(result) } public async disconnect(): Promise { - // this.provider?.off("chainChanged", this.handleDisconnect) - this.provider = null - } - - public async associateToken(accountId: string, tokenId: string): Promise { - let result: string - - // https://stackoverflow.com/questions/76980638/how-do-you-associate-dissociate-an-hts-token-using-evm-transaction - - const tokenAddress = EntityID.parse(tokenId)?.toAddress() ?? null - if (accountId !== null && tokenAddress !== null && this.provider !== null) { + const provider = this.fetchProvider() - const abi = ["function associate()"] - const signer = await this.provider.getSigner() - const contract = new ethers.Contract("0x" + tokenAddress, abi, signer) - try { - const transactionResult = await contract.associate(); - const hederaTransaction = await this.waitForTransactionSurfacing(transactionResult.hash) - result = typeof hederaTransaction == "string" ? hederaTransaction : hederaTransaction.transaction_id - } catch (reason) { - throw this.makeCallFailure(reason, "associateToken") + // Tries calling wallet_revokePermissions() method + // This one is not supported by Coinbase => in that case, we hide the exception + try { + await wallet_revokePermissions(provider) + } catch(reason) { + if (eth_isUnsupportedMethod(reason)) { + // See above + } else { + throw reason } - } else { - throw this.callFailure("Invalid arguments") } + } + public async associateToken(accountId: string, tokenId: string): Promise { + const abi = ["function associate()"] + const iface = new ethers.Interface(abi) + const callData = iface.encodeFunctionData("associate", []) + const result = await this.executeCall(accountId, tokenId, callData) return Promise.resolve(result) } public async dissociateToken(accountId: string, tokenId: string): Promise { - let result: string - - // https://stackoverflow.com/questions/76980638/how-do-you-associate-dissociate-an-hts-token-using-evm-transaction - - const tokenAddress = EntityID.parse(tokenId)?.toAddress() ?? null - if (accountId !== null && tokenAddress !== null && this.provider !== null) { - - const abi = ["function dissociate()"]; - const signer = await this.provider.getSigner() - const contract = new ethers.Contract("0x" + tokenAddress, abi, signer) - try { - const transactionResult = await contract.dissociate(); - const hederaTransaction = await this.waitForTransactionSurfacing(transactionResult.hash) - result = typeof hederaTransaction == "string" ? hederaTransaction : hederaTransaction.transaction_id - } catch (reason) { - throw this.makeCallFailure(reason, "dissociateToken") - } - } else { - throw this.callFailure("Invalid arguments") - } - + const abi = ["function dissociate()"]; + const iface = new ethers.Interface(abi) + const callData = iface.encodeFunctionData("dissociate", []) + const result = await this.executeCall(accountId, tokenId, callData) return Promise.resolve(result) } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async callContract(contractId: string, contractAddress: string, functionData: string, payerId: string): Promise { + public async callContract(contractId: string, callData: string, accountId: string): Promise { let result: ContractResultDetails | string - if (this.provider !== null) { - const signer = await this.provider.getSigner() - const transaction: ethers.TransactionRequest = { - to: contractAddress, - data: functionData + const accountAddress = await AccountByIdCache.instance.findAccountAddress(accountId) + const contractAddress = await ContractByIdCache.instance.findContractAddress(contractId) + if (accountAddress !== null && contractAddress !== null) { + // 1) Checks current chain and tries to setup if needed + if (!await this.isChainOK()) { + await this.trySetupChain() } + // 2) Sends transaction try { - const response = await signer.sendTransaction(transaction) - result = await this.waitForContractResultSurfacing(response.hash) + result = await this.sendTransaction(accountAddress, "0x" + contractAddress, callData) } catch (reason) { - throw this.makeCallFailure(reason, "callContract") + if (eth_isUserReject(reason)) { + throw new WalletDriverCancelError() + } else { + throw reason + } + } + // 3) Waits for transaction to appear in mirror node + try { + result = await this.waitForContractResultSurfacing(result) + } catch { + // Accurately ignored } + } else { throw this.callFailure("Invalid arguments") } + return result } - public isConnected(): boolean { - return this.provider !== null - } // - // Protected (for subclasses usage) + // Private // - protected makeCallFailure(reason: unknown, extra: string): WalletDriverError { - if (this.isCancelError(reason)) { - throw new WalletDriverCancelError() - } else { - console.log("WalletDriver_Ethereum.makeCallFailure: " + JSON.stringify(reason)) - const providerError = (reason as ethers.EthersError).error - throw this.callFailure(providerError?.message ?? extra) - } - } - - protected isCancelError(reason: unknown): boolean { - return (reason as ethers.EthersError).code == "ACTION_REJECTED" - } - - protected isChainMissing(reason: unknown): boolean { - const providerError = (reason as ethers.EthersError).error - return typeof providerError == "object" && "code" in providerError && providerError.code == 4902 - } - - protected fetchEthereumProviders(): object[] { - // See https://docs.cloud.coinbase.com/wallet-sdk/docs/injected-provider-guidance - - let result: object[] - if ("ethereum" in window && typeof window.ethereum === "object" && window.ethereum !== null) { - const ethereum = window.ethereum as Object - if ("providers" in ethereum && Array.isArray(ethereum.providers)) { - result = ethereum.providers - } else { - result = [ethereum] - } - } else { - result = [] - } - - return result + protected constructor(name: string, logoURL: string | null, iconURL: string | null, providerDN: string) { + super(name, logoURL, iconURL) + this.providerDN = providerDN } // - // Protected (to be altered by subclass if needed) + // Private // - protected async switchToNetwork(provider: BrowserProvider, networkEntry: NetworkEntry): Promise { - - const chainId = networkEntry.sourcifySetup?.hexChainID() - if (chainId == null) { - throw this.connectFailure("Network " + networkEntry.name + " is not setup for use with wallet") - } - - // Make sure that chainId is the current chain in Metamask - const walletChainId = await provider.send('eth_chainId', []) - if (walletChainId !== chainId) { - try { - // Try to switch - await provider.send("wallet_switchEthereumChain", [{chainId: chainId}]) - } catch (reason) { - if (this.isCancelError(reason)) { - throw new WalletDriverCancelError() - } else if (this.isChainMissing(reason)) { - // Try to add chain et retry - try { - await this.addHederaChain(provider, chainId) - } catch { - throw this.connectFailure("Make sure that 'Hedera " + networkEntry.name + "' network is added to " + this.name) - } - // With some wallets, wallet_addEthereumChain also implies wallet_switchEthereumChain - // Not sure this behavior is standard => we switch if needed - const newWalletChainId = await provider.send('eth_chainId', []) - if (newWalletChainId !== chainId) { - await provider.send("wallet_switchEthereumChain", [{chainId: chainId}]) - } - } else { - throw this.connectFailure("Make sure that 'Hedera " + networkEntry.name + "' network is added to " + this.name) - } - } - } - } - - protected async addHederaChain(provider: BrowserProvider, desiredChainId: string): Promise { - const NETWORK_CONFIG = { - rpcUrls: [""], - chainName: "", - blockExplorerUrls: [""], - } - - switch (desiredChainId) { - case "0x127": - NETWORK_CONFIG.chainName = "Hedera Mainnet" - NETWORK_CONFIG.rpcUrls = ["https://mainnet.hashio.io/api"] - NETWORK_CONFIG.blockExplorerUrls = ["https://hashscan.io/mainnet/dashboard"] - break; - case "0x128": - NETWORK_CONFIG.chainName = "Hedera Testnet" - NETWORK_CONFIG.rpcUrls = ["https://testnet.hashio.io/api"] - NETWORK_CONFIG.blockExplorerUrls = ["https://hashscan.io/testnet/dashboard"] - break; - case "0x129": - NETWORK_CONFIG.chainName = "Hedera Previewnet" - NETWORK_CONFIG.rpcUrls = ["https://previewnet.hashio.io/api"] - NETWORK_CONFIG.blockExplorerUrls = ["https://hashscan.io/preview/dashboard"] - break; - } - try { - await provider.send( - 'wallet_addEthereumChain', - [{ - chainId: desiredChainId, - rpcUrls: NETWORK_CONFIG.rpcUrls, - chainName: NETWORK_CONFIG.chainName, - nativeCurrency: {name: 'HBAR', decimals: 18, symbol: 'HBAR'}, - blockExplorerUrls: NETWORK_CONFIG.blockExplorerUrls, - iconUrls: [HederaLogo], - }], - ) - } catch (reason: any) { - if (this.isCancelError(reason)) { - throw new WalletDriverCancelError() - } else { - throw this.connectFailure(reason) - } + private makeCallFailure(reason: unknown, extra: string): WalletDriverError { + if (eth_isUserReject(reason)) { + throw new WalletDriverCancelError() + } else { + console.log("WalletDriver_Ethereum.makeCallFailure: " + JSON.stringify(reason)) + const providerError = (reason as ethers.EthersError).error + throw this.callFailure(providerError?.message ?? extra) } } - protected async fetchAccountIds(provider: BrowserProvider): Promise { + private async fetchAccountIds(): Promise { const result: string[] = [] - try { - // We do this in two steps: - // 1) wallet_requestPermissions first : this forces wallet to interact with user - // 2) eth_requestAccounts to get accounts chosen by user - // See reference discussion here: - // https://github.com/MetaMask/metamask-extension/issues/8990#issuecomment-980489771 + const provider = this.fetchProvider() - // 1) - await provider.send("wallet_requestPermissions", [{eth_accounts: {}}]) + try { - // 2) - const accountResponse = await provider.send("eth_requestAccounts", []) + // Requests accounts + const accountResponse = await eth_requestAccounts(provider) // Fetches info for each return accounts for (const address of accountResponse ?? []) { @@ -364,7 +227,7 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { } } } catch (reason) { - if (this.isCancelError(reason)) { + if (eth_isUserReject(reason)) { throw new WalletDriverCancelError() } else { throw this.connectFailure("Check " + this.name + " extension for details") @@ -375,7 +238,7 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { } - protected async waitForTransactionSurfacing(ethereumHash: ethers.BytesLike): Promise { + private async waitForTransactionSurfacing(ethereumHash: ethers.BytesLike): Promise { let result: Promise const hash = ethers.hexlify(ethereumHash) @@ -401,7 +264,7 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { } - protected async waitForContractResultSurfacing(ethereumHash: ethers.BytesLike): Promise { + private async waitForContractResultSurfacing(ethereumHash: ethers.BytesLike): Promise { let result: Promise const hash = ethers.hexlify(ethereumHash) @@ -421,5 +284,202 @@ export abstract class WalletDriver_Ethereum extends WalletDriver { return result } + private fetchProvider(): EIP1193Provider { + const p = EIP6963Agent.instance.findProviderDetails(this.providerDN) + if (p == null) { + throw this.extensionNotFound() + } + return p.provider + } + + private async executeCall(accountId: string, tokenId: string, callData: string): Promise { + let result: string + + const accountAddress = await AccountByIdCache.instance.findAccountAddress(accountId) + const tokenAddress = EntityID.parse(tokenId)?.toAddress() ?? null + if (accountAddress !== null && tokenAddress !== null) { + // 1) Checks current chain and tries to setup if needed + if (!await this.isChainOK()) { + await this.trySetupChain() + } + // 2) Sends transaction + try { + result = await this.sendTransaction(accountAddress, "0x" + tokenAddress, callData) + } catch (reason) { + if (eth_isUserReject(reason)) { + throw new WalletDriverCancelError() + } else { + throw reason + } + } + // 3) Waits for transaction to appear in mirror node + try { + await this.waitForTransactionSurfacing(result) + } catch { + // Accurately ignored + } + + } else { + throw this.callFailure("Invalid arguments") + } + + return result + } + + private async sendTransaction(fromAddress: string, toAddress: string, callData: string): Promise { + const ethParams = { + from: fromAddress, + to: toAddress, + data: callData, + gas: "0x1E8480", // 2_000_000 + gasPrice: "0x1D1A94A2000" // 2_000_000_000_000 + } + const request = { + method: "eth_sendTransaction", + params: [ethParams] + } + return await this.fetchProvider().request(request) as string + } + + private async trySetupChain(): Promise { + + try { + await this.trySwitchingChain() + if (!await this.isChainOK()) { + await this.tryAddingChain() + } + } catch(reason) { + if (eth_isUserReject(reason)) { + throw new WalletDriverCancelError() + // } else if (eth_isUnsupportedMethod(reason)) { + // throw new WalletClientSetupRequiredError() + } else { + throw reason + } + } + if (!await this.isChainOK()) { + throw this.networkSetupFailure(routeManager.currentNetwork.value) + } + } + + + private targetChainId(): string { + const result = networkToChainId(routeManager.currentNetwork.value) + if (result === null) { + throw "bug" + } + return result + } + + + private async isChainOK(): Promise { + const currentChainId = await eth_chainId(this.fetchProvider()) + return this.targetChainId() == currentChainId + } + + private async trySwitchingChain(): Promise { + try { + await wallet_switchEthereumChain(this.fetchProvider(), this.targetChainId()) + } catch(reason) { + if (!eth_isUnsupportedMethod(reason) && !eth_isUnrecognizedChainId(reason)) { + throw reason + } + } + } + + private async tryAddingChain(): Promise { + const chainParam = this.makeChainParam() + if (chainParam !== null) { + await wallet_addEthereumChain(this.fetchProvider(), chainParam) + } else { + throw "bug" + } + } + + private makeChainParam(): AddEthereumChainParameter|null { + let result: AddEthereumChainParameter|null + + switch(routeManager.currentNetwork.value) { + case "mainnet": + result = CHAIN_PARAM_MAINNET + break + case "testnet": + result = CHAIN_PARAM_TESTNET + break + case "previewnet": + result = CHAIN_PARAM_PREVIEWNET + break + default: + result = null + break + } + return result + } + + private networkSetupFailure(network: string): WalletDriverError { + const extra = "Make sure that 'Hedera " + network + "' network is added to " + this.name + return this.connectFailure(extra) + } + +} + +const HEDERA_LOGO = + 'data:image/svg+xml;utf8,' + + '' + + '' + + '' + + +const NATIVE_CURRENCY = { + name: 'HBAR', + decimals: 18, + symbol: 'HBAR' +} + +const CHAIN_PARAM_MAINNET: AddEthereumChainParameter = { + chainId: "0x127", + blockExplorerUrls: [ "https://hashscan.io/mainnet/dashboard" ], + chainName: "Hedera Mainnet", + iconUrls: [ HEDERA_LOGO ], + nativeCurrency: NATIVE_CURRENCY, + rpcUrls: [ "https://mainnet.hashio.io/api" ] +} + +const CHAIN_PARAM_TESTNET: AddEthereumChainParameter = { + chainId: "0x128", + blockExplorerUrls: [ "https://hashscan.io/testnet/dashboard" ], + chainName: "Hedera Testnet", + iconUrls: [ HEDERA_LOGO ], + nativeCurrency: NATIVE_CURRENCY, + rpcUrls: [ "https://testnet.hashio.io/api" ] +} + +const CHAIN_PARAM_PREVIEWNET: AddEthereumChainParameter = { + chainId: "0x129", + blockExplorerUrls: [ "https://hashscan.io/previewnet/dashboard" ], + chainName: "Hedera Previewnet", + iconUrls: [ HEDERA_LOGO ], + nativeCurrency: NATIVE_CURRENCY, + rpcUrls: [ "https://previewnet.hashio.io/api" ] +} + +function networkToChainId(network: string): string|null { + let result: string|null + // https://docs.hedera.com/hedera/core-concepts/smart-contracts/deploying-smart-contracts/json-rpc-relay + switch(network) { + case "mainnet": + result = "0x127" + break + case "testnet": + result = "0x128" + break + case "previewnet": + result = "0x129" + break + default: + result = null + break + } + return result } diff --git a/src/utils/wallet/WalletDriver_Hashpack.ts b/src/utils/wallet/WalletDriver_Hashpack.ts index 32fd62266..3baeb6ecc 100644 --- a/src/utils/wallet/WalletDriver_Hashpack.ts +++ b/src/utils/wallet/WalletDriver_Hashpack.ts @@ -61,10 +61,6 @@ export class WalletDriver_Hashpack extends WalletDriver_Hedera { return Promise.resolve() } - public isConnected(): boolean { - return this.hashConnect !== null - } - // // WalletDriver_Hedera diff --git a/src/utils/wallet/WalletDriver_Hedera.ts b/src/utils/wallet/WalletDriver_Hedera.ts index 348925526..5504eb762 100644 --- a/src/utils/wallet/WalletDriver_Hedera.ts +++ b/src/utils/wallet/WalletDriver_Hedera.ts @@ -214,7 +214,7 @@ export abstract class WalletDriver_Hedera extends WalletDriver { return Promise.resolve(result) } - public async callContract(contractId: string, contractAddress: string, functionData: string, payerId: string): Promise { + public async callContract(contractId: string, functionData: string, payerId: string): Promise { let result: string | ContractResultDetails const fp = hexToByte(functionData) diff --git a/src/utils/wallet/WalletDriver_Metamask.ts b/src/utils/wallet/WalletDriver_Metamask.ts index 65daf31e7..f7ef45bc0 100644 --- a/src/utils/wallet/WalletDriver_Metamask.ts +++ b/src/utils/wallet/WalletDriver_Metamask.ts @@ -19,7 +19,6 @@ */ import {WalletDriver_Ethereum} from "@/utils/wallet/WalletDriver_Ethereum"; -import {MetaMaskInpageProvider} from "@metamask/providers"; /* References: @@ -32,8 +31,6 @@ import {MetaMaskInpageProvider} from "@metamask/providers"; export class WalletDriver_Metamask extends WalletDriver_Ethereum { - private metamaskProvider: MetaMaskInpageProvider | null = null - // // Public // @@ -41,35 +38,9 @@ export class WalletDriver_Metamask extends WalletDriver_Ethereum { public constructor() { super("Metamask", "https://freelogopng.com/images/all_img/1683020860metamask-logo-white.png", - "data:image/png;base64,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") - } - - // - // WalletDriver_Ethereum - // - - public async isExpectedProvider(provider: object): Promise { - const result = "isMetaMask" in provider && provider.isMetaMask == true && !("isBraveWallet" in provider) - return Promise.resolve(result) - } - - public async connect(network: string): Promise { - const result = await super.connect(network) - this.metamaskProvider?.once('chainChanged', this.handleDisconnect) - return Promise.resolve(result) - } - - public async disconnect(): Promise { - this.metamaskProvider?.off("chainChanged", this.handleDisconnect) - this.metamaskProvider = null - return super.disconnect() + "data:image/png;base64,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", + "io.metamask") } - // - // Private - // - - private readonly handleDisconnect = () => this.disconnect() - } diff --git a/src/utils/wallet/WalletManager.ts b/src/utils/wallet/WalletManager.ts index 352933c2f..8e123a978 100644 --- a/src/utils/wallet/WalletManager.ts +++ b/src/utils/wallet/WalletManager.ts @@ -279,10 +279,9 @@ export class WalletManager { } } - public async callContract(contractId: string, contractAddress: string, functionData: string): Promise { + public async callContract(contractId: string, functionData: string): Promise { if (this.accountIdRef.value !== null) { - return this.activeDriver.callContract(contractId, contractAddress, functionData, - this.accountIdRef.value) + return this.activeDriver.callContract(contractId, functionData, this.accountIdRef.value) } else { throw this.activeDriver.callFailure("callContract") } diff --git a/src/utils/wallet/eip1193.ts b/src/utils/wallet/eip1193.ts new file mode 100644 index 000000000..455db0278 --- /dev/null +++ b/src/utils/wallet/eip1193.ts @@ -0,0 +1,181 @@ +/*- + * + * Hedera Mirror Node Explorer + * + * Copyright (C) 2021 - 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// +// https://eips.ethereum.org/EIPS/eip-1193 +// + +export interface EIP1193Provider { + isStatus?: boolean + host?: string + path?: string + sendAsync?: ( + request: { method: string; params?: Array | object }, + callback: (error: Error | null, response: unknown) => void + ) => void + send?: ( + request: { method: string; params?: Array | object }, + callback: (error: Error | null, response: unknown) => void + ) => void + request: (request: { + method: string + params?: Array | object + }) => Promise +} + +declare global { + export interface WindowEventMap { + "eip6963:announceProvider": CustomEvent + } +} + +// +// Tools +// + +// +// https://eips.ethereum.org/EIPS/eip-1474 +// + + +export async function eth_requestAccounts(p: EIP1193Provider): Promise { + return await p.request({ + "method": "eth_requestAccounts", + "params": [], + }) as string[] +} + +export async function eth_accounts(p: EIP1193Provider): Promise { + return await p.request({ + "method": "eth_accounts", + "params": [], + }) as string[] +} + +export async function eth_chainId(p: EIP1193Provider): Promise { + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-695.md + return await p.request({ + "method": "eth_chainId", + "params": [], + }) as string +} + +export async function wallet_switchEthereumChain(p: EIP1193Provider, newChainId: string): Promise { + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3326.md + await p.request({ + "method": "wallet_switchEthereumChain", + "params": [{ + chainId: newChainId + }], + }) +} + +export async function wallet_requestPermissions(p: EIP1193Provider): Promise { + await p.request({ + "method": "wallet_requestPermissions", + "params": [{ + eth_accounts: {} + }], + }) +} + +export async function wallet_revokePermissions(p: EIP1193Provider): Promise { + await p.request({ + "method": "wallet_revokePermissions", + "params": [{ + eth_accounts: {} + }], + }) +} + +// +// Error tooling +// + +export function eth_getErrorCode(reason: unknown): number|null { + let result: number|null + if (typeof reason == "object" && reason !== null && "code" in reason && typeof reason["code"] == "number") { + result = reason["code"] + } else { + result = null + } + return result +} + +export function eth_isUserReject(reason: unknown): boolean { + // https://eips.ethereum.org/EIPS/eip-1193#provider-errors + return eth_getErrorCode(reason) == 4001 +} + +export function eth_isUnsupportedMethod(reason: unknown): boolean { + // https://eips.ethereum.org/EIPS/eip-1474 + const code = eth_getErrorCode(reason) + return code == -32601 || code == -32004 +} + +export function eth_isUnrecognizedChainId(reason: unknown): boolean { + // https://eips.ethereum.org/EIPS/eip-3326 + return eth_getErrorCode(reason) == 4902 +} + + +export interface AddEthereumChainParameter { + // https://eips.ethereum.org/EIPS/eip-3085 + chainId: string; + blockExplorerUrls?: string[]; + chainName?: string; + iconUrls?: string[]; + nativeCurrency?: { + name: string; + symbol: string; + decimals: number; + }; + rpcUrls?: string[]; +} + +export async function wallet_addEthereumChain(p: EIP1193Provider, param: AddEthereumChainParameter): Promise { + await p.request({ + "method": "wallet_addEthereumChain", + "params": [ param ], + }) +} + + +export interface WatchAssetParameters { + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-747.md + type: string // The asset's interface, e.g. 'ERC1046' + options: WatchAssetOptions +} + +export interface WatchAssetOptions { + // https://docs.metamask.io/wallet/reference/wallet_watchasset/ + address: string // The hexadecimal address of the token contract + symbol?: string // A ticker symbol or shorthand, up to 11 characters (optional for ERC-20 tokens) + decimals?: number // The number of token decimals (optional for ERC-20 tokens) + image?: string // A string URL of the token logo (optional for ERC-20 tokens) + tokenId?: string // The unique identifier of the NFT (required for ERC-721 and ERC-1155 tokens) +} + +export async function wallet_watchAsset(p: EIP1193Provider, params: WatchAssetParameters): Promise { + await p.request({ + "method": "wallet_watchAsset", + "params": params, + }) +} diff --git a/src/utils/wallet/eip6963.ts b/src/utils/wallet/eip6963.ts new file mode 100644 index 000000000..b08aaf7d8 --- /dev/null +++ b/src/utils/wallet/eip6963.ts @@ -0,0 +1,45 @@ +/*- + * + * Hedera Mirror Node Explorer + * + * Copyright (C) 2021 - 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +// +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-6963.md +// + +import {EIP1193Provider} from "@/utils/wallet/eip1193"; + +export interface EIP6963ProviderInfo { + rdns: string + uuid: string + name: string + icon: string +} + +export interface EIP6963ProviderDetail { + info: EIP6963ProviderInfo + provider: EIP1193Provider +} + +export type EIP6963AnnounceProviderEvent = { + detail: { + info: EIP6963ProviderInfo + provider: Readonly + } +} diff --git a/tests/unit/staking/WalletDriver_Mock.ts b/tests/unit/staking/WalletDriver_Mock.ts index 4165ce0ae..d9c811543 100644 --- a/tests/unit/staking/WalletDriver_Mock.ts +++ b/tests/unit/staking/WalletDriver_Mock.ts @@ -30,8 +30,6 @@ export class WalletDriver_Mock extends WalletDriver_Hedera { public readonly account: AccountBalanceTransactions public readonly transactionId: string - private connected = false - public updateAccountCounter = 0 // @@ -67,10 +65,6 @@ export class WalletDriver_Mock extends WalletDriver_Hedera { } } - isConnected(): boolean { - return this.connected - } - // // WalletDriver_Hedera //