From e8b7fb539a81c626ad986e7726ea72b6b75ae4e2 Mon Sep 17 00:00:00 2001 From: Adam Carpenter Date: Tue, 4 Apr 2023 10:55:15 -0600 Subject: [PATCH 001/125] Replaced isAddress check from ethers --- docs/yarn.lock | 181 +++++++++------------- packages/coinbase/package.json | 2 +- packages/common/package.json | 2 +- packages/common/src/index.ts | 2 +- packages/common/src/types.ts | 4 +- packages/common/src/utils.ts | 7 + packages/core/package.json | 4 +- packages/core/src/provider.ts | 11 +- packages/core/src/store/actions.ts | 3 +- packages/core/src/types.ts | 5 +- packages/dcent/package.json | 2 +- packages/demo/package.json | 2 +- packages/enkrypt/package.json | 2 +- packages/fortmatic/package.json | 2 +- packages/fortmatic/src/index.ts | 11 +- packages/frontier/package.json | 2 +- packages/gas/package.json | 2 +- packages/gnosis/package.json | 2 +- packages/hw-common/package.json | 2 +- packages/infinity-wallet/package.json | 2 +- packages/injected/package.json | 4 +- packages/keepkey/package.json | 2 +- packages/keepkey/src/index.ts | 6 +- packages/keystone/package.json | 2 +- packages/ledger/package.json | 2 +- packages/ledger/src/index.ts | 2 +- packages/magic/package.json | 2 +- packages/mew-wallet/package.json | 2 +- packages/mew/package.json | 2 +- packages/phantom/package.json | 2 +- packages/portis/package.json | 2 +- packages/react/package.json | 2 +- packages/sequence/package.json | 2 +- packages/sequence/src/index.ts | 4 +- packages/taho/package.json | 2 +- packages/tallyho/package.json | 2 +- packages/torus/package.json | 2 +- packages/torus/src/index.ts | 4 +- packages/transaction-preview/package.json | 2 +- packages/trezor/package.json | 2 +- packages/trust/package.json | 2 +- packages/uauth/package.json | 2 +- packages/uauth/src/index.ts | 2 +- packages/vue/package.json | 2 +- packages/walletconnect/package.json | 2 +- packages/walletconnect/src/v1.ts | 2 +- packages/walletconnect/src/v2.ts | 4 +- packages/walletlink/package.json | 2 +- packages/web3auth/package.json | 2 +- packages/xdefi/package.json | 2 +- packages/zeal/package.json | 2 +- yarn.lock | 38 +++++ 52 files changed, 191 insertions(+), 171 deletions(-) diff --git a/docs/yarn.lock b/docs/yarn.lock index 4a9cabe51..5eff4666d 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -3198,13 +3198,13 @@ "@walletconnect/types" "^1.8.0" "@walletconnect/utils" "^1.8.0" -"@walletconnect/core@2.4.10": - version "2.4.10" - resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.4.10.tgz#8975996b5c47d0d11a1187b3793215678c3ea3af" - integrity sha512-3ZVS07NS9+zG+Mw4MOxYhoJHwCSuIOrq+HuhaTLZZ+NswscZ+GwguF2fTsRNgk4jXkMJodaqUFxfPJeCVVcwHQ== +"@walletconnect/core@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.5.2.tgz#999605a62a3b37867d2559e40b62778534eb787c" + integrity sha512-R0D9NKgHBpdun65q+1L49GOIGDLaIodnyb+Dq0tXGVzvXzy2lkXOlh2e9am61ixaVrUsHt7b96b318geqsuk4Q== dependencies: "@walletconnect/heartbeat" "1.2.0" - "@walletconnect/jsonrpc-provider" "1.0.9" + "@walletconnect/jsonrpc-provider" "1.0.10" "@walletconnect/jsonrpc-utils" "^1.0.4" "@walletconnect/jsonrpc-ws-connection" "1.0.10" "@walletconnect/keyvaluestorage" "^1.0.2" @@ -3213,8 +3213,8 @@ "@walletconnect/relay-auth" "^1.0.4" "@walletconnect/safe-json" "^1.0.1" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.4.10" - "@walletconnect/utils" "2.4.10" + "@walletconnect/types" "2.5.2" + "@walletconnect/utils" "2.5.2" events "^3.3.0" lodash.isequal "4.5.0" pino "7.11.0" @@ -3257,20 +3257,19 @@ dependencies: tslib "1.14.1" -"@walletconnect/ethereum-provider@2.4.10": - version "2.4.10" - resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.4.10.tgz#450167cf3fbfb7813189076f13c0c61e324f8c25" - integrity sha512-anD3inbmuIZgWJ7Km5QP6L/hjI8sK/3Q4WA+g8OlyHS2cqO9sPjYdbPUVnzVgwhTDbNDJr2AUNXmvpm2R0hq5Q== +"@walletconnect/ethereum-provider@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.5.2.tgz#b70c01fc281ae8b6c424fc063bc48b476ef3f83a" + integrity sha512-WEN85tsuHgvoiMK4KpsRsOgsKB0QLCctSwxTqyWDybBbXuJRJGWXkZ6Oma9VSmUR0MgPSjiGmOFgY4ybMlhEMA== dependencies: "@walletconnect/jsonrpc-http-connection" "^1.0.4" "@walletconnect/jsonrpc-provider" "^1.0.6" "@walletconnect/jsonrpc-types" "^1.0.2" "@walletconnect/jsonrpc-utils" "^1.0.4" - "@walletconnect/sign-client" "2.4.10" - "@walletconnect/types" "2.4.10" - "@walletconnect/universal-provider" "2.4.10" - "@walletconnect/utils" "2.4.10" - "@web3modal/standalone" "^2.2.0" + "@walletconnect/sign-client" "2.5.2" + "@walletconnect/types" "2.5.2" + "@walletconnect/universal-provider" "2.5.2" + "@walletconnect/utils" "2.5.2" events "^3.3.0" "@walletconnect/events@^1.0.1": @@ -3312,10 +3311,10 @@ cross-fetch "^3.1.4" tslib "1.14.1" -"@walletconnect/jsonrpc-provider@1.0.9": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.9.tgz#ce5ab64dce6a739110aef204ffeedd668ad343d8" - integrity sha512-8CwmiDW42F+F8Qct13lX2x4lJOsi0mNBtUln3VS6TpWioTaL1VfforC/8ULc3tHXv+SNWwAXn2lCZbDcYhdRcA== +"@walletconnect/jsonrpc-provider@1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.10.tgz#8351a06b70faa8f8c0e77dc2c6d9b0190d17d407" + integrity sha512-g0ffPSpY3P6GqGjWGHsr3yqvQUhj7q2k6pAikoXv5XTXWaJRzFvrlbFkSgxziXsBrwrMZn0qvPufvpN4mMZ5FA== dependencies: "@walletconnect/jsonrpc-utils" "^1.0.6" "@walletconnect/safe-json" "^1.0.1" @@ -3449,19 +3448,19 @@ dependencies: tslib "1.14.1" -"@walletconnect/sign-client@2.4.10": - version "2.4.10" - resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.4.10.tgz#727072fcbf0c1f84c5370155f0feb7e711733ca4" - integrity sha512-8yNpRUVvkoFY5sdj7QbW1+g6QWgP8VLy1xVAqWkjLIiPieMA6IQcOpaEih9Bbq55oTOxjeWO9+E+V8/0bNXVvQ== +"@walletconnect/sign-client@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.5.2.tgz#d05df9dce271720fdb75741fb162dcc899e39029" + integrity sha512-eKUnGCVgYqN+6b4gm27ML/064m0c/2hTlTHy6tbUszYtEPTzb+q4fvpnWs6blaOjzc18l8NFwX3c1+MHxVdQUQ== dependencies: - "@walletconnect/core" "2.4.10" + "@walletconnect/core" "2.5.2" "@walletconnect/events" "^1.0.1" "@walletconnect/heartbeat" "1.2.0" "@walletconnect/jsonrpc-utils" "^1.0.4" "@walletconnect/logger" "^2.0.1" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.4.10" - "@walletconnect/utils" "2.4.10" + "@walletconnect/types" "2.5.2" + "@walletconnect/utils" "2.5.2" events "^3.3.0" pino "7.11.0" @@ -3481,18 +3480,6 @@ dependencies: tslib "1.14.1" -"@walletconnect/types@2.4.10": - version "2.4.10" - resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.4.10.tgz#7f85a761b9d65e192d2f510ce858383f19a340f7" - integrity sha512-AvT3ynXXDXty94SadbjGrqqQA8vB1g9AchHZOakCY/Cfo5etpUFG3PfubWMC1FKe2FPk020nLkc2ghjNxHGGtw== - dependencies: - "@walletconnect/events" "^1.0.1" - "@walletconnect/heartbeat" "1.2.0" - "@walletconnect/jsonrpc-types" "^1.0.2" - "@walletconnect/keyvaluestorage" "^1.0.2" - "@walletconnect/logger" "^2.0.1" - events "^3.3.0" - "@walletconnect/types@2.5.2", "@walletconnect/types@^2.5.2": version "2.5.2" resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.5.2.tgz#b2ad73f9e6e19a90fe372babc9ed461fe27098fe" @@ -3510,27 +3497,27 @@ resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.8.0.tgz#3f5e85b2d6b149337f727ab8a71b8471d8d9a195" integrity sha512-Cn+3I0V0vT9ghMuzh1KzZvCkiAxTq+1TR2eSqw5E5AVWfmCtECFkVZBP6uUJZ8YjwLqXheI+rnjqPy7sVM4Fyg== -"@walletconnect/universal-provider@2.4.10": - version "2.4.10" - resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.4.10.tgz#24696a2b9267d18a8a84008c5b42c8d5b917e4f0" - integrity sha512-KbRl3ivDGXtycp1qs/p8RvXz0f8VgG8k+NKpD6x9/ibnuuFLRt6UgriLHHCslJ9SSGuGHAeIaWs+kRRDKDNFXQ== +"@walletconnect/universal-provider@2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.5.2.tgz#f0ec21be16cde5c42f2dc87630add01d2e01acbb" + integrity sha512-R61VL02zvcljwSC+FJVzxGswbN21tokQLG0IQL1tVq30+KfkZOt0y/UxsDNvgHNGleGgfoQZzOWsfSLgp5pcBQ== dependencies: "@walletconnect/jsonrpc-http-connection" "^1.0.4" "@walletconnect/jsonrpc-provider" "^1.0.6" "@walletconnect/jsonrpc-types" "^1.0.2" "@walletconnect/jsonrpc-utils" "^1.0.4" "@walletconnect/logger" "^2.0.1" - "@walletconnect/sign-client" "2.4.10" - "@walletconnect/types" "2.4.10" - "@walletconnect/utils" "2.4.10" + "@walletconnect/sign-client" "2.5.2" + "@walletconnect/types" "2.5.2" + "@walletconnect/utils" "2.5.2" eip1193-provider "1.0.1" events "^3.3.0" pino "7.11.0" -"@walletconnect/utils@2.4.10": - version "2.4.10" - resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.4.10.tgz#1fbae7973008c06209ccf79797f732e44e97ac1c" - integrity sha512-mg01uaGY+DoT5yMVb7eL9zXdXZLRfkz85b63URa6QyfWD0Jbstmviutc5NU2YzzbIuekT3miL4cwPvi0MRklWA== +"@walletconnect/utils@2.5.2", "@walletconnect/utils@^2.5.2": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.5.2.tgz#2ee0a10ea646f3e33e192de4b087a846e03b839f" + integrity sha512-s5bpY5q/RaXMc6LgPp+E7qPbKhrff9TjrLRjN2m9COnt9cERowpQEFrPzWmh10FatRZ7dNrudJ5I/c36nFc+hw== dependencies: "@stablelib/chacha20poly1305" "1.0.1" "@stablelib/hkdf" "1.0.1" @@ -3541,7 +3528,7 @@ "@walletconnect/relay-api" "^1.0.9" "@walletconnect/safe-json" "^1.0.1" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.4.10" + "@walletconnect/types" "2.5.2" "@walletconnect/window-getters" "^1.0.1" "@walletconnect/window-metadata" "^1.0.1" detect-browser "5.3.0" @@ -3561,27 +3548,6 @@ js-sha3 "0.8.0" query-string "6.13.5" -"@walletconnect/utils@^2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.5.2.tgz#2ee0a10ea646f3e33e192de4b087a846e03b839f" - integrity sha512-s5bpY5q/RaXMc6LgPp+E7qPbKhrff9TjrLRjN2m9COnt9cERowpQEFrPzWmh10FatRZ7dNrudJ5I/c36nFc+hw== - dependencies: - "@stablelib/chacha20poly1305" "1.0.1" - "@stablelib/hkdf" "1.0.1" - "@stablelib/random" "^1.0.2" - "@stablelib/sha256" "1.0.1" - "@stablelib/x25519" "^1.0.3" - "@walletconnect/jsonrpc-utils" "^1.0.4" - "@walletconnect/relay-api" "^1.0.9" - "@walletconnect/safe-json" "^1.0.1" - "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.5.2" - "@walletconnect/window-getters" "^1.0.1" - "@walletconnect/window-metadata" "^1.0.1" - detect-browser "5.3.0" - query-string "7.1.1" - uint8arrays "^3.1.0" - "@walletconnect/window-getters@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@walletconnect/window-getters/-/window-getters-1.0.0.tgz#1053224f77e725dfd611c83931b5f6c98c32bfc8" @@ -3626,10 +3592,10 @@ ethers "5.5.4" joi "17.8.1" -"@web3-onboard/core@^2.16.0": - version "2.16.0" - resolved "https://registry.yarnpkg.com/@web3-onboard/core/-/core-2.16.0.tgz#4eabdf66c66763bc6eeaabc97f2ea1c3639984eb" - integrity sha512-VyDK4qYo18zXTl/6mEFtGknRVtP4M4u+o99xJJLXCBr1eT49SUQhTrVUqTJ1lO5nA/j67qyPGojnf7dL/tAUiA== +"@web3-onboard/core@^2.16.1-alpha.2": + version "2.16.1-alpha.2" + resolved "https://registry.yarnpkg.com/@web3-onboard/core/-/core-2.16.1-alpha.2.tgz#6679e9bb0bce303fb9649a0f8bb2e50ee182c0c2" + integrity sha512-ZeHYGFmSPctYcV0vP3t4p2L8LtTGUMWYxlBBmzpA5Xd+F7ZIPCJChTpEkrzw0UzV5qJ1MLybVv4oP4En9iew7Q== dependencies: "@unstoppabledomains/resolution" "^8.0" "@web3-onboard/common" "^2.3.0" @@ -3850,16 +3816,17 @@ joi "17.8.1" rxjs "^7.5.2" -"@web3-onboard/walletconnect@^2.3.3": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@web3-onboard/walletconnect/-/walletconnect-2.3.3.tgz#5bdcf67a2fbbe0a9823ea860e7d3954b8aba09b3" - integrity sha512-Dyn/yGfrd/xq+BlIypTjqlu6hcKXeny3WxCG5p1l+hqBsVx1d279CdJUYdeDLlkZ6SAOR+9tfuPj2EyxSuU2MA== +"@web3-onboard/walletconnect@^2.3.4-alpha.1": + version "2.3.4-alpha.2" + resolved "https://registry.yarnpkg.com/@web3-onboard/walletconnect/-/walletconnect-2.3.4-alpha.2.tgz#ce9d6e106f2ed4d5faa43d44796e23e56abf7d3a" + integrity sha512-pz7scUS9275YJYk/maWGKHtF3Xib662WXa39qaziMNsiPjyAn6Ngl4wDG/LFg9UwC2+1xcAaAW/MZS1LeaDCCQ== dependencies: "@ethersproject/providers" "^5.5.0" "@walletconnect/client" "^1.8.0" - "@walletconnect/ethereum-provider" "2.4.10" + "@walletconnect/ethereum-provider" "2.5.2" "@walletconnect/qrcode-modal" "^1.8.0" "@web3-onboard/common" "^2.3.0" + "@web3modal/standalone" "^2.2.2" rxjs "^7.5.2" "@web3-onboard/web3auth@^2.2.0": @@ -4080,28 +4047,28 @@ "@web3auth/base-evm-adapter" "^5.1.0" "@web3auth/ethereum-provider" "^5.1.0" -"@web3modal/core@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@web3modal/core/-/core-2.2.0.tgz#847459e1ab1766f312c39715270a7ca0fe831666" - integrity sha512-Kafg/KtK6S9x0Ofcaq9hj7dRK5/541nM+LnayPmHxx4fSrDgcM9YYhL12fI4BG1xGOJwkeZjgFOtS0qf123Cjw== +"@web3modal/core@2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@web3modal/core/-/core-2.2.2.tgz#1e282dc45bddb11c04f1c93abce570bac1b9a620" + integrity sha512-RKbYNIEVP5Hwiva68PWXExbkTFLUTasneyRpcjoQSM4BIh78qXp1YMt0nyTvFdHmHQEGxXEMCuRG5qoE97uMHA== dependencies: buffer "6.0.3" - valtio "1.9.0" + valtio "1.10.3" -"@web3modal/standalone@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@web3modal/standalone/-/standalone-2.2.0.tgz#9f81ed976dd16bd795f902503065f75c9160eab3" - integrity sha512-cLFW4VamSJ7L4sM5OGmr1SHK3FgyLUMEaacvHsCA3XSvUF0LxbMC+N4uBsONrW4c0JyIjTdeii1GqG4B3jwn7Q== +"@web3modal/standalone@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@web3modal/standalone/-/standalone-2.2.2.tgz#2d5ce74bbb7f112b31da32049620afa75c4a4686" + integrity sha512-c05kkTFNGZqnjJ3n2C8uo+wWL6ut1jexGYAyTvbweDengdsOr8LDo0VpK5V3XSKCV2fFcPh5JE9H1aA4jpnZPg== dependencies: - "@web3modal/core" "2.2.0" - "@web3modal/ui" "2.2.0" + "@web3modal/core" "2.2.2" + "@web3modal/ui" "2.2.2" -"@web3modal/ui@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@web3modal/ui/-/ui-2.2.0.tgz#2837da46706f1a3fcdf2f2c22e7ed029e235a3ab" - integrity sha512-jcV5C9AuMdsFdf6Ljsr0v2lInu8FJJyXcZPaMHkgYNIczzgMEpDE+UOA7hLnyCTUxM9R0AgRcgfTyMWb9H8Ssw== +"@web3modal/ui@2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@web3modal/ui/-/ui-2.2.2.tgz#f1c1ac908230d4214c35891a5d922c118353eaf2" + integrity sha512-PAuMOuk4sZ4UGjucGMZKzu6Qu56XtFsgLaqOn8ZgP2RkZmYEBGSG9mUQVzJd3XzfzAy1T91Wmqp/3TI3m0pXuQ== dependencies: - "@web3modal/core" "2.2.0" + "@web3modal/core" "2.2.2" lit "2.6.1" motion "10.15.5" qrcode "1.5.1" @@ -8769,10 +8736,10 @@ protocol-buffers-schema@3.1.0: resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.1.0.tgz#d8a819549ead3e6bd189ebe9e50e96636bbc5cc7" integrity sha512-1g9zFjLFhGN1Dc5UVO8D2loVslp6sVxk5sJqgD66CuWUITh2gOaTLRN/pIakGFfB6e0nNF6hImrYFDurEsA1UQ== -proxy-compare@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.4.0.tgz#90f6abffe734ef86d8e37428c5026268606a9c1b" - integrity sha512-FD8KmQUQD6Mfpd0hywCOzcon/dbkFP8XBd9F1ycbKtvVsfv6TsFUKJ2eC0Iz2y+KzlkdT1Z8SY6ZSgm07zOyqg== +proxy-compare@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.5.0.tgz#0387c5e4d283ba9b1c0353bb20def4449b06bbd2" + integrity sha512-f1us0OsVAJ3tdIMXGQx2lmseYS4YXe4W+sKF5g5ww/jV+5ogMadPt+sIZ+88Ga9kvMJsrRNWzCrKPpr6pMWYbA== prr@~1.0.1: version "1.0.1" @@ -10234,12 +10201,12 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -valtio@1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.9.0.tgz#d5d9f664319eaf18dd98f758d50495eca28eb0b8" - integrity sha512-mQLFsAlKbYascZygFQh6lXuDjU5WHLoeZ8He4HqMnWfasM96V6rDbeFkw1XeG54xycmDonr/Jb4xgviHtuySrA== +valtio@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.10.3.tgz#273eda9ba6459869798b4f58c84514e18fb80ed8" + integrity sha512-t3Ez/+baJ+Z5tIyeaI6nCAbW/hrmcq2jditwg/X++o5IvCdiGirQKTOv1kJq0glgUo13v5oABCVGcinggBfiKw== dependencies: - proxy-compare "2.4.0" + proxy-compare "2.5.0" use-sync-external-store "1.2.0" varint@5.0.0: @@ -10809,4 +10776,4 @@ yn@3.1.1: yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== \ No newline at end of file + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/packages/coinbase/package.json b/packages/coinbase/package.json index 0a08e4079..b50fc679f 100644 --- a/packages/coinbase/package.json +++ b/packages/coinbase/package.json @@ -59,6 +59,6 @@ }, "dependencies": { "@coinbase/wallet-sdk": "^3.6.0", - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/common/package.json b/packages/common/package.json index da7db7cf6..e5fdef107 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/common", - "version": "2.3.0", + "version": "2.4.0-alpha.1", "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 5e46b5eb8..df00be2f2 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,7 +1,7 @@ export { ProviderRpcError } from './errors.js' export { createEIP1193Provider } from './eip-1193.js' export { InterVar } from './fonts.js' -export { weiToEth } from './utils.js' +export { weiToEth, isAddress } from './utils.js' export * from './types.js' export * from './validation.js' diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 6bcbb5390..4f74e84fd 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -219,7 +219,7 @@ export interface ProviderInfo { chainId: ChainId } -export type AccountAddress = string +export type AccountAddress = Address /** * An array of addresses @@ -296,7 +296,7 @@ export interface EthSignTransactionRequest { params: [TransactionObject] } -type Address = string +export type Address = `0x${string}` type Message = string export interface EthSignMessageRequest { method: 'eth_sign' diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 3d11abf68..3e597b416 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -1,4 +1,11 @@ import Bignumber from 'bignumber.js' +import type { Address } from 'types' + +const addressRegex = /^0x[a-fA-F0-9]{40}$/ + +export function isAddress(address: string): address is Address { + return addressRegex.test(address) +} export function weiToEth(wei: string): string { return new Bignumber(wei).div(1e18).toString(10) diff --git a/packages/core/package.json b/packages/core/package.json index a5bf770a4..50d58845d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/core", - "version": "2.16.1-alpha.2", + "version": "2.17.0-alpha.1", "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardized spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", @@ -85,7 +85,7 @@ }, "dependencies": { "@unstoppabledomains/resolution": "^8.0", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "bignumber.js": "^9.0.0", "bnc-sdk": "^4.6.7", "bowser": "^2.11.0", diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index 623146783..5facf8856 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -1,7 +1,8 @@ import { fromEventPattern, Observable } from 'rxjs' import { filter, takeUntil, take, share, switchMap } from 'rxjs/operators' import partition from 'lodash.partition' -import { providers, utils } from 'ethers' +import { providers } from 'ethers' +import { isAddress } from '@web3-onboard/common' import { weiToEth } from '@web3-onboard/common' import { disconnectWallet$ } from './streams.js' import { updateAccount, updateWallet } from './store/actions.js' @@ -11,6 +12,7 @@ import { state } from './store/index.js' import { getBNMulitChainSdk } from './services.js' import type { + Address, ChainId, EIP1102Request, EIP1193Provider, @@ -23,7 +25,6 @@ import type { import type { Account, - Address, Balances, Ens, Uns, @@ -151,7 +152,7 @@ export function trackWallet( updateWallet(label, { accounts: [ existingAccount || { - address: address, + address: address as Address, ens: null, uns: null, balance: null @@ -387,7 +388,7 @@ export async function getUns( // check if address is valid ETH address before attempting to resolve // chain we don't recognize and don't have a rpcUrl for requests - if (connect.disableUDResolution || !utils.isAddress(address) || !chain) + if (connect.disableUDResolution || !isAddress(address) || !chain) return null try { @@ -411,7 +412,7 @@ export async function getUns( } export async function getBalance( - address: string, + address: Address, chain: Chain ): Promise { // chain we don't recognize and don't have a rpcUrl for requests diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index 1f3f892f5..5c04bca83 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -65,6 +65,7 @@ import { UPDATE_CONNECT_MODAL, UPDATE_CHAINS } from './constants.js' +import type { Address } from 'bnc-sdk' export function addChains(chains: Chain[]): void { // chains are validated on init @@ -181,7 +182,7 @@ export function setPrimaryWallet(wallet: WalletState, address?: string): void { export function updateAccount( id: string, - address: string, + address: Address, update: Partial ): void { const action = { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8d5408d7b..9aa736a2d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -2,6 +2,7 @@ import type { SvelteComponent } from 'svelte' import type { AppMetadata, + Address, Device, WalletInit, EIP1193Provider, @@ -154,8 +155,6 @@ export type Avatar = { linkage: Array<{ type: string; content: string }> } -export type Address = string - export interface AppState { chains: Chain[] walletModules: WalletModule[] @@ -414,7 +413,7 @@ export type ResetStoreAction = { export type UpdateAccountAction = { type: 'update_account' - payload: { id: string; address: string } & Partial + payload: { id: string; address: Address } & Partial } export type UpdateAccountCenterAction = { diff --git a/packages/dcent/package.json b/packages/dcent/package.json index 2de612b3a..73687c01a 100644 --- a/packages/dcent/package.json +++ b/packages/dcent/package.json @@ -56,7 +56,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "@web3-onboard/hw-common": "^2.2.0", "@ethereumjs/tx": "^3.4.0", "@ethersproject/providers": "^5.5.0", diff --git a/packages/demo/package.json b/packages/demo/package.json index d4a146d53..e5e7ebe29 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -23,7 +23,7 @@ "webpack-dev-server": "4.7.4" }, "dependencies": { - "@web3-onboard/core": "^2.16.1-alpha.2", + "@web3-onboard/core": "^2.17.0-alpha.1", "@web3-onboard/coinbase": "^2.2.1", "@web3-onboard/transaction-preview": "^2.0.5", "@web3-onboard/dcent": "^2.2.4", diff --git a/packages/enkrypt/package.json b/packages/enkrypt/package.json index ad56aef14..b69adad90 100644 --- a/packages/enkrypt/package.json +++ b/packages/enkrypt/package.json @@ -63,6 +63,6 @@ "window": "^4.2.7" }, "dependencies": { - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/fortmatic/package.json b/packages/fortmatic/package.json index b1adf6605..d1fa4c346 100644 --- a/packages/fortmatic/package.json +++ b/packages/fortmatic/package.json @@ -58,7 +58,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "fortmatic": "^2.2.1" } } diff --git a/packages/fortmatic/src/index.ts b/packages/fortmatic/src/index.ts index 8cce5d88d..fc8c5cdcc 100644 --- a/packages/fortmatic/src/index.ts +++ b/packages/fortmatic/src/index.ts @@ -1,4 +1,10 @@ -import type { WalletInit, APIKey, EIP1193Provider } from '@web3-onboard/common' +import type { + WalletInit, + APIKey, + EIP1193Provider, + Address, + ProviderAccounts +} from '@web3-onboard/common' function fortmatic(options: APIKey): WalletInit { const { apiKey } = options @@ -29,7 +35,8 @@ function fortmatic(options: APIKey): WalletInit { const patchedProvider = createEIP1193Provider(fortmaticProvider, { eth_requestAccounts: async () => { try { - const accounts = await instance.user.login() + const accounts = + (await instance.user.login()) as ProviderAccounts return accounts } catch (error) { const { code } = error as { code: number } diff --git a/packages/frontier/package.json b/packages/frontier/package.json index e9fa8c673..55684fa30 100644 --- a/packages/frontier/package.json +++ b/packages/frontier/package.json @@ -59,6 +59,6 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } \ No newline at end of file diff --git a/packages/gas/package.json b/packages/gas/package.json index f39993a93..18186a6db 100644 --- a/packages/gas/package.json +++ b/packages/gas/package.json @@ -32,7 +32,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "rxjs": "^7.5.2", "joi": "17.8.1" } diff --git a/packages/gnosis/package.json b/packages/gnosis/package.json index 1c182daa8..37143e48d 100644 --- a/packages/gnosis/package.json +++ b/packages/gnosis/package.json @@ -59,6 +59,6 @@ "dependencies": { "@gnosis.pm/safe-apps-provider": "^0.9.2", "@gnosis.pm/safe-apps-sdk": "^6.1.1", - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/hw-common/package.json b/packages/hw-common/package.json index 3d957ff0a..9bfb51d4c 100644 --- a/packages/hw-common/package.json +++ b/packages/hw-common/package.json @@ -81,7 +81,7 @@ }, "dependencies": { "@ethereumjs/common": "2.6.2", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "ethers": "5.5.4", "joi": "17.8.1", "rxjs": "^7.5.2" diff --git a/packages/infinity-wallet/package.json b/packages/infinity-wallet/package.json index e5aa86ab2..2510ab6af 100644 --- a/packages/infinity-wallet/package.json +++ b/packages/infinity-wallet/package.json @@ -60,7 +60,7 @@ "devDependencies": { "@types/node": "^17.0.21", "typescript": "^4.5.5", - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" }, "dependencies": { "@infinitywallet/infinity-connector": "^1.0.6" diff --git a/packages/injected/package.json b/packages/injected/package.json index 5015d2562..8afa7607f 100644 --- a/packages/injected/package.json +++ b/packages/injected/package.json @@ -1,6 +1,6 @@ { "name": "@web3-onboard/injected-wallets", - "version": "2.8.3", + "version": "2.4.3-alpha.1", "description": "Injected wallet module for connecting browser extension and mobile wallets to Web3-Onboard. Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", "keywords": [ "Ethereum", @@ -64,7 +64,7 @@ "window": "^4.2.7" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "joi": "17.8.1", "lodash.uniqby": "^4.7.0" } diff --git a/packages/keepkey/package.json b/packages/keepkey/package.json index e4341dfdd..4aeaae9e2 100644 --- a/packages/keepkey/package.json +++ b/packages/keepkey/package.json @@ -63,7 +63,7 @@ "@ethersproject/providers": "^5.5.0", "@shapeshiftoss/hdwallet-core": "^1.15.2", "@shapeshiftoss/hdwallet-keepkey-webusb": "^1.15.2", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "@web3-onboard/hw-common": "^2.2.0", "ethereumjs-util": "^7.1.3" } diff --git a/packages/keepkey/src/index.ts b/packages/keepkey/src/index.ts index cfb06086e..64231b382 100644 --- a/packages/keepkey/src/index.ts +++ b/packages/keepkey/src/index.ts @@ -198,9 +198,9 @@ function keepkey({ acc.balance.value.isZero() ) { zeroBalanceAccounts++ - accounts.push(acc) + accounts.push(acc as Account) } else { - accounts.push(acc) + accounts.push(acc as Account) // Reset the number of 0 balance accounts zeroBalanceAccounts = 0 } @@ -239,7 +239,7 @@ function keepkey({ asset }) - return [account] + return [account as Account] } catch (error) { throw new Error('Invalid derivation path') } diff --git a/packages/keystone/package.json b/packages/keystone/package.json index bf1f904a7..fa8aa7dab 100644 --- a/packages/keystone/package.json +++ b/packages/keystone/package.json @@ -58,7 +58,7 @@ "@ethereumjs/tx": "^3.4.0", "@ethersproject/providers": "^5.5.0", "@keystonehq/eth-keyring": "^0.14.00.3", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "@web3-onboard/hw-common": "^2.2.0" } } diff --git a/packages/ledger/package.json b/packages/ledger/package.json index 6430dce77..0227ba2a9 100644 --- a/packages/ledger/package.json +++ b/packages/ledger/package.json @@ -61,7 +61,7 @@ "@ethersproject/providers": "^5.5.0", "@ledgerhq/connect-kit-loader": "^1.0.2", "@walletconnect/client": "^1.7.1", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "rxjs": "^7.5.2" } } diff --git a/packages/ledger/src/index.ts b/packages/ledger/src/index.ts index e39f951ba..9aac1d297 100644 --- a/packages/ledger/src/index.ts +++ b/packages/ledger/src/index.ts @@ -161,7 +161,7 @@ function ledger(options?: LedgerOptions): WalletInit { : `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) - return resolve(accounts) + return resolve(accounts as ProviderAccounts) } // Subscribe to connection events diff --git a/packages/magic/package.json b/packages/magic/package.json index 912cc6b83..50e9d9b4e 100644 --- a/packages/magic/package.json +++ b/packages/magic/package.json @@ -80,7 +80,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "joi": "17.8.1", "magic-sdk": "^8.1.0", "rxjs": "^7.5.2" diff --git a/packages/mew-wallet/package.json b/packages/mew-wallet/package.json index 70b264feb..f75168214 100644 --- a/packages/mew-wallet/package.json +++ b/packages/mew-wallet/package.json @@ -64,7 +64,7 @@ "window": "^4.2.7" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "lodash.uniqby": "^4.7.0" } } diff --git a/packages/mew/package.json b/packages/mew/package.json index cd2d26d2d..4c5a0db8a 100644 --- a/packages/mew/package.json +++ b/packages/mew/package.json @@ -59,7 +59,7 @@ "@myetherwallet/mewconnect-web-client": "^2.2.0-beta.14" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "rxjs": "^7.5.2" } } diff --git a/packages/phantom/package.json b/packages/phantom/package.json index 74cbbd509..61af8e42d 100644 --- a/packages/phantom/package.json +++ b/packages/phantom/package.json @@ -59,6 +59,6 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/portis/package.json b/packages/portis/package.json index 85b163215..5121c3fab 100644 --- a/packages/portis/package.json +++ b/packages/portis/package.json @@ -57,6 +57,6 @@ }, "dependencies": { "@portis/web3": "^4.0.6", - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/react/package.json b/packages/react/package.json index 78a20f854..11f3f8304 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -63,7 +63,7 @@ }, "dependencies": { "@web3-onboard/core": "^2.16.1-alpha.2", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "use-sync-external-store": "1.0.0" }, "peerDependencies": { diff --git a/packages/sequence/package.json b/packages/sequence/package.json index d06d90b64..afd1032f2 100644 --- a/packages/sequence/package.json +++ b/packages/sequence/package.json @@ -56,7 +56,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "0xsequence": "^0.43.1" }, "peerDependencies": { diff --git a/packages/sequence/src/index.ts b/packages/sequence/src/index.ts index 8debd9bbb..cdc54ac45 100644 --- a/packages/sequence/src/index.ts +++ b/packages/sequence/src/index.ts @@ -1,4 +1,4 @@ -import type { WalletInit } from '@web3-onboard/common' +import type { ProviderAccounts, WalletInit } from '@web3-onboard/common' interface SequenceOptions { appName?: string @@ -40,7 +40,7 @@ function sequence(options?: SequenceOptions): WalletInit { const provider = createEIP1193Provider(sequenceProvider, { eth_requestAccounts: async () => { const address = await instance.getAddress() - return [address] + return [address] as ProviderAccounts }, eth_chainId: async () => { const chainId = await instance.getChainId() diff --git a/packages/taho/package.json b/packages/taho/package.json index 1e9559b46..17060e500 100644 --- a/packages/taho/package.json +++ b/packages/taho/package.json @@ -66,7 +66,7 @@ "window": "^4.2.7" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "tallyho-detect-provider": "^1.0.0", "tallyho-onboarding": "^1.0.2" } diff --git a/packages/tallyho/package.json b/packages/tallyho/package.json index aec4fdbc2..10c7aa31f 100644 --- a/packages/tallyho/package.json +++ b/packages/tallyho/package.json @@ -64,7 +64,7 @@ "window": "^4.2.7" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "tallyho-detect-provider": "^1.0.0", "tallyho-onboarding": "^1.0.2" } diff --git a/packages/torus/package.json b/packages/torus/package.json index 878f2a53c..b93166021 100644 --- a/packages/torus/package.json +++ b/packages/torus/package.json @@ -58,6 +58,6 @@ }, "dependencies": { "@toruslabs/torus-embed": "1.38.2", - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/torus/src/index.ts b/packages/torus/src/index.ts index defcef94e..f3a3309f6 100644 --- a/packages/torus/src/index.ts +++ b/packages/torus/src/index.ts @@ -1,4 +1,4 @@ -import type { WalletInit } from '@web3-onboard/common' +import type { ProviderAccounts, WalletInit } from '@web3-onboard/common' import type { TorusCtorArgs, TorusParams } from '@toruslabs/torus-embed' type TorusOptions = TorusCtorArgs & TorusParams @@ -59,7 +59,7 @@ function torus(options?: TorusOptions): WalletInit { eth_requestAccounts: async () => { try { const accounts = await instance.login() - return accounts + return accounts as ProviderAccounts } catch (error) { throw new ProviderRpcError({ code: ProviderRpcErrorCode.ACCOUNT_ACCESS_REJECTED, diff --git a/packages/transaction-preview/package.json b/packages/transaction-preview/package.json index 7881e911a..480143b14 100644 --- a/packages/transaction-preview/package.json +++ b/packages/transaction-preview/package.json @@ -80,7 +80,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "bnc-sdk": "^4.6.7", "bowser": "^2.11.0", "joi": "17.8.1", diff --git a/packages/trezor/package.json b/packages/trezor/package.json index 9a05eb633..d33af8967 100644 --- a/packages/trezor/package.json +++ b/packages/trezor/package.json @@ -60,7 +60,7 @@ "dependencies": { "@ethereumjs/tx": "^3.4.0", "@ethersproject/providers": "^5.5.0", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "@web3-onboard/hw-common": "^2.2.0", "buffer": "^6.0.3", "eth-crypto": "^2.1.0", diff --git a/packages/trust/package.json b/packages/trust/package.json index cc0aaa3a1..e5b8bdc38 100644 --- a/packages/trust/package.json +++ b/packages/trust/package.json @@ -61,6 +61,6 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/uauth/package.json b/packages/uauth/package.json index 2cb825ce6..398476ee4 100644 --- a/packages/uauth/package.json +++ b/packages/uauth/package.json @@ -65,7 +65,7 @@ "@ethersproject/providers": "^5.5.0", "@walletconnect/client": "^1.7.1", "@walletconnect/qrcode-modal": "^1.7.1", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "joi": "17.8.1", "rxjs": "^7.5.2" } diff --git a/packages/uauth/src/index.ts b/packages/uauth/src/index.ts index 51eb0b30f..0fa20e5a1 100644 --- a/packages/uauth/src/index.ts +++ b/packages/uauth/src/index.ts @@ -233,7 +233,7 @@ function uauth(options: UauthInitOptions): WalletInit { ? chainId : `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) - return resolve(accounts) + return resolve(accounts as ProviderAccounts) } // @ts-ignore Subscribe to connection events diff --git a/packages/vue/package.json b/packages/vue/package.json index 433040010..f8e6bc54c 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -62,7 +62,7 @@ "dependencies": { "@vueuse/core": "^8.4.2", "@vueuse/rxjs": "^8.2.0", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "@web3-onboard/core": "^2.16.1-alpha.2", "vue-demi": "^0.12.4" }, diff --git a/packages/walletconnect/package.json b/packages/walletconnect/package.json index 424e37a48..a0c607ba1 100644 --- a/packages/walletconnect/package.json +++ b/packages/walletconnect/package.json @@ -65,7 +65,7 @@ "@walletconnect/client": "^1.8.0", "@walletconnect/qrcode-modal": "^1.8.0", "@web3modal/standalone":"^2.2.2", - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "rxjs": "^7.5.2" } } diff --git a/packages/walletconnect/src/v1.ts b/packages/walletconnect/src/v1.ts index 5acb3ebbe..81388038c 100644 --- a/packages/walletconnect/src/v1.ts +++ b/packages/walletconnect/src/v1.ts @@ -166,7 +166,7 @@ function walletConnect( ? chainId : `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) - return resolve(accounts) + return resolve(accounts as ProviderAccounts) } // Subscribe to connection events diff --git a/packages/walletconnect/src/v2.ts b/packages/walletconnect/src/v2.ts index bffcab7b6..aa76a4dc5 100644 --- a/packages/walletconnect/src/v2.ts +++ b/packages/walletconnect/src/v2.ts @@ -216,7 +216,7 @@ function walletConnect(options?: WalletConnectOptions): WalletInit { ? chainId : `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) - resolve(this.connector.accounts) + resolve(this.connector.accounts as ProviderAccounts) }, error: reject }) @@ -239,7 +239,7 @@ function walletConnect(options?: WalletConnectOptions): WalletInit { const chainId = this.connector.chainId const hexChainId = `0x${chainId.toString(16)}` this.emit('chainChanged', hexChainId) - return resolve(accounts) + return resolve(accounts as ProviderAccounts) } } ) diff --git a/packages/walletlink/package.json b/packages/walletlink/package.json index 516889c61..6e545c920 100644 --- a/packages/walletlink/package.json +++ b/packages/walletlink/package.json @@ -58,7 +58,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "walletlink": "^2.5.0" } } diff --git a/packages/web3auth/package.json b/packages/web3auth/package.json index 679962315..25c02be79 100644 --- a/packages/web3auth/package.json +++ b/packages/web3auth/package.json @@ -57,7 +57,7 @@ "typescript": "^4.5.5" }, "dependencies": { - "@web3-onboard/common": "^2.3.0", + "@web3-onboard/common": "^2.4.0-alpha.1", "@solana/web3.js": "^1.73.0", "@web3auth/base": "^5.0.1", "@web3auth/modal": "^5.0.1", diff --git a/packages/xdefi/package.json b/packages/xdefi/package.json index 8fd8ca3d8..077fe37de 100644 --- a/packages/xdefi/package.json +++ b/packages/xdefi/package.json @@ -64,6 +64,6 @@ "window": "^4.2.7" }, "dependencies": { - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/packages/zeal/package.json b/packages/zeal/package.json index 2f762894d..78bcf4881 100644 --- a/packages/zeal/package.json +++ b/packages/zeal/package.json @@ -54,6 +54,6 @@ "window": "^4.2.7" }, "dependencies": { - "@web3-onboard/common": "^2.3.0" + "@web3-onboard/common": "^2.4.0-alpha.1" } } diff --git a/yarn.lock b/yarn.lock index a0012765b..b216a41db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3439,6 +3439,44 @@ "@walletconnect/window-getters" "^1.0.1" tslib "1.14.1" +"@web3-onboard/common@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@web3-onboard/common/-/common-2.3.0.tgz#22a2314e9e6d58494629c4e6ca35d9ba37f3dac3" + integrity sha512-wFVJ1Sw2dRSRbAwJLmHs9eloRBTfC2N7V2IdKEf8dN1oRhsVgkSYeMYR3Vv27YzjXmzyZUdlEux7Fy2JJqbQCg== + dependencies: + bignumber.js "^9.1.0" + ethers "5.5.4" + joi "17.8.1" + +"@web3-onboard/core@^2.16.1-alpha.2": + version "2.16.1-alpha.2" + resolved "https://registry.yarnpkg.com/@web3-onboard/core/-/core-2.16.1-alpha.2.tgz#6679e9bb0bce303fb9649a0f8bb2e50ee182c0c2" + integrity sha512-ZeHYGFmSPctYcV0vP3t4p2L8LtTGUMWYxlBBmzpA5Xd+F7ZIPCJChTpEkrzw0UzV5qJ1MLybVv4oP4En9iew7Q== + dependencies: + "@unstoppabledomains/resolution" "^8.0" + "@web3-onboard/common" "^2.3.0" + bignumber.js "^9.0.0" + bnc-sdk "^4.6.7" + bowser "^2.11.0" + ethers "5.5.3" + eventemitter3 "^4.0.7" + joi "17.8.1" + lodash.merge "^4.6.2" + lodash.partition "^4.6.0" + nanoid "^4.0.0" + rxjs "^7.5.5" + svelte "^3.49.0" + svelte-i18n "^3.3.13" + +"@web3-onboard/injected-wallets@^2.8.3": + version "2.8.3" + resolved "https://registry.yarnpkg.com/@web3-onboard/injected-wallets/-/injected-wallets-2.8.3.tgz#6614fcebc1c128ccbfb4c4b6cee96ac654fbb402" + integrity sha512-0pi6b4TxKAjJ9edwAF56nl8Bz16c3FvVw2sElBs/lx8+yZIzoc2XMjPlOFFFOTkR8mWNg5vKzDqKN6Xu8lxWTQ== + dependencies: + "@web3-onboard/common" "^2.3.0" + joi "17.8.1" + lodash.uniqby "^4.7.0" + "@web3-react/abstract-connector@^6.0.7": version "6.0.7" resolved "https://registry.yarnpkg.com/@web3-react/abstract-connector/-/abstract-connector-6.0.7.tgz#401b3c045f1e0fab04256311be49d5144e9badc6" From a279c87992f658fd5d0ad5d110a2b400e20ee716 Mon Sep 17 00:00:00 2001 From: Adam Carpenter Date: Tue, 4 Apr 2023 11:32:21 -0600 Subject: [PATCH 002/125] Replace Ethers BigNumber usage with browser native BigInt --- packages/common/src/types.ts | 2 -- packages/core/src/replacement.ts | 3 +-- packages/core/src/views/connect/Index.svelte | 2 -- packages/fortmatic/src/index.ts | 10 +++++----- packages/magic/src/index.ts | 4 ++-- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 4f74e84fd..6d64bd9c8 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -1,7 +1,6 @@ import type { ConnectionInfo } from 'ethers/lib/utils' import type EventEmitter from 'eventemitter3' import type { TypedData as EIP712TypedData } from 'eip-712' -import type { ethers } from 'ethers' export type { TypedData as EIP712TypedData } from 'eip-712' /** @@ -189,7 +188,6 @@ export interface WalletModule { export type GetInterfaceHelpers = { chains: Chain[] appMetadata: AppMetadata | null - BigNumber: typeof ethers.BigNumber EventEmitter: typeof EventEmitter } diff --git a/packages/core/src/replacement.ts b/packages/core/src/replacement.ts index 378e452f5..66b0daa56 100644 --- a/packages/core/src/replacement.ts +++ b/packages/core/src/replacement.ts @@ -1,5 +1,4 @@ import type { EthereumTransactionData, Network } from 'bnc-sdk' -import { BigNumber } from 'ethers' import { configuration } from './configuration.js' import { state } from './store/index.js' import type { WalletState } from './types.js' @@ -71,7 +70,7 @@ export async function replaceTransaction({ from, to: type === 'cancel' ? from : to, chainId: parseInt(chainId), - value: `${BigNumber.from(value).toHexString()}`, + value: `0x${BigInt(value).toString(16)}`, nonce: toHexString(nonce), gasLimit: toHexString(gasLimit), maxFeePerGas: maxFeePerGasWeiHex, diff --git a/packages/core/src/views/connect/Index.svelte b/packages/core/src/views/connect/Index.svelte index e7fe0035f..5c4b4c6f4 100644 --- a/packages/core/src/views/connect/Index.svelte +++ b/packages/core/src/views/connect/Index.svelte @@ -1,7 +1,6 @@ +``` + +##### Buffer polyfill + +It seems some component or dependency requires Node's Buffer. To polyfill this, the simplest way I could find was to install the buffer package and include the following in web3-onboard.ts: + +```javascript +import { Buffer } from 'buffer' +globalThis.Buffer = Buffer +``` + +See [this github issue](https://github.com/blocknative/web3-onboard/issues/1568#issuecomment-1463963462) for further troubleshooting + +### Vite + +Checkout a boilerplate example for Vite-React (here)[https://github.com/blocknative/web3-onboard/tree/develop/examples/with-vite-react] + +Add the following dev dependencies: + +`npm i --save-dev rollup-plugin-polyfill-node crypto-browserify stream-browserify assert` + +Then add the following to your `vite.config.js` file: + +```javascript +import inject from '@rollup/plugin-inject' + +import nodePolyfills from 'rollup-plugin-polyfill-node' + +const MODE = process.env.NODE_ENV +const development = MODE === 'development' + +export default { + // other config options + plugins: [ + sveltekit(), + development && + nodePolyfills({ + include: [ + 'node_modules/**/*.js', + new RegExp('node_modules/.vite/.*js'), + 'http', + 'crypto' + ] + }) + ], + resolve: { + alias: { + crypto: 'crypto-browserify', + stream: 'stream-browserify', + assert: 'assert' + } + }, + build: { + rollupOptions: { + external: ['@web3-onboard/*'], + plugins: [ + nodePolyfills({ include: ['crypto', 'http'] }), + inject({ Buffer: ['buffer', 'Buffer'] }) + ] + }, + commonjsOptions: { + transformMixedEsModules: true + } + }, + optimizeDeps: { + exclude: ['@ethersproject/hash', 'wrtc', 'http'], + include: [ + '@web3-onboard/core', + '@web3-onboard/gas', + '@web3-onboard/sequence', + 'js-sha3', + '@ethersproject/bignumber' + ], + esbuildOptions: { + // Node.js global to browser globalThis + define: { + global: 'globalThis' + } + } + }, + define: { + global: 'window' + } +} +``` + +### Nuxt.js + +Add the following to your `nuxt.config.js`: + +```javascript +build: { + standalone: true, +} +``` + +### Next.js + +Checkout a boilerplate example for NextJS v13 (here)[https://github.com/blocknative/web3-onboard/tree/develop/examples/with-nextjs-13] + +Checkout a boilerplate example for NextJS (here)[https://github.com/blocknative/web3-onboard/tree/develop/examples/with-nextjs] + +## Package Managers + +### npm and yarn + +Web3-Onboard will work out of the box with `npm` and `yarn` support. + +### pnpm + +We have had issues reported when using `pnpm` as the package manager when working with web3-onboard. +As we work to understand this new manager more and the issues around it we recommend using `npm` or `yarn` for now. diff --git a/packages/core-wagmi/package.json b/packages/core-wagmi/package.json new file mode 100644 index 000000000..3c00a0367 --- /dev/null +++ b/packages/core-wagmi/package.json @@ -0,0 +1,107 @@ +{ + "name": "@web3-onboard/core-wagmi", + "version": "2.22.0-viem.1", + "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardized spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", + "keywords": [ + "Ethereum", + "Web3", + "EVM", + "dapp", + "Multichain", + "Wallet", + "Transaction", + "Provider", + "Hardware Wallet", + "Notifications", + "React", + "Svelte", + "Solid.js", + "Vue", + "Next", + "Nuxt", + "MetaMask", + "Coinbase", + "WalletConnect", + "Ledger", + "Trezor", + "Connect Wallet", + "Ethereum Hooks", + "Blocknative", + "Mempool", + "pending", + "confirmed", + "Injected Wallet", + "Crypto", + "Crypto Wallet", + "onchain" + ], + "repository": { + "type": "git", + "url": "https://github.com/blocknative/web3-onboard.git", + "directory": "packages/core" + }, + "homepage": "https://onboard.blocknative.com", + "bugs": "https://github.com/blocknative/web3-onboard/issues", + "scripts": { + "build": "rollup -c", + "dev": "rollup -c -w", + "start": "sirv public --no-clear", + "type-check": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint -c './.eslintrc.cjs' './src' && prettier --check './src/**/*'", + "lint:fix": "eslint --fix -c './.eslintrc.cjs' './src' && prettier --write './src/**/*'" + }, + "typings": "dist/index.d.ts", + "files": [ + "dist" + ], + "module": "dist/index.js", + "browser": "dist/index.js", + "main": "dist/index.js", + "type": "module", + "license": "MIT", + "devDependencies": { + "@rollup-extras/plugin-copy": "~1.2.2", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.0.6", + "@rollup/plugin-replace": "^3.0.0", + "@rollup/plugin-typescript": "^11.1.6", + "@tsconfig/svelte": "^5.0.0", + "@types/lodash.merge": "^4.6.6", + "@types/lodash.partition": "^4.6.6", + "@typescript-eslint/eslint-plugin": "^4.31.1", + "@typescript-eslint/parser": "^4.31.1", + "@web3-onboard/gas": "^2.1.5", + "@web3-onboard/transaction-preview": "^2.0.5", + "@web3-onboard/unstoppable-resolution": "^2.0.0-alpha.1", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-svelte3": "^3.2.1", + "prettier": "^2.4.0", + "prettier-plugin-svelte": "^2.4.0", + "rollup": "^2.14.0", + "rollup-plugin-svelte": "^7.2.0", + "svelte-check": "^3.7.1", + "svelte-preprocess": "^5.1.4", + "tslib": "^2.0.0", + "typescript": "^5.4.5", + "@rollup/plugin-commonjs":"24.0.1" + }, + "dependencies": { + "@web3-onboard/common": "^2.4.0-viem.1", + "bnc-sdk": "^4.6.7", + "bowser": "^2.11.0", + "eventemitter3": "^4.0.7", + "joi": "17.9.1", + "lodash.merge": "^4.6.2", + "lodash.partition": "^4.6.0", + "nanoid": "^4.0.0", + "rxjs": "^7.5.5", + "svelte": "^3.49.0", + "svelte-i18n": "^3.3.13", + "viem": "^2.9.32", + "@wagmi/core": "^2.9.1" + }, + "engines": { + "node": ">=16.15.1" + } +} diff --git a/packages/core-wagmi/rollup.config.js b/packages/core-wagmi/rollup.config.js new file mode 100644 index 000000000..a4ce6bf84 --- /dev/null +++ b/packages/core-wagmi/rollup.config.js @@ -0,0 +1,72 @@ +import svelte from 'rollup-plugin-svelte' +import resolve from '@rollup/plugin-node-resolve' +import replace from '@rollup/plugin-replace' +import json from '@rollup/plugin-json' +import sveltePreprocess from 'svelte-preprocess' +import typescript from '@rollup/plugin-typescript' +import copy from '@rollup-extras/plugin-copy' +import commonjs from '@rollup/plugin-commonjs' + +const production = !process.env.ROLLUP_WATCH +const specificPackages = ['idna-uts46-hx'] // Replace with the package names you want to target +const includePackages = specificPackages.map(pkg => `node_modules/${pkg}/**`) + +export default { + input: 'src/index.ts', + output: { + format: 'es', + dir: 'dist/', + sourcemap: true + }, + plugins: [ + json(), + replace({ + 'process.env.NODE_ENV': JSON.stringify(production), + preventAssignment: true + }), + svelte({ + preprocess: sveltePreprocess({ + sourceMap: !production, + typescript: { + tsconfigFile: './tsconfig.json' + }, + postcss: true + }), + compilerOptions: { + dev: !production + }, + emitCss: false + }), + resolve({ + browser: true, + dedupe: ['svelte'] + }), + typescript({ + sourceMap: !production, + inlineSources: !production + }), + copy({ + src: 'src/i18n/en.json', + dest: 'i18n' + }), + commonjs({ + include: includePackages + }) + ], + external: [ + '@web3-onboard/common', + 'bowser', + 'joi', + 'rxjs', + 'rxjs/operators', + 'svelte-i18n', + 'svelte/store', + 'lodash.merge', + 'lodash.partition', + 'eventemitter3', + 'bnc-sdk', + 'nanoid', + '@unstoppabledomains/resolution', + 'viem' + ] +} diff --git a/packages/core-wagmi/src/chain.ts b/packages/core-wagmi/src/chain.ts new file mode 100644 index 000000000..6d5bebb05 --- /dev/null +++ b/packages/core-wagmi/src/chain.ts @@ -0,0 +1,144 @@ +import { firstValueFrom, Observable } from 'rxjs' +import { filter, map } from 'rxjs/operators' +import { type Chain, ProviderRpcErrorCode } from '@web3-onboard/common' +import { addNewChain, switchChain } from './provider.js' +import { state } from './store/index.js' +import { switchChainModal$ } from './streams.js' +import { validateSetChainOptions } from './validation.js' +import type { WalletState } from './types.js' +import { toHexString } from './utils.js' +import { updateChain } from './store/actions.js' + +async function setChain(options: { + chainId: string | number + chainNamespace?: string + wallet?: WalletState['label'] + rpcUrl?: string + label?: string + token?: string +}): Promise { + const error = validateSetChainOptions(options) + + if (error) { + throw error + } + + const { wallets, chains } = state.get() + const { + chainId, + chainNamespace = 'evm', + wallet: walletToSet, + rpcUrl, + label, + token + } = options + const chainIdHex = toHexString(chainId) + + // validate that chainId has been added to chains + const chain = chains.find( + ({ namespace, id }) => + namespace === chainNamespace && + id.toLowerCase() === chainIdHex.toLowerCase() + ) + + if (!chain) { + throw new Error( + `Chain with chainId: ${chainId} and chainNamespace: ${chainNamespace} has not been set and must be added when Onboard is initialized.` + ) + } + + const wallet = walletToSet + ? wallets.find(({ label }) => label === walletToSet) + : wallets[0] + + // validate a wallet is connected + if (!wallet) { + throw new Error( + walletToSet + ? `Wallet with label ${walletToSet} is not connected` + : 'A wallet must be connected before a chain can be set' + ) + } + + const [walletConnectedChain] = wallet.chains + + // check if wallet is already connected to chainId + if ( + walletConnectedChain.namespace === chainNamespace && + walletConnectedChain.id === chainIdHex + ) { + return true + } + + try { + await switchChain(wallet.provider, chainIdHex) + return true + } catch (error) { + const { code } = error as { code: number } + const switchChainModalClosed$ = switchChainModal$.pipe( + filter(x => x === null), + map(() => false) + ) + if ( + code === ProviderRpcErrorCode.CHAIN_NOT_ADDED || + code === ProviderRpcErrorCode.UNRECOGNIZED_CHAIN_ID + ) { + // chain has not been added to wallet + if (rpcUrl || label || token) { + if (rpcUrl) { + chain.rpcUrl = rpcUrl + } + + if (label) { + chain.label = label + } + + if (token) { + chain.token = token + } + + updateChain(chain) + } + + // add chain to wallet + return chainNotInWallet( + wallet, + chain, + switchChainModalClosed$, + chainIdHex + ) + } + + if (code === ProviderRpcErrorCode.UNSUPPORTED_METHOD) { + // method not supported + switchChainModal$.next({ chain }) + return firstValueFrom(switchChainModalClosed$) + } + } + + return false +} + +const chainNotInWallet = async ( + wallet: WalletState, + chain: Chain, + switchChainModalClosed$: Observable, + chainIdHex: string +): Promise => { + try { + await addNewChain(wallet.provider, chain) + await switchChain(wallet.provider, chainIdHex) + return true + } catch (error) { + const { code } = error as { code: number } + if (code === ProviderRpcErrorCode.ACCOUNT_ACCESS_REJECTED) { + // add new chain rejected by user + return false + } + // display notification to user to switch chain + switchChainModal$.next({ chain }) + return firstValueFrom(switchChainModalClosed$) + } +} + +export default setChain diff --git a/packages/core-wagmi/src/configuration.ts b/packages/core-wagmi/src/configuration.ts new file mode 100644 index 000000000..d828ad69c --- /dev/null +++ b/packages/core-wagmi/src/configuration.ts @@ -0,0 +1,17 @@ +import type { Configuration } from './types.js' +import { getDevice } from './utils.js' + +export let configuration: Configuration = { + svelteInstance: null, + apiKey: undefined, + device: getDevice(), + initialWalletInit: [], + gas: undefined, + containerElements: { accountCenter: undefined, connectModal: undefined }, + transactionPreview: undefined, + unstoppableResolution: undefined +} + +export function updateConfiguration(update: Partial): void { + configuration = { ...configuration, ...update } +} diff --git a/packages/core-wagmi/src/connect.ts b/packages/core-wagmi/src/connect.ts new file mode 100644 index 000000000..63d1133db --- /dev/null +++ b/packages/core-wagmi/src/connect.ts @@ -0,0 +1,71 @@ +import { firstValueFrom } from 'rxjs' +import { filter, withLatestFrom, pluck } from 'rxjs/operators' +import { configuration } from './configuration.js' +import { state } from './store/index.js' +import { setWalletModules } from './store/actions.js' +import { connectWallet$, wallets$ } from './streams.js' +import type { + ConnectOptions, + ConnectOptionsString, + WalletState +} from './types.js' +import { wait } from './utils.js' +import { validateConnectOptions } from './validation.js' +import { initializeWAGMI } from './services/wagmi' + +async function connect( + options?: ConnectOptions | ConnectOptionsString +): Promise { + if (options) { + const error = validateConnectOptions(options) + if (error) { + throw error + } + } + + const { chains } = state.get() + + // Wallets require the chains for initializing providers, + // so we must ensure at least one is set + if (!chains.length) + throw new Error( + 'At least one chain must be set before attempting to connect a wallet' + ) + + const { autoSelect } = options || { + autoSelect: { label: '', disableModals: false } + } + + // if auto selecting, wait until next event loop + if (autoSelect && (typeof autoSelect === 'string' || autoSelect.label)) { + await wait(50) + } + + // first time calling connect, so initialize and set wallet modules + if (!state.get().walletModules.length) { + setWalletModules(configuration.initialWalletInit) + await initializeWAGMI(state.get().walletModules) + } + + connectWallet$.next({ + autoSelect: + typeof autoSelect === 'string' + ? { label: autoSelect, disableModals: false } + : autoSelect, + inProgress: true + }) + + const result$ = connectWallet$.pipe( + filter( + ({ inProgress, actionRequired }) => + inProgress === false && !actionRequired + ), + withLatestFrom(wallets$), + pluck(1) + ) + + return firstValueFrom(result$) +} + + +export default connect diff --git a/packages/core-wagmi/src/constants.ts b/packages/core-wagmi/src/constants.ts new file mode 100644 index 000000000..09c3f5631 --- /dev/null +++ b/packages/core-wagmi/src/constants.ts @@ -0,0 +1,41 @@ +import type { AppState } from './types.js' + +export const APP_INITIAL_STATE: AppState = { + wallets: [], + walletModules: [], + chains: [], + accountCenter: { + enabled: true, + position: 'bottomRight', + expanded: false, + minimal: true + }, + notify: { + enabled: true, + transactionHandler: () => {}, + position: 'topRight', + replacement: { + gasPriceProbability: { + speedup: 80, + cancel: 95 + } + } + }, + notifications: [], + locale: '', + connect: { + showSidebar: true, + disableClose: false + }, + appMetadata: null, +} + +export const STORAGE_KEYS = { + TERMS_AGREEMENT: 'onboard.js:agreement', + LAST_CONNECTED_WALLET: 'onboard.js:last_connected_wallet' +} + +export const MOBILE_WINDOW_WIDTH = 768 + +export const BN_BOOST_RPC_URL = 'https://rpc.blocknative.com/boost' +export const BN_BOOST_INFO_URL = 'https://docs.blocknative.com/blocknative-mev-protection/transaction-boost' diff --git a/packages/core-wagmi/src/disconnect.ts b/packages/core-wagmi/src/disconnect.ts new file mode 100644 index 000000000..8e6f39dae --- /dev/null +++ b/packages/core-wagmi/src/disconnect.ts @@ -0,0 +1,55 @@ +import { getBNMulitChainSdk } from './services/bnSDK.js' +import { state } from './store/index.js' +import { removeWallet } from './store/actions.js' +import { disconnectWallet$ } from './streams.js' +import type { DisconnectOptions, WalletState } from './types.js' +import { validateDisconnectOptions } from './validation.js' +import { delLocalStore, getLocalStore, setLocalStore } from './utils' +import { STORAGE_KEYS } from './constants' + +async function disconnect(options: DisconnectOptions): Promise { + const error = validateDisconnectOptions(options) + if (error) { + throw error + } + + const { label } = options + + if (state.get().notify.enabled) { + // handle unwatching addresses + const sdk = await getBNMulitChainSdk() + + if (sdk) { + const wallet = state.get().wallets.find(wallet => wallet.label === label) + + if (wallet) { + wallet.accounts.forEach(({ address }) => { + sdk.unsubscribe({ + id: address, + chainId: wallet.chains[0].id, + timeout: 60000 + }) + }) + } + } + } + + disconnectWallet$.next(label) + removeWallet(label) + + const labels = JSON.parse(getLocalStore(STORAGE_KEYS.LAST_CONNECTED_WALLET) || '') + + if (Array.isArray(labels) && labels.indexOf(label) >= 0) { + setLocalStore( + STORAGE_KEYS.LAST_CONNECTED_WALLET, + JSON.stringify(labels.filter(walletLabel => walletLabel !== label)) + ) + } + if (typeof labels === 'string' && labels === label) { + delLocalStore(STORAGE_KEYS.LAST_CONNECTED_WALLET) + } + + return state.get().wallets +} + +export default disconnect diff --git a/packages/core-wagmi/src/global.d.ts b/packages/core-wagmi/src/global.d.ts new file mode 100644 index 000000000..1a25456a2 --- /dev/null +++ b/packages/core-wagmi/src/global.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/core-wagmi/src/i18n/en.json b/packages/core-wagmi/src/i18n/en.json new file mode 100644 index 000000000..d5d2d7658 --- /dev/null +++ b/packages/core-wagmi/src/i18n/en.json @@ -0,0 +1,121 @@ +{ + "connect": { + "selectingWallet": { + "header": "Available Wallets", + "sidebar": { + "heading": "", + "subheading": "Connect your wallet", + "paragraph": "Connecting your wallet is like “logging in” to Web3. Select your wallet from the options to get started.", + "IDontHaveAWallet": "I don't have a wallet" + }, + "recommendedWalletsPart1": "{app} only supports", + "recommendedWalletsPart2": "on this platform. Please use or install one of the supported wallets to continue", + "installWallet": "You do not have any wallets installed that {app} supports, please use a supported wallet", + "agreement": { + "agree": "I agree to the", + "terms": "Terms & Conditions", + "and": "and", + "privacy": "Privacy Policy" + }, + "whyDontISeeMyWallet": "Why don't I see my wallet?", + "learnMore": "Click here to learn more" + }, + "connectingWallet": { + "header": "{connectionRejected, select, false {Connecting to {wallet}...} other {Connection Rejected}}", + "sidebar": { + "subheading": "Approve Connection", + "paragraph": "Please approve the connection in your wallet and authorize access to continue." + }, + "mainText": "Connecting...", + "paragraph": "Make sure to select all accounts that you want to grant access to.", + "previousConnection": "{wallet} already has a pending connection request, please open the {wallet} app to login and connect.", + "rejectedText": "Connection Rejected!", + "rejectedCTA": "Click here to try again", + "primaryButton": "Back to wallets" + }, + "connectedWallet": { + "header": "Connection Successful", + "sidebar": { + "subheading": "Connection Successful!", + "paragraph": "Your wallet is now connected to {app}" + }, + "mainText": "Connected" + } + }, + "modals": { + "actionRequired": { + "heading": "Action required in {wallet}", + "paragraph": "Please switch the active account in your wallet.", + "linkText": "Learn more.", + "buttonText": "Okay" + }, + "switchChain": { + "heading": "Switch Chain", + "paragraph1": "{app} requires that you switch your wallet to the {nextNetworkName} network to continue.", + "paragraph2": "*Some wallets may not support changing networks. If you can not change networks in your wallet you may consider switching to a different wallet." + }, + "confirmDisconnectAll": { + "heading": "Disconnect all Wallets", + "description": "Are you sure that you would like to disconnect all your wallets?", + "confirm": "Confirm", + "cancel": "Cancel" + }, + "confirmTransactionProtection": { + "heading": "Enable Transaction Protection", + "description": "Protect RPC endpoints hide your transaction from front-running and sandwich bots.", + "link": "Learn more", + "enable": "Enable", + "dismiss": "Dismiss" + } + }, + "accountCenter": { + "connectAnotherWallet": "Connect another Wallet", + "disconnectAllWallets": "Disconnect all Wallets", + "currentNetwork": "Current Network", + "enableTransactionProtection": "Enable Transaction Protection", + "appInfo": "App Info", + "learnMore": "Learn More", + "gettingStartedGuide": "Getting Started Guide", + "smartContracts": "Smart Contract(s)", + "explore": "Explore", + "poweredBy": "powered by", + "addAccount": "Add Account", + "setPrimaryAccount": "Set Primary Account", + "disconnectWallet": "Disconnect Wallet", + "copyAddress": "Copy Wallet address" + }, + "notify": { + "transaction": { + "txRequest": "Your transaction is waiting for you to confirm", + "nsfFail": "You have insufficient funds for this transaction", + "txUnderpriced": "The gas price for your transaction is too low, try a higher gas price", + "txRepeat": "This could be a repeat transaction", + "txAwaitingApproval": "You have a previous transaction waiting for you to confirm", + "txConfirmReminder": "Please confirm your transaction to continue", + "txSendFail": "You rejected the transaction", + "txSent": "Your transaction has been sent to the network", + "txStallPending": "Your transaction has stalled before it was sent, please try again", + "txStuck": "Your transaction is stuck due to a nonce gap", + "txPool": "Your transaction has started", + "txStallConfirmed": "Your transaction has stalled and hasn't been confirmed", + "txSpeedUp": "Your transaction has been sped up", + "txCancel": "Your transaction is being canceled", + "txFailed": "Your transaction has failed", + "txConfirmed": "Your transaction has succeeded", + "txError": "Oops something went wrong, please try again", + "txReplaceError": "There was an error replacing your transaction, please try again" + }, + "watched": { + "txPool": "Your account is {verb} {formattedValue} {asset} {preposition} {counterpartyShortened}", + "txSpeedUp": "Transaction for {formattedValue} {asset} {preposition} {counterpartyShortened} has been sped up", + "txCancel": "Transaction for {formattedValue} {asset} {preposition} {counterpartyShortened} has been canceled", + "txConfirmed": "Your account successfully {verb} {formattedValue} {asset} {preposition} {counterpartyShortened}", + "txFailed": "Your account failed to {verb} {formattedValue} {asset} {preposition} {counterpartyShortened}", + "txStuck": "Your transaction is stuck due to a nonce gap" + }, + "time": { + "minutes": "min", + "seconds": "sec" + } + } +} diff --git a/packages/core-wagmi/src/i18n/index.ts b/packages/core-wagmi/src/i18n/index.ts new file mode 100644 index 000000000..db5b473f9 --- /dev/null +++ b/packages/core-wagmi/src/i18n/index.ts @@ -0,0 +1,29 @@ +import { addMessages, init, getLocaleFromNavigator } from 'svelte-i18n' +import merge from 'lodash.merge' +import en from './en.json' +import type { i18nOptions } from '../types.js' + +function initialize(options?: i18nOptions): void { + if (options) { + const { en: customizedEn } = options + const merged = merge(en, customizedEn || {}) + addMessages('en', merged) + + const customLocales = Object.keys(options).filter(key => key !== 'en') + + // Sync register all customLocales + customLocales.forEach(locale => { + const dictionary = options[locale] + dictionary && addMessages(locale, dictionary) + }) + } else { + addMessages('en', en) + } + + init({ + fallbackLocale: 'en', + initialLocale: getLocaleFromNavigator() + }) +} + +export default initialize diff --git a/packages/core-wagmi/src/icons/arbitrum.ts b/packages/core-wagmi/src/icons/arbitrum.ts new file mode 100644 index 000000000..a72fd2947 --- /dev/null +++ b/packages/core-wagmi/src/icons/arbitrum.ts @@ -0,0 +1,10 @@ +export default ` + + + + + + + + +` diff --git a/packages/core-wagmi/src/icons/arrow-forward.ts b/packages/core-wagmi/src/icons/arrow-forward.ts new file mode 100644 index 000000000..7935b3465 --- /dev/null +++ b/packages/core-wagmi/src/icons/arrow-forward.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/avalanche.ts b/packages/core-wagmi/src/icons/avalanche.ts new file mode 100644 index 000000000..822840748 --- /dev/null +++ b/packages/core-wagmi/src/icons/avalanche.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/base.ts b/packages/core-wagmi/src/icons/base.ts new file mode 100644 index 000000000..5f7e3dcc7 --- /dev/null +++ b/packages/core-wagmi/src/icons/base.ts @@ -0,0 +1,18 @@ +export default ` + + + + + + + + + + + + + + + + +` \ No newline at end of file diff --git a/packages/core-wagmi/src/icons/binance.ts b/packages/core-wagmi/src/icons/binance.ts new file mode 100644 index 000000000..31197323a --- /dev/null +++ b/packages/core-wagmi/src/icons/binance.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/blocknative-icon.ts b/packages/core-wagmi/src/icons/blocknative-icon.ts new file mode 100644 index 000000000..91ca1cb81 --- /dev/null +++ b/packages/core-wagmi/src/icons/blocknative-icon.ts @@ -0,0 +1,25 @@ +export default ` + + + + + + + + + + + + + + + + + + + + + + + +` diff --git a/packages/core-wagmi/src/icons/caret-light.ts b/packages/core-wagmi/src/icons/caret-light.ts new file mode 100644 index 000000000..0e9df67fd --- /dev/null +++ b/packages/core-wagmi/src/icons/caret-light.ts @@ -0,0 +1 @@ +export default `` diff --git a/packages/core-wagmi/src/icons/caret.ts b/packages/core-wagmi/src/icons/caret.ts new file mode 100644 index 000000000..b5f45c651 --- /dev/null +++ b/packages/core-wagmi/src/icons/caret.ts @@ -0,0 +1 @@ +export default `` diff --git a/packages/core-wagmi/src/icons/celo.ts b/packages/core-wagmi/src/icons/celo.ts new file mode 100644 index 000000000..1308f8cc6 --- /dev/null +++ b/packages/core-wagmi/src/icons/celo.ts @@ -0,0 +1,12 @@ +export default ` + + + + + + +` diff --git a/packages/core-wagmi/src/icons/checkmark.ts b/packages/core-wagmi/src/icons/checkmark.ts new file mode 100644 index 000000000..9bcddb267 --- /dev/null +++ b/packages/core-wagmi/src/icons/checkmark.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/close-circle.ts b/packages/core-wagmi/src/icons/close-circle.ts new file mode 100644 index 000000000..99288ef30 --- /dev/null +++ b/packages/core-wagmi/src/icons/close-circle.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/close.ts b/packages/core-wagmi/src/icons/close.ts new file mode 100644 index 000000000..a6b29b183 --- /dev/null +++ b/packages/core-wagmi/src/icons/close.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/degen.ts b/packages/core-wagmi/src/icons/degen.ts new file mode 100644 index 000000000..8bc8d206a --- /dev/null +++ b/packages/core-wagmi/src/icons/degen.ts @@ -0,0 +1,12 @@ +export default ` + + + + + + + + + + +` \ No newline at end of file diff --git a/packages/core-wagmi/src/icons/elipsis.ts b/packages/core-wagmi/src/icons/elipsis.ts new file mode 100644 index 000000000..00248fab3 --- /dev/null +++ b/packages/core-wagmi/src/icons/elipsis.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/error.ts b/packages/core-wagmi/src/icons/error.ts new file mode 100644 index 000000000..7b6a96ad8 --- /dev/null +++ b/packages/core-wagmi/src/icons/error.ts @@ -0,0 +1,4 @@ +export default ` + + +` diff --git a/packages/core-wagmi/src/icons/ethereum.ts b/packages/core-wagmi/src/icons/ethereum.ts new file mode 100644 index 000000000..96f0aaad5 --- /dev/null +++ b/packages/core-wagmi/src/icons/ethereum.ts @@ -0,0 +1,10 @@ +export default ` + + + + + + + + +` diff --git a/packages/core-wagmi/src/icons/fantom.ts b/packages/core-wagmi/src/icons/fantom.ts new file mode 100644 index 000000000..2017057cc --- /dev/null +++ b/packages/core-wagmi/src/icons/fantom.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/gnosis.ts b/packages/core-wagmi/src/icons/gnosis.ts new file mode 100644 index 000000000..afbe6bc58 --- /dev/null +++ b/packages/core-wagmi/src/icons/gnosis.ts @@ -0,0 +1,9 @@ +export default ` + + + + + + + +` diff --git a/packages/core-wagmi/src/icons/harmony-one.ts b/packages/core-wagmi/src/icons/harmony-one.ts new file mode 100644 index 000000000..6460e0c94 --- /dev/null +++ b/packages/core-wagmi/src/icons/harmony-one.ts @@ -0,0 +1,11 @@ +export default ` + + + + + + + + + +` diff --git a/packages/core-wagmi/src/icons/hourglass.ts b/packages/core-wagmi/src/icons/hourglass.ts new file mode 100644 index 000000000..3dcee91f0 --- /dev/null +++ b/packages/core-wagmi/src/icons/hourglass.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/index.ts b/packages/core-wagmi/src/icons/index.ts new file mode 100644 index 000000000..8565422dd --- /dev/null +++ b/packages/core-wagmi/src/icons/index.ts @@ -0,0 +1,26 @@ +// bn branding +export { default as defaultBnIcon } from './blocknative-icon.js' +export { default as poweredByBlocknative } from './poweredByBlocknative.js' +// chain icons +export { default as ethereumIcon } from './ethereum.js' +export { default as polygonIcon } from './polygon.js' +export { default as binanceIcon } from './binance.js' +export { default as fantomIcon } from './fantom.js' +export { default as optimismIcon } from './optimism.js' +export { default as avalancheIcon } from './avalanche.js' +export { default as celoIcon } from './celo.js' +export { default as gnosisIcon } from './gnosis.js' +export { default as harmonyOneIcon } from './harmony-one.js' +export { default as arbitrumIcon } from './arbitrum.js' +export { default as baseIcon } from './base.js' +// other +export { default as hourglass } from './hourglass.js' +export { default as questionIcon } from './question.js' +export { default as checkmark } from './checkmark.js' +export { default as errorIcon } from './error.js' +export { default as infoIcon } from './info.js' +export { default as caretIcon } from './caret.js' +export { default as warningIcon } from './warning.js' +export { default as successIcon } from './success.js' +export { default as pendingIcon } from './pending.js' +export { default as degenIcon } from './degen.js' diff --git a/packages/core-wagmi/src/icons/info.ts b/packages/core-wagmi/src/icons/info.ts new file mode 100644 index 000000000..684bcbc91 --- /dev/null +++ b/packages/core-wagmi/src/icons/info.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/optimism.ts b/packages/core-wagmi/src/icons/optimism.ts new file mode 100644 index 000000000..c6a4b94d9 --- /dev/null +++ b/packages/core-wagmi/src/icons/optimism.ts @@ -0,0 +1,6 @@ +export default ` + + + + +` diff --git a/packages/core-wagmi/src/icons/pending.ts b/packages/core-wagmi/src/icons/pending.ts new file mode 100644 index 000000000..be3f19dc5 --- /dev/null +++ b/packages/core-wagmi/src/icons/pending.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/plus-circle.ts b/packages/core-wagmi/src/icons/plus-circle.ts new file mode 100644 index 000000000..90e86c237 --- /dev/null +++ b/packages/core-wagmi/src/icons/plus-circle.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/polygon.ts b/packages/core-wagmi/src/icons/polygon.ts new file mode 100644 index 000000000..68fc34271 --- /dev/null +++ b/packages/core-wagmi/src/icons/polygon.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/poweredByBlocknative.ts b/packages/core-wagmi/src/icons/poweredByBlocknative.ts new file mode 100644 index 000000000..41523d85e --- /dev/null +++ b/packages/core-wagmi/src/icons/poweredByBlocknative.ts @@ -0,0 +1,35 @@ +export default ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` diff --git a/packages/core-wagmi/src/icons/question.ts b/packages/core-wagmi/src/icons/question.ts new file mode 100644 index 000000000..245cc361d --- /dev/null +++ b/packages/core-wagmi/src/icons/question.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/shield-icon.ts b/packages/core-wagmi/src/icons/shield-icon.ts new file mode 100644 index 000000000..18102aa0f --- /dev/null +++ b/packages/core-wagmi/src/icons/shield-icon.ts @@ -0,0 +1,4 @@ +export default ` + + +` \ No newline at end of file diff --git a/packages/core-wagmi/src/icons/success.ts b/packages/core-wagmi/src/icons/success.ts new file mode 100644 index 000000000..5fb3707f2 --- /dev/null +++ b/packages/core-wagmi/src/icons/success.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/icons/warning.ts b/packages/core-wagmi/src/icons/warning.ts new file mode 100644 index 000000000..5f3f308c5 --- /dev/null +++ b/packages/core-wagmi/src/icons/warning.ts @@ -0,0 +1,5 @@ +export default ` + + + +` diff --git a/packages/core-wagmi/src/index.ts b/packages/core-wagmi/src/index.ts new file mode 100644 index 000000000..fbdba54ef --- /dev/null +++ b/packages/core-wagmi/src/index.ts @@ -0,0 +1,498 @@ +import connectWallet from './connect.js' +import disconnectWallet from './disconnect.js' +import setChain from './chain.js' +import { state } from './store/index.js' +import { reset$, wallets$ } from './streams.js' +import initI18N from './i18n/index.js' +import App from './views/Index.svelte' +import type { + ConnectModalOptions, + InitOptions, + Notify, + Theme +} from './types.js' +import { APP_INITIAL_STATE, STORAGE_KEYS } from './constants.js' +import { configuration, updateConfiguration } from './configuration.js' +import updateBalances from './update-balances.js' +import { chainIdToHex, getLocalStore, setLocalStore } from './utils.js' +import { preflightNotifications } from './preflight-notifications.js' + +import { + validateInitOptions, + validateNotify, + validateNotifyOptions +} from './validation.js' + +import { + addChains, + updateAccountCenter, + updateNotify, + customNotification, + setLocale, + setPrimaryWallet, + setWalletModules, + updateConnectModal, + updateTheme, + updateAppMetadata +} from './store/actions.js' +import type { PatchedEIP1193Provider } from '@web3-onboard/transaction-preview' +import { getBlocknativeSdk } from './services/bnSDK.js' +import { wagmiConfig } from './services/wagmi' + +const API = { + connectWallet, + disconnectWallet, + setChain, + wagmiConfig, + state: { + get: state.get, + select: state.select, + actions: { + setWalletModules, + setLocale, + updateNotify, + customNotification, + preflightNotifications, + updateBalances, + updateAccountCenter, + setPrimaryWallet, + updateTheme, + updateAppMetadata + } + } +} + +export type OnboardAPI = typeof API + +export type { + InitOptions, + ConnectOptions, + DisconnectOptions, + WalletState, + ConnectedChain, + AccountCenter, + AppState, + CustomNotification, + Notification, + Notify, + UpdateNotification, + PreflightNotificationsOptions, + Theme +} from './types.js' + +export type { EIP1193Provider } from '@web3-onboard/common' + +function init(options: InitOptions): OnboardAPI { + if (typeof window === 'undefined') return API + + if (options) { + const error = validateInitOptions(options) + + if (error) { + throw error + } + } + + const { + wallets, + chains, + appMetadata, + i18n, + accountCenter, + apiKey, + notify, + gas, + connect, + containerElements, + transactionPreview, + theme, + disableFontDownload, + unstoppableResolution + } = options + + if (containerElements) updateConfiguration({ containerElements }) + + const { device, svelteInstance } = configuration + + if (svelteInstance) { + // if already initialized, need to cleanup old instance + console.warn('Re-initializing Onboard and resetting back to initial state') + reset$.next() + } + + initI18N(i18n) + addChains(chainIdToHex(chains)) + + if (typeof connect !== 'undefined') { + updateConnectModal( + connect as ConnectModalOptions | Partial + ) + } + // update accountCenter + if (typeof accountCenter !== 'undefined') { + let accountCenterUpdate + const { hideTransactionProtectionBtn, transactionProtectionInfoLink } = + accountCenter + + if (device.type === 'mobile') { + accountCenterUpdate = { + ...APP_INITIAL_STATE.accountCenter, + hideTransactionProtectionBtn, + transactionProtectionInfoLink, + ...(accountCenter.mobile ? accountCenter.mobile : {}) + } + } else if (accountCenter.desktop) { + accountCenterUpdate = { + ...APP_INITIAL_STATE.accountCenter, + hideTransactionProtectionBtn, + transactionProtectionInfoLink, + ...accountCenter.desktop + } + } + if (typeof accountCenterUpdate !== 'undefined') { + updateAccountCenter(accountCenterUpdate) + } + } + + // update notify + if (typeof notify !== 'undefined') { + if ('desktop' in notify || 'mobile' in notify) { + const error = validateNotifyOptions(notify) + + if (error) { + throw error + } + + if ( + notify && + notify.desktop && + notify.desktop.position && + accountCenter && + accountCenter.desktop && + accountCenter.desktop.position + ) { + notify.desktop.position = accountCenter.desktop.position + } + + if ( + notify && + notify.mobile && + notify.mobile.position && + accountCenter && + accountCenter.mobile && + accountCenter.mobile.position + ) { + notify.mobile.position = accountCenter.mobile.position + } + + let notifyUpdate: Partial = {} + + if (device.type === 'mobile' && notify.mobile) { + notifyUpdate = { + ...APP_INITIAL_STATE.notify, + ...notify.mobile + } + } else if (notify.desktop) { + notifyUpdate = { + ...APP_INITIAL_STATE.notify, + ...notify.desktop + } + } + + updateNotify(notifyUpdate) + } else { + const error = validateNotify(notify as Notify) + + if (error) { + throw error + } + + const notifyUpdate: Partial = { + ...APP_INITIAL_STATE.notify, + ...notify + } + + updateNotify(notifyUpdate) + } + } else { + const notifyUpdate: Partial = APP_INITIAL_STATE.notify + + updateNotify(notifyUpdate) + } + + const app = + svelteInstance || mountApp(theme || {}, disableFontDownload || false) + + updateConfiguration({ + svelteInstance: app, + apiKey, + initialWalletInit: wallets, + gas, + transactionPreview, + unstoppableResolution + }) + + appMetadata && updateAppMetadata(appMetadata) + + if (apiKey && transactionPreview) { + const getBnSDK = async () => { + const sdk = await getBlocknativeSdk() + if (!sdk) return + transactionPreview.init({ + containerElement: '#w3o-transaction-preview-container', + sdk, + apiKey + }) + wallets$.subscribe(wallets => { + wallets.forEach(({ provider }) => { + transactionPreview.patchProvider(provider as PatchedEIP1193Provider) + }) + }) + } + getBnSDK() + } + + theme && updateTheme(theme) + + // handle auto connection of last wallet + if ( + connect && + (connect.autoConnectLastWallet || connect.autoConnectAllPreviousWallet) + ) { + const lastConnectedWallets = getLocalStore( + STORAGE_KEYS.LAST_CONNECTED_WALLET + ) + try { + const lastConnectedWalletsParsed = JSON.parse( + lastConnectedWallets as string + ) + if ( + lastConnectedWalletsParsed && + Array.isArray(lastConnectedWalletsParsed) && + lastConnectedWalletsParsed.length + ) { + connectAllPreviousWallets(lastConnectedWalletsParsed, connect) + } + } catch (err) { + // Handle for legacy single wallet approach + // Above try will throw syntax error is local storage is not json + if (err instanceof SyntaxError && lastConnectedWallets) { + API.connectWallet({ + autoSelect: { + label: lastConnectedWallets, + disableModals: true + } + }) + } + } + } + + return API +} + +const fontFamilyExternallyDefined = ( + theme: Theme, + disableFontDownload: boolean +): boolean => { + if (disableFontDownload) return true + if ( + document.body && + (getComputedStyle(document.body).getPropertyValue( + '--onboard-font-family-normal' + ) || + getComputedStyle(document.body).getPropertyValue('--w3o-font-family')) + ) + return true + if (!theme) return false + if (typeof theme === 'object' && theme['--w3o-font-family']) return true + return false +} + +const importInterFont = async (): Promise => { + const { InterVar } = await import('@web3-onboard/common') + // Add Fonts to main page + const styleEl = document.createElement('style') + + styleEl.innerHTML = ` + ${InterVar} + ` + + document.body.appendChild(styleEl) +} + +const connectAllPreviousWallets = async ( + lastConnectedWallets: Array, + connect: ConnectModalOptions +): Promise => { + const activeWalletsList = [] + const parsedWalletList = lastConnectedWallets + + if (!connect.autoConnectAllPreviousWallet) { + API.connectWallet({ + autoSelect: { label: parsedWalletList[0], disableModals: true } + }) + activeWalletsList.push(parsedWalletList[0]) + } else { + // Loop in reverse to maintain wallet order + for (let i = parsedWalletList.length; i--; ) { + const walletConnectionPromise = await API.connectWallet({ + autoSelect: { label: parsedWalletList[i], disableModals: true } + }) + // Update localStorage list for available wallets + if (walletConnectionPromise.some(r => r.label === parsedWalletList[i])) { + activeWalletsList.unshift(parsedWalletList[i]) + } + } + } + setLocalStore( + STORAGE_KEYS.LAST_CONNECTED_WALLET, + JSON.stringify(activeWalletsList) + ) +} + +function mountApp(theme: Theme, disableFontDownload: boolean) { + class Onboard extends HTMLElement { + constructor() { + super() + } + } + + if (!customElements.get('onboard-v2')) { + customElements.define('onboard-v2', Onboard) + } + + if (!fontFamilyExternallyDefined(theme, disableFontDownload)) { + importInterFont() + } + + // add to DOM + const onboard = document.createElement('onboard-v2') + const target = onboard.attachShadow({ mode: 'open' }) + + onboard.style.all = 'initial' + + target.innerHTML = ` + + + ` + let connectModalContEl + if ( + configuration && + configuration.containerElements && + configuration.containerElements.connectModal + ) { + connectModalContEl = configuration.containerElements.connectModal + } + + const containerElementQuery = + connectModalContEl || state.get().accountCenter.containerElement || 'body' + + const containerElement = document.querySelector(containerElementQuery) + + if (!containerElement) { + throw new Error( + `Element with query ${containerElementQuery} does not exist.` + ) + } + + containerElement.appendChild(onboard) + + const app = new App({ + target + }) + + return app +} + +export default init diff --git a/packages/core-wagmi/src/notify.ts b/packages/core-wagmi/src/notify.ts new file mode 100644 index 000000000..eda65c618 --- /dev/null +++ b/packages/core-wagmi/src/notify.ts @@ -0,0 +1,168 @@ +import { get } from 'svelte/store' +import { _ } from 'svelte-i18n' +import defaultCopy from './i18n/en.json' +import { weiToEth } from '@web3-onboard/common' +import type { EthereumTransactionData } from 'bnc-sdk' + +import type { + CustomNotification, + Notification, + NotificationType +} from './types.js' + +import { validateTransactionHandlerReturn } from './validation.js' +import { state } from './store/index.js' +import { addNotification } from './store/actions.js' +import updateBalances from './update-balances.js' +import { updateTransaction } from './streams.js' + +export function handleTransactionUpdates( + transaction: EthereumTransactionData +): void { + const customized = state.get().notify.transactionHandler(transaction) + const invalid = validateTransactionHandlerReturn(customized) + + if (invalid) { + throw invalid + } + + if (transaction.eventCode === 'txConfirmed') { + const addresses = [ + transaction.watchedAddress, + transaction.counterparty + ].filter(Boolean) as string[] + updateBalances(addresses) + } + + const notification = transactionEventToNotification(transaction, customized) + + addNotification(notification) + updateTransaction(transaction) +} + +export function transactionEventToNotification( + transaction: EthereumTransactionData, + customization: CustomNotification | boolean | void +): Notification { + const { + id, + hash, + startTime, + eventCode, + direction, + counterparty, + value, + asset, + network + } = transaction + + const type: NotificationType = eventToType(eventCode) + + const key = `${id || hash}-${ + (typeof customization === 'object' && customization.eventCode) || eventCode + }` + + const counterpartyShortened: string | undefined = + counterparty && + counterparty.substring(0, 4) + + '...' + + counterparty.substring(counterparty.length - 4) + const formattedValue = weiToEth(value) + const formatterOptions = + counterparty && value + ? { + messageId: `notify.watched['${eventCode}']`, + values: { + verb: + eventCode === 'txConfirmed' + ? direction === 'incoming' + ? 'received' + : 'sent' + : direction === 'incoming' + ? 'receiving' + : 'sending', + formattedValue, + preposition: direction === 'incoming' ? 'from' : 'to', + counterpartyShortened, + asset + } + } + : { + messageId: `notify.transaction['${eventCode}']`, + values: { formattedValue, asset } + } + + const formatter = get(_) + + const notificationDefaultMessages = defaultCopy.notify + + const typeKey: keyof typeof notificationDefaultMessages = counterparty + ? 'watched' + : 'transaction' + + const notificationMessageType = notificationDefaultMessages[typeKey] + + const defaultMessage = + notificationMessageType[eventCode as keyof typeof notificationMessageType] + + const message = formatter(formatterOptions.messageId, { + values: formatterOptions.values, + default: defaultMessage + }) + + let notification = { + id: id || hash, + type, + key, + network, + startTime: startTime || Date.now(), + eventCode, + message, + autoDismiss: typeToDismissTimeout( + (typeof customization === 'object' && customization.type) || type + ) + } + + if (typeof customization === 'object') { + notification = { ...notification, ...customization } + } + + return notification +} + +export function eventToType(eventCode: string | undefined): NotificationType { + switch (eventCode) { + case 'txSent': + case 'txPool': + return 'pending' + case 'txSpeedUp': + case 'txCancel': + case 'txRequest': + case 'txRepeat': + case 'txAwaitingApproval': + case 'txConfirmReminder': + case 'txStuck': + return 'hint' + case 'txError': + case 'txSendFail': + case 'txFailed': + case 'txDropped': + case 'nsfFail': + case 'txUnderpriced': + return 'error' + case 'txConfirmed': + return 'success' + default: + return 'hint' + } +} + +export function typeToDismissTimeout(type: string): number { + switch (type) { + case 'success': + case 'hint': + return 4000 + default: + return 0 + } +} diff --git a/packages/core-wagmi/src/preflight-notifications.ts b/packages/core-wagmi/src/preflight-notifications.ts new file mode 100644 index 000000000..d9247e7c3 --- /dev/null +++ b/packages/core-wagmi/src/preflight-notifications.ts @@ -0,0 +1,231 @@ +import { nanoid } from 'nanoid' +import defaultCopy from './i18n/en.json' +import type { Network } from 'bnc-sdk' +import { bigIntToHex, ethToWeiBigInt } from '@web3-onboard/common' + +import type { Notification, PreflightNotificationsOptions } from './types.js' +import { addNotification, removeNotification } from './store/actions.js' +import { state } from './store/index.js' +import { eventToType } from './notify.js' +import { networkToChainId } from './utils.js' +import { validatePreflightNotifications } from './validation.js' + +let notificationsArr: Notification[] +state.select('notifications').subscribe(notifications => { + notificationsArr = notifications +}) + +export async function preflightNotifications( + options: PreflightNotificationsOptions +): Promise { + const invalid = validatePreflightNotifications(options) + + if (invalid) { + throw invalid + } + + const { + sendTransaction, + estimateGas, + gasPrice, + balance, + txDetails, + txApproveReminderTimeout + } = options + + // Check for reminder timeout and confirm its greater than 3 seconds + const reminderTimeout: number = + txApproveReminderTimeout && txApproveReminderTimeout > 3000 + ? txApproveReminderTimeout + : 15000 + + // if `balance` or `estimateGas` or `gasPrice` is not provided, + // then sufficient funds check is disabled + // if `txDetails` is not provided, + // then duplicate transaction check is disabled + // if dev doesn't want notify to initiate the transaction + // and `sendTransaction` is not provided, then transaction + // rejected notification is disabled + // to disable hints for `txAwaitingApproval`, `txConfirmReminder` + // or any other notification, then return false from listener functions + + const [gas, price] = await gasEstimates( + estimateGas || (() => Promise.resolve('')), + gasPrice || (() => Promise.resolve('')) + ) + const id = createId(nanoid()) + const value = BigInt((txDetails && txDetails.value) || 0) + + // check sufficient balance if required parameters are available + if (balance && gas && price) { + const transactionCost = BigInt(gas) * BigInt(price) + value + + // if transaction cost is greater than the current balance + if (bigIntToHex(transactionCost) > bigIntToHex(ethToWeiBigInt(balance))) { + const eventCode = 'nsfFail' + + addNotification(buildNotification(eventCode, id)) + } + } + + // check previous transactions awaiting approval + const txRequested = notificationsArr.find(tx => tx.eventCode === 'txRequest') + + if (txRequested) { + const eventCode = 'txAwaitingApproval' + + const newNotification = buildNotification(eventCode, txRequested.id) + addNotification(newNotification) + } + + // confirm reminder timeout defaults to 20 seconds + setTimeout(() => { + const awaitingApproval = notificationsArr.find( + tx => tx.id === id && tx.eventCode === 'txRequest' + ) + + if (awaitingApproval) { + const eventCode = 'txConfirmReminder' + + const newNotification = buildNotification(eventCode, awaitingApproval.id) + addNotification(newNotification) + } + }, reminderTimeout) + + const eventCode = 'txRequest' + addNotification(buildNotification(eventCode, id)) + + // if not provided with sendTransaction function, + // resolve with transaction hash(or void) so dev can initiate transaction + if (!sendTransaction) { + return id + } + // get result and handle errors + let hash + try { + hash = await sendTransaction() + } catch (error) { + type CatchError = { + message: string + stack: string + } + const { eventCode, errorMsg } = extractMessageFromError(error as CatchError) + + addNotification(buildNotification(eventCode, id)) + console.error(errorMsg) + return + } + + // Remove preflight notification if a resolves to hash + // and let the SDK take over + removeNotification(id) + if (hash) { + return hash + } + return +} + +const buildNotification = (eventCode: string, id: string): Notification => { + return { + eventCode, + type: eventToType(eventCode), + id, + key: createKey(id, eventCode), + message: createMessageText(eventCode), + startTime: Date.now(), + network: Object.keys(networkToChainId).find( + key => networkToChainId[key] === state.get().chains[0].id + ) as Network, + autoDismiss: 0 + } +} + +const createKey = (id: string, eventCode: string): string => { + return `${id}-${eventCode}` +} + +const createId = (id: string): string => { + return `${id}-preflight` +} + +const createMessageText = (eventCode: string): string => { + const notificationDefaultMessages = defaultCopy.notify + + const notificationMessageType = notificationDefaultMessages.transaction + + return notificationDefaultMessages.transaction[ + eventCode as keyof typeof notificationMessageType + ] +} + +export function extractMessageFromError(error: { + message: string + stack: string +}): { eventCode: string; errorMsg: string } { + if (!error.stack || !error.message) { + return { + eventCode: 'txError', + errorMsg: 'An unknown error occurred' + } + } + + const message = error.stack || error.message + + if (message.includes('User denied transaction signature')) { + return { + eventCode: 'txSendFail', + errorMsg: 'User denied transaction signature' + } + } + + if (message.includes('transaction underpriced')) { + return { + eventCode: 'txUnderpriced', + errorMsg: 'Transaction is under priced' + } + } + + return { + eventCode: 'txError', + errorMsg: message + } +} + +const gasEstimates = async ( + gasFunc: () => Promise, + gasPriceFunc: () => Promise +) => { + if (!gasFunc || !gasPriceFunc) { + return Promise.resolve([]) + } + + const gasProm = gasFunc() + if (!gasProm.then) { + throw new Error('The `estimateGas` function must return a Promise') + } + + const gasPriceProm = gasPriceFunc() + if (!gasPriceProm.then) { + throw new Error('The `gasPrice` function must return a Promise') + } + + return Promise.all([gasProm, gasPriceProm]) + .then(([gasResult, gasPriceResult]) => { + if (typeof gasResult !== 'string') { + throw new Error( + `The Promise returned from calling 'estimateGas' must resolve with a value of type 'string'. Received a value of: ${gasResult} with a type: ${typeof gasResult}` + ) + } + + if (typeof gasPriceResult !== 'string') { + throw new Error( + `The Promise returned from calling 'gasPrice' must resolve with a value of type 'string'. Received a value of: ${gasPriceResult} with a type: ${typeof gasPriceResult}` + ) + } + + return [BigInt(gasResult), BigInt(gasPriceResult)] + }) + .catch(error => { + throw new Error(`There was an error getting gas estimates: ${error}`) + }) +} diff --git a/packages/core-wagmi/src/provider.ts b/packages/core-wagmi/src/provider.ts new file mode 100644 index 000000000..5ea07cfae --- /dev/null +++ b/packages/core-wagmi/src/provider.ts @@ -0,0 +1,578 @@ +import { fromEventPattern, Observable } from 'rxjs' +import { filter, takeUntil, take, share, switchMap } from 'rxjs/operators' +import partition from 'lodash.partition' +import { isAddress, weiHexToEth } from '@web3-onboard/common' +import { disconnectWallet$ } from './streams.js' +import { updateAccount, updateWallet } from './store/actions.js' +import { chainIdToViemENSImport, validEnsChain } from './utils.js' +import disconnect from './disconnect.js' +import { state } from './store/index.js' +import { getBNMulitChainSdk } from './services/bnSDK.js' +import { configuration } from './configuration.js' +import { updateSecondaryTokens } from './update-balances' + +import type { Uns } from '@web3-onboard/unstoppable-resolution' +import type { PublicClient, GetEnsTextReturnType } from 'viem' +import type { + Address, + ChainId, + EIP1102Request, + EIP1193Provider, + ProviderAccounts, + Chain, + AccountsListener, + ChainListener, + SelectAccountsRequest +} from '@web3-onboard/common' + +import type { + Account, + Balances, + Ens, + WalletPermission, + WalletState +} from './types.js' + +export const viemProviders: { + [key: string]: PublicClient +} = {} + +async function getProvider(chain: Chain): Promise { + if (!chain) return null + + if (!viemProviders[chain.rpcUrl as string]) { + const viemChain = await chainIdToViemENSImport(chain.id) + if (!viemChain) return null + + const { createPublicClient, http } = await import('viem') + const publicProvider = createPublicClient({ + chain: viemChain, + transport: http() + }) + viemProviders[chain.rpcUrl as string] = publicProvider as PublicClient + } + + return viemProviders[chain.rpcUrl as string] +} + +export function requestAccounts( + provider: EIP1193Provider +): Promise { + const args = { method: 'eth_requestAccounts' } as EIP1102Request + return provider.request(args) +} + +export function selectAccounts( + provider: EIP1193Provider +): Promise { + const args = { method: 'eth_selectAccounts' } as SelectAccountsRequest + return provider.request(args) +} + +export function getChainId(provider: EIP1193Provider): Promise { + return provider.request({ method: 'eth_chainId' }) as Promise +} + +export function listenAccountsChanged(args: { + provider: EIP1193Provider + disconnected$: Observable +}): Observable { + const { provider, disconnected$ } = args + + const addHandler = (handler: AccountsListener) => { + provider.on('accountsChanged', handler) + } + + const removeHandler = (handler: AccountsListener) => { + provider.removeListener('accountsChanged', handler) + } + + return fromEventPattern(addHandler, removeHandler).pipe( + takeUntil(disconnected$) + ) +} + +export function listenChainChanged(args: { + provider: EIP1193Provider + disconnected$: Observable +}): Observable { + const { provider, disconnected$ } = args + const addHandler = (handler: ChainListener) => { + provider.on('chainChanged', handler) + } + + const removeHandler = (handler: ChainListener) => { + provider.removeListener('chainChanged', handler) + } + + return fromEventPattern(addHandler, removeHandler).pipe( + takeUntil(disconnected$) + ) +} + +export function trackWallet( + provider: EIP1193Provider, + label: WalletState['label'] +): void { + const disconnected$ = disconnectWallet$.pipe( + filter(wallet => wallet === label), + take(1) + ) + + const accountsChanged$ = listenAccountsChanged({ + provider, + disconnected$ + }).pipe(share()) + + // when account changed, set it to first account and subscribe to events + accountsChanged$.subscribe(async ([address]) => { + // sync accounts with internal state + // in the case of an account has been manually disconnected + try { + await syncWalletConnectedAccounts(label) + } catch (error) { + console.warn( + 'Web3Onboard: Error whilst trying to sync connected accounts:', + error + ) + } + + // no address, then no account connected, so disconnect wallet + // this could happen if user locks wallet, + // or if disconnects app from wallet + if (!address) { + disconnect({ label }) + return + } + + const { wallets } = state.get() + const wallet = wallets.find(wallet => wallet.label === label) + const accounts = wallet ? wallet.accounts : [] + + const [[existingAccount], restAccounts] = partition( + accounts, + account => account.address === address + ) + + // update accounts without ens/uns and balance first + updateWallet(label, { + accounts: [ + existingAccount || { + address: address as Address, + ens: null, + uns: null, + balance: null + }, + ...restAccounts + ] + }) + + // if not existing account and notifications, + // then subscribe to transaction events + if (state.get().notify.enabled && !existingAccount) { + const sdk = await getBNMulitChainSdk() + + if (sdk) { + const wallet = state + .get() + .wallets.find(wallet => wallet.label === label) + try { + if (wallet) { + sdk.subscribe({ + id: address, + chainId: wallet.chains[0]?.id, + type: 'account' + }) + } + } catch (error) { + // unsupported network for transaction events + } + } + } + }) + + // also when accounts change, update Balance and ENS/UNS + accountsChanged$ + .pipe( + switchMap(async ([address]) => { + if (!address) return + + const { wallets, chains } = state.get() + + const primaryWallet = wallets.find(wallet => wallet.label === label) + if (!primaryWallet) return // Add null check for primaryWallet + + const { chains: walletChains, accounts } = primaryWallet + + const [connectedWalletChain] = walletChains + + const chain = chains.find( + ({ namespace, id }) => + namespace === 'evm' && id === connectedWalletChain.id + ) + if (!chain) return + + const balanceProm = getBalance(address, chain) + const secondaryTokenBal = updateSecondaryTokens(address, chain) + const account = accounts.find(account => account.address === address) + + const ensChain = chains.find( + ({ id }) => id === validEnsChain(connectedWalletChain.id) + ) + + const ensProm = + account && account.ens + ? Promise.resolve(account.ens) + : ensChain + ? getEns(address, ensChain) + : Promise.resolve(null) + + const unsProm = + account && account.uns + ? Promise.resolve(account.uns) + : ensChain + ? getUns(address, ensChain) + : Promise.resolve(null) + console.log('ENS', await ensProm) + return Promise.all([ + Promise.resolve(address), + balanceProm, + ensProm, + unsProm, + secondaryTokenBal + ]) + }) + ) + .subscribe(res => { + if (!res) return + const [address, balance, ens, uns, secondaryTokens] = res + updateAccount(label, address, { balance, ens, uns, secondaryTokens }) + }) + + const chainChanged$ = listenChainChanged({ provider, disconnected$ }).pipe( + share() + ) + + // Update chain on wallet when chainId changed + chainChanged$.subscribe(async chainId => { + const { wallets } = state.get() + const wallet = wallets.find(wallet => wallet.label === label) + if (!wallet) return // Add null check for wallet + const { chains, accounts } = wallet + const [connectedWalletChain] = chains + + if (chainId === connectedWalletChain.id) return + + if (state.get().notify.enabled) { + const sdk = await getBNMulitChainSdk() + + if (sdk) { + const wallet = state + .get() + .wallets.find(wallet => wallet.label === label) + + if (!wallet) return // Add null check for wallet + + // Unsubscribe with timeout of 60 seconds + // to allow for any currently inflight transactions + wallet.accounts.forEach(({ address }) => { + sdk.unsubscribe({ + id: address, + chainId: wallet.chains[0].id, + timeout: 60000 + }) + }) + + // resubscribe for new chainId + wallet.accounts.forEach(({ address }) => { + try { + sdk.subscribe({ + id: address, + chainId: chainId, + type: 'account' + }) + } catch (error) { + // unsupported network for transaction events + } + }) + } + } + + const resetAccounts = accounts.map( + ({ address }) => + ({ + address, + ens: null, + uns: null, + balance: null + } as Account) + ) + + updateWallet(label, { + chains: [{ namespace: 'evm', id: chainId }], + accounts: resetAccounts + }) + }) + + // when chain changes get ens/uns and balance for each account for wallet + chainChanged$ + .pipe( + switchMap(async chainId => { + const { wallets, chains } = state.get() + const primaryWallet = wallets.find(wallet => wallet.label === label) + const accounts = primaryWallet?.accounts || [] + + const chain = chains.find( + ({ namespace, id }) => namespace === 'evm' && id === chainId + ) + if (!chain) return Promise.resolve(null) + + return Promise.all( + accounts.map(async ({ address }) => { + const balanceProm = getBalance(address, chain) + + const secondaryTokenBal = updateSecondaryTokens(address, chain) + const ensChain = chains.find( + ({ id }) => id === validEnsChain(chainId) + ) + + const ensProm = ensChain + ? getEns(address, ensChain) + : Promise.resolve(null) + + const unsProm = ensChain + ? getUns(address, ensChain) + : Promise.resolve(null) + + const [balance, ens, uns, secondaryTokens] = await Promise.all([ + balanceProm, + ensProm, + unsProm, + secondaryTokenBal + ]) + + return { + address, + balance, + ens, + uns, + secondaryTokens + } + }) + ) + }) + ) + .subscribe(updatedAccounts => { + updatedAccounts && updateWallet(label, { accounts: updatedAccounts }) + }) + + disconnected$.subscribe(() => { + provider.disconnect && provider.disconnect() + }) +} + +export async function getEns( + address: Address, + chain: Chain +): Promise { + // chain we don't recognize and don't have a rpcUrl for requests + if (!chain) return null + + const provider = await getProvider(chain) + if (!provider) return null + + try { + const name = await provider.getEnsName({ + address + }) + let ens = null + + if (name) { + const { labelhash, normalize } = await import('viem/ens') + const normalizedName = normalize(name) + + const ensResolver = await provider.getEnsResolver({ + name: normalizedName + }) + const avatar = await provider.getEnsAvatar({ + name: normalizedName + }) + const contentHash = labelhash(normalizedName) + const getText = async (key: string): Promise => { + return await provider.getEnsText({ + name, + key + }) + } + + ens = { + name, + avatar, + contentHash, + ensResolver, + getText + } + } + + return ens + } catch (error) { + console.error(error) + return null + } +} + +export async function getUns( + address: Address, + chain: Chain +): Promise { + const { unstoppableResolution } = configuration + + // check if address is valid ETH address before attempting to resolve + // chain we don't recognize and don't have a rpcUrl for requests + if (!unstoppableResolution || !isAddress(address) || !chain) return null + + try { + return await unstoppableResolution(address) + } catch (error) { + console.error(error) + return null + } +} + +export async function getBalance( + address: Address, + chain: Chain +): Promise { + // chain we don't recognize and don't have a rpcUrl for requests + if (!chain) return null + + const { wallets } = state.get() + + try { + const wallet = wallets.find(wallet => !!wallet.provider) + if (!wallet) return null + const provider = wallet.provider + const balanceHex = await provider.request({ + method: 'eth_getBalance', + params: [address, 'latest'] + }) + return balanceHex + ? { [chain.token || 'eth']: weiHexToEth(balanceHex) } + : null + } catch (error) { + console.error(error) + return null + } +} + +export function switchChain( + provider: EIP1193Provider, + chainId: ChainId +): Promise { + return provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId }] + }) +} + +export function addNewChain( + provider: EIP1193Provider, + chain: Chain +): Promise { + return provider.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: chain.id, + chainName: chain.label, + nativeCurrency: { + name: chain.label, + symbol: chain.token, + decimals: 18 + }, + rpcUrls: [chain.publicRpcUrl || chain.rpcUrl], + blockExplorerUrls: chain.blockExplorerUrl + ? [chain.blockExplorerUrl] + : undefined + } + ] + }) +} + +export function updateChainRPC( + provider: EIP1193Provider, + chain: Chain, + rpcUrl: string +): Promise { + return provider.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: chain.id, + chainName: chain.label, + nativeCurrency: { + name: chain.label, + symbol: chain.token, + decimals: 18 + }, + rpcUrls: [rpcUrl], + blockExplorerUrls: chain.blockExplorerUrl + ? [chain.blockExplorerUrl] + : undefined + } + ] + }) +} + +export async function getPermissions( + provider: EIP1193Provider +): Promise { + try { + const permissions = (await provider.request({ + method: 'wallet_getPermissions' + })) as WalletPermission[] + + return Array.isArray(permissions) ? permissions : [] + } catch (error) { + return [] + } +} + +export async function syncWalletConnectedAccounts( + label: WalletState['label'] +): Promise { + const wallet = state.get().wallets.find(wallet => wallet.label === label) + if (!wallet) return + const permissions = await getPermissions(wallet.provider) + const accountsPermissions = permissions.find( + ({ parentCapability }) => parentCapability === 'eth_accounts' + ) + + if (accountsPermissions) { + const { value: connectedAccounts } = accountsPermissions.caveats.find( + ({ type }) => type === 'restrictReturnedAccounts' + ) || { value: null } + + if (connectedAccounts) { + const syncedAccounts = wallet.accounts.filter(({ address }) => + connectedAccounts.includes(address) + ) + + updateWallet(wallet.label, { ...wallet, accounts: syncedAccounts }) + } + } +} + +export const addOrSwitchChain = async ( + provider: EIP1193Provider, + chain: Chain +): Promise => { + try { + const { id } = chain + await addNewChain(provider, chain) + await switchChain(provider, id) + return id + } catch (error) { + return + } +} diff --git a/packages/core-wagmi/src/replacement.ts b/packages/core-wagmi/src/replacement.ts new file mode 100644 index 000000000..542b70605 --- /dev/null +++ b/packages/core-wagmi/src/replacement.ts @@ -0,0 +1,91 @@ +import type { EthereumTransactionData, Network } from 'bnc-sdk' +import { bigIntToHex } from '@web3-onboard/common' +import { configuration } from './configuration.js' +import { state } from './store/index.js' +import type { WalletState } from './types.js' +import { gweiToWeiHex, networkToChainId, toHexString } from './utils.js' +import type { GasPrice } from '@web3-onboard/gas' + +const ACTIONABLE_EVENT_CODES: string[] = ['txPool'] +const VALID_GAS_NETWORKS: Network[] = ['main', 'matic-main'] + +const WALLETS_SUPPORT_REPLACEMENT: WalletState['label'][] = [ + 'Ledger', + 'Trezor', + 'Keystone', + 'KeepKey', + `D'CENT` +] + +export const actionableEventCode = (eventCode: string): boolean => + ACTIONABLE_EVENT_CODES.includes(eventCode) + +export const validGasNetwork = (network: Network): boolean => + VALID_GAS_NETWORKS.includes(network) + +export const walletSupportsReplacement = (wallet: WalletState): boolean => + wallet && WALLETS_SUPPORT_REPLACEMENT.includes(wallet.label) + +export async function replaceTransaction({ + type, + wallet, + transaction +}: { + type: 'speedup' | 'cancel' + wallet: WalletState + transaction: EthereumTransactionData +}): Promise { + const { from, input, value, to, nonce, gas: gasLimit, network } = transaction + + const chainId = networkToChainId[network] + + const { gasPriceProbability } = state.get().notify.replacement as { + gasPriceProbability?: + | { speedup?: number | undefined; cancel?: number | undefined } + | undefined + } + + const { gas, apiKey } = configuration + if (!gas) return + // get gas price + const [gasResult] = await gas.get({ + chains: [networkToChainId[network]], + endpoint: 'blockPrices', + apiKey + }) + + const { maxFeePerGas, maxPriorityFeePerGas } = + (gasResult.blockPrices[0].estimatedPrices.find( + ({ confidence }) => + confidence === + (type === 'speedup' + ? gasPriceProbability?.speedup + : gasPriceProbability?.cancel) + ) as GasPrice) || {} + + if (!maxFeePerGas || !maxPriorityFeePerGas) return + + const maxFeePerGasWeiHex = gweiToWeiHex(maxFeePerGas) + const maxPriorityFeePerGasWeiHex = gweiToWeiHex(maxPriorityFeePerGas) + + // Some wallets do not like empty '0x' val + const dataObj = input === '0x' ? {} : { data: input } + + return wallet.provider.request({ + method: 'eth_sendTransaction', + params: [ + { + type: '0x2', + from, + to: type === 'cancel' ? from : to, + chainId: parseInt(chainId), + value: bigIntToHex(BigInt(value)), + nonce: toHexString(nonce), + gasLimit: toHexString(gasLimit), + maxFeePerGas: maxFeePerGasWeiHex, + maxPriorityFeePerGas: maxPriorityFeePerGasWeiHex, + ...dataObj + } + ] + }) +} diff --git a/packages/core-wagmi/src/services/bnSDK.ts b/packages/core-wagmi/src/services/bnSDK.ts new file mode 100644 index 000000000..a8c4674ca --- /dev/null +++ b/packages/core-wagmi/src/services/bnSDK.ts @@ -0,0 +1,50 @@ +import type { MultiChain } from 'bnc-sdk' +import type SDK from 'bnc-sdk' +import { configuration } from '../configuration.js' +import { handleTransactionUpdates } from '../notify.js' + +let blocknativeMultiChainSdk: MultiChain +let blocknativeSdk: SDK + +/** + * + * @returns MultiChain SDK if apiKey + */ + +export async function getBNMulitChainSdk(): Promise { + const { apiKey } = configuration + + if (!apiKey) return null + + if (!blocknativeMultiChainSdk) { + const { default: Blocknative } = await import('bnc-sdk') + blocknativeMultiChainSdk = Blocknative.multichain({ + apiKey: configuration.apiKey ?? '' + }) + + blocknativeMultiChainSdk.transactions$.subscribe(handleTransactionUpdates) + } + + return blocknativeMultiChainSdk +} + +/** + * + * @returns SDK if apiKey + */ +export async function getBlocknativeSdk(): Promise { + const { apiKey } = configuration + + if (!apiKey) return null + + if (!blocknativeSdk) { + const { default: Blocknative } = await import('bnc-sdk') + blocknativeSdk = new Blocknative({ + dappId: configuration.apiKey ?? '', + networkId: 1 + }) + return blocknativeSdk + } + + return blocknativeSdk +} diff --git a/packages/core-wagmi/src/services/wagmi.ts b/packages/core-wagmi/src/services/wagmi.ts new file mode 100644 index 000000000..1fc741bd1 --- /dev/null +++ b/packages/core-wagmi/src/services/wagmi.ts @@ -0,0 +1,194 @@ +import type { + Config, + ConnectReturnType, + Connector, + CreateConnectorFn +} from '@wagmi/core' +import { state } from '../store' +import { http, createConfig, createConnector, connect } from '@wagmi/core' +import { type Chain as ViemChain, type Transport } from 'viem' +import type { Chain, EIP1193Provider, WalletModule } from '@web3-onboard/common' +import { chainIdToViemImport } from '../utils' +import { addOrSwitchChain, getChainId, requestAccounts } from '../provider' +import EventEmitter from 'eventemitter3' +import { updateWagmiConfig } from '../store/actions' + +export let wagmiConfig: Config | undefined + +export async function initializeWAGMI( + walletModules: WalletModule[] +): Promise { + const { chains, appMetadata } = state.get() + const connectors = await walletModules.reduce(async (acc, wallet) => { + const result = await acc + const { label, getInterface } = wallet + try { + console.log(label, wallet) + const provider = await getInterface({ + chains, + EventEmitter, + appMetadata + }) + if (provider) { + console.log(label, provider) + const wagmiConnector = await createWagmiConnector( + label, + provider.provider + ) + result.push(wagmiConnector) + } + } catch (e) { + console.warn(`Error creating wagmi connector for wallet: ${wallet.label} + - Wallet may not be installed or available to the browser. Error: ${e}`) + } + return result + }, Promise.resolve([])) + + const transports: Record = {} + + const viemChains = (await Promise.all( + chains.map(async (w3OChain: Chain) => { + const { id } = w3OChain + transports[parseInt(id, 16)] = http() + return (await chainIdToViemImport(w3OChain)) as ViemChain + }) + )) as [ViemChain, ...ViemChain[]] + + try { + wagmiConfig = createConfig({ + chains: [...viemChains], + transports, + multiInjectedProviderDiscovery: false, + connectors: await Promise.all(connectors) // Await the connectors array + }) + updateWagmiConfig(wagmiConfig) + } catch (e) { + console.error( + `Failed to initialize Web3-Onboard WAGMI instance - Error: ${e}` + ) + } +} + +export async function createWagmiConnector( + label: string, + provider: EIP1193Provider +): Promise { + try { + return createConnector(() => + ({ + name: label, + id: label.split(' ').join(), + connect: () => + Promise.all([requestAccounts(provider), getChainId(provider)]).then( + ([accounts, chainId]) => { + return { + chainId: parseInt(chainId, 16), + accounts: accounts as `0x${string}`[] + } + } + ), + disconnect: () => Promise.resolve(provider.disconnect()), + getAccounts: () => + requestAccounts(provider).then(acc => { + return acc + }), + getChainId: () => + getChainId(provider).then(chainId => { + console.log('chainId', chainId) + return parseInt(chainId, 16) + }), + getProvider: () => Promise.resolve(provider), + isAuthorized: () => + requestAccounts(provider).then(accounts => { + console.log('accounts', accounts) + return !!accounts.length + }), + switchChain: ({ chainId }: { chainId: number }) => { + try { + return addOrSwitchChain(provider, chainId.toString(16)).then( + id => { + getChainId(provider).then(chainId => { + console.log('chainId', chainId) + return parseInt(chainId, 16) + }) + } + ) + } catch (error) { + console.log('error switching chain', error) + } + }, + + onAccountsChanged: (accounts: string[]) => { + // Disconnect if there are no accounts + if (accounts.length === 0) provider.disconnect() + }, + onChainChanged: () => {}, + onDisconnect: () => {}, + emitter: new EventEmitter() + } as unknown as Connector) + ) + } catch (e) { + console.error('error creating connector', e) + } +} + +export async function connectWalletToWagmi( + label: string, + provider: EIP1193Provider +): Promise> { + try { + return await connect(wagmiConfig, { + connector: { + name: label, + id: label.split(' ').join(), + connect: () => + Promise.all([requestAccounts(provider), getChainId(provider)]).then( + ([accounts, chainId]) => { + return { + chainId: parseInt(chainId, 16), + accounts: accounts as `0x${string}`[] + } + } + ), + disconnect: () => Promise.resolve(provider.disconnect()), + getAccounts: () => + requestAccounts(provider).then(acc => { + return acc + }), + getChainId: () => + getChainId(provider).then(chainId => { + console.log('chainId', chainId) + return parseInt(chainId, 16) + }), + getProvider: () => Promise.resolve(provider), + isAuthorized: () => + requestAccounts(provider).then(accounts => { + console.log('accounts', accounts) + return !!accounts.length + }), + switchChain: ({ chainId }: { chainId: number }) => { + try { + return addOrSwitchChain(provider, chainId.toString(16)).then(id => { + getChainId(provider).then(chainId => { + console.log('chainId', chainId) + return parseInt(chainId, 16) + }) + }) + } catch (error) { + console.log('error switching chain', error) + } + }, + + onAccountsChanged: (accounts: string[]) => { + // Disconnect if there are no accounts + if (accounts.length === 0) provider.disconnect() + }, + onChainChanged: () => {}, + onDisconnect: () => {}, + emitter: new EventEmitter() + } as unknown as Connector + }) + } catch (e) { + console.error('error connecting wallet to wagmi', e) + } +} diff --git a/packages/core-wagmi/src/store/actions.ts b/packages/core-wagmi/src/store/actions.ts new file mode 100644 index 000000000..c54fedfae --- /dev/null +++ b/packages/core-wagmi/src/store/actions.ts @@ -0,0 +1,491 @@ +import type { + AppMetadata, + Chain, + WalletHelpers, + WalletInit, + WalletModule +} from '@web3-onboard/common' +import { nanoid } from 'nanoid' +import { dispatch } from './index.js' +import { configuration } from '../configuration.js' +import { handleThemeChange, returnTheme } from '../themes.js' + +import type { + Account, + AddChainsAction, + AddWalletAction, + AccountCenter, + RemoveWalletAction, + ResetStoreAction, + SetWalletModulesAction, + SetLocaleAction, + UpdateAccountAction, + UpdateAccountCenterAction, + UpdateWalletAction, + WalletState, + UpdateNotifyAction, + Notification, + AddNotificationAction, + RemoveNotificationAction, + UpdateAllWalletsAction, + CustomNotification, + UpdateNotification, + CustomNotificationUpdate, + Notify, + ConnectModalOptions, + UpdateConnectModalAction, + Theme, + UpdateChainsAction, + UpdateAppMetadataAction, + UpdateWagmiConfigAction +} from '../types.js' + +import { + validateAccountCenterUpdate, + validateLocale, + validateNotification, + validateCustomNotification, + validateCustomNotificationUpdate, + validateString, + validateWallet, + validateWalletInit, + validateUpdateBalances, + validateNotify, + validateConnectModalUpdate, + validateUpdateTheme, + validateSetChainOptions, + validateAppMetadataUpdate +} from '../validation.js' + +import { + ADD_CHAINS, + UPDATE_WALLET, + RESET_STORE, + ADD_WALLET, + REMOVE_WALLET, + UPDATE_ACCOUNT, + UPDATE_ACCOUNT_CENTER, + UPDATE_NOTIFY, + SET_WALLET_MODULES, + SET_LOCALE, + ADD_NOTIFICATION, + REMOVE_NOTIFICATION, + UPDATE_ALL_WALLETS, + UPDATE_CONNECT_MODAL, + UPDATE_CHAINS, + UPDATE_APP_METADATA, + UPDATE_WAGMI_CONFIG +} from './constants.js' +import type { Address } from 'bnc-sdk' +import type { Config } from '@wagmi/core' + +export function addChains(chains: Chain[]): void { + // chains are validated on init + const action = { + type: ADD_CHAINS, + payload: chains.map(({ namespace = 'evm', id, rpcUrl, ...rest }) => ({ + ...rest, + namespace, + id: id.toLowerCase(), + rpcUrl: rpcUrl ? rpcUrl.trim() : null + })) + } + + dispatch(action as AddChainsAction) +} + +export function updateChain(updatedChain: Chain): void { + const { + label, + token, + rpcUrl, + id: chainId, + namespace: chainNamespace + } = updatedChain + const error = validateSetChainOptions({ + label, + token, + rpcUrl, + chainId, + chainNamespace + }) + + if (error) { + throw error + } + const action = { + type: UPDATE_CHAINS, + payload: updatedChain + } + dispatch(action as UpdateChainsAction) +} + +export function addWallet(wallet: WalletState): void { + const error = validateWallet(wallet) + + if (error) { + console.error(error) + throw error + } + + const action = { + type: ADD_WALLET, + payload: wallet + } + + dispatch(action as AddWalletAction) +} + +export function updateWallet(id: string, update: Partial): void { + const error = validateWallet(update) + + if (error) { + console.error(error) + throw error + } + + const action = { + type: UPDATE_WALLET, + payload: { + id, + ...update + } + } + + dispatch(action as UpdateWalletAction) +} + +export function removeWallet(id: string): void { + const error = validateString(id, 'wallet id') + + if (error) { + throw error + } + + const action = { + type: REMOVE_WALLET, + payload: { + id + } + } + + dispatch(action as RemoveWalletAction) +} + +export function setPrimaryWallet(wallet: WalletState, address?: string): void { + const error = + validateWallet(wallet) || (address && validateString(address, 'address')) + + if (error) { + throw error + } + + // if also setting the primary account + if (address) { + const account = wallet.accounts.find(ac => ac.address === address) + + if (account) { + wallet.accounts = [ + account, + ...wallet.accounts.filter(({ address }) => address !== account.address) + ] + } + } + + // add wallet will set it to first wallet since it already exists + addWallet(wallet) +} + +export function updateAccount( + id: string, + address: Address, + update: Partial +): void { + const action = { + type: UPDATE_ACCOUNT, + payload: { + id, + address, + ...update + } + } + + dispatch(action as UpdateAccountAction) +} + +export function updateAccountCenter( + update: AccountCenter | Partial +): void { + const error = validateAccountCenterUpdate(update) + + if (error) { + throw error + } + + const action = { + type: UPDATE_ACCOUNT_CENTER, + payload: update + } + + dispatch(action as UpdateAccountCenterAction) +} + +export function updateConnectModal( + update: ConnectModalOptions | Partial +): void { + const error = validateConnectModalUpdate(update) + + if (error) { + throw error + } + + const action = { + type: UPDATE_CONNECT_MODAL, + payload: update + } + + dispatch(action as UpdateConnectModalAction) +} + +export function updateNotify(update: Partial): void { + const error = validateNotify(update) + + if (error) { + throw error + } + + const action = { + type: UPDATE_NOTIFY, + payload: update + } + + dispatch(action as UpdateNotifyAction) +} + +export function addNotification(notification: Notification): void { + const error = validateNotification(notification) + + if (error) { + throw error + } + + const action = { + type: ADD_NOTIFICATION, + payload: notification + } + + dispatch(action as AddNotificationAction) +} + +export function addCustomNotification( + notification: CustomNotificationUpdate +): void { + const customNotificationError = validateCustomNotificationUpdate(notification) + + if (customNotificationError) { + throw customNotificationError + } + + const action = { + type: ADD_NOTIFICATION, + payload: notification + } + + dispatch(action as AddNotificationAction) +} + +export function customNotification(updatedNotification: CustomNotification): { + dismiss: () => void + update: UpdateNotification +} { + const customNotificationError = + validateCustomNotification(updatedNotification) + + if (customNotificationError) { + throw customNotificationError + } + + const customIdKey = `customNotification-${nanoid()}` + const notification: CustomNotificationUpdate = { + ...updatedNotification, + id: customIdKey, + key: customIdKey + } + addCustomNotification(notification) + + const dismiss = () => { + if (notification.id) { + removeNotification(notification.id) + } + } + + const update = ( + notificationUpdate: CustomNotification + ): { + dismiss: () => void + update: UpdateNotification + } => { + const customNotificationError = + validateCustomNotification(updatedNotification) + + if (customNotificationError) { + throw customNotificationError + } + + const notificationAfterUpdate: CustomNotificationUpdate = { + ...notificationUpdate, + id: notification.id, + key: notification.key + } + addCustomNotification(notificationAfterUpdate) + + return { + dismiss, + update + } + } + + addCustomNotification(notification) + + return { + dismiss, + update + } +} + +export function removeNotification(id: Notification['id']): void { + if (typeof id !== 'string') { + throw new Error('Notification id must be of type string') + } + + const action = { + type: REMOVE_NOTIFICATION, + payload: id + } + + dispatch(action as RemoveNotificationAction) +} + +export function resetStore(): void { + const action = { + type: RESET_STORE + } + + dispatch(action as ResetStoreAction) +} + +export function setWalletModules(wallets: WalletInit[]): void { + const error = validateWalletInit(wallets) + + if (error) { + throw error + } + + const modules = initializeWalletModules(wallets) + const dedupedWallets = uniqueWalletsByLabel(modules) + + const action = { + type: SET_WALLET_MODULES, + payload: dedupedWallets + } + + dispatch(action as SetWalletModulesAction) +} + +export function setLocale(locale: string): void { + const error = validateLocale(locale) + + if (error) { + throw error + } + + const action = { + type: SET_LOCALE, + payload: locale + } + + dispatch(action as SetLocaleAction) +} + +export function updateAllWallets(wallets: WalletState[]): void { + const error = validateUpdateBalances(wallets) + + if (error) { + throw error + } + + const action = { + type: UPDATE_ALL_WALLETS, + payload: wallets + } + + dispatch(action as UpdateAllWalletsAction) +} + +// ==== HELPERS ==== // +export function initializeWalletModules(modules: WalletInit[]): WalletModule[] { + const { device }: WalletHelpers = configuration + if (!device) return [] + return modules.reduce((acc, walletInit) => { + const initialized = walletInit({ device }) + + if (initialized) { + // injected wallets is an array of wallets + acc.push(...(Array.isArray(initialized) ? initialized : [initialized])) + } + + return acc + }, [] as WalletModule[]) +} + +export function uniqueWalletsByLabel( + walletModuleList: WalletModule[] +): WalletModule[] { + return walletModuleList.filter( + (wallet, i) => + wallet && + walletModuleList.findIndex( + (innerWallet: WalletModule) => + innerWallet && innerWallet.label === wallet.label + ) === i + ) +} + +export function updateTheme(theme: Theme): void { + const error = validateUpdateTheme(theme) + if (error) { + throw error + } + + const themingObj = returnTheme(theme) + themingObj && handleThemeChange(themingObj) +} + +export function updateAppMetadata( + update: AppMetadata | Partial +): void { + const error = validateAppMetadataUpdate(update) + + if (error) { + throw error + } + + const action = { + type: UPDATE_APP_METADATA, + payload: update + } + + dispatch(action as UpdateAppMetadataAction) +} + +export function updateWagmiConfig( + update: Config +): void { + + const action = { + type: UPDATE_WAGMI_CONFIG, + payload: update + } + + dispatch(action as UpdateWagmiConfigAction) +} diff --git a/packages/core-wagmi/src/store/constants.ts b/packages/core-wagmi/src/store/constants.ts new file mode 100644 index 000000000..af0608d1d --- /dev/null +++ b/packages/core-wagmi/src/store/constants.ts @@ -0,0 +1,17 @@ +export const ADD_CHAINS = 'add_chains' +export const UPDATE_CHAINS = 'update_chains' +export const RESET_STORE = 'reset_store' +export const ADD_WALLET = 'add_wallet' +export const UPDATE_WALLET = 'update_wallet' +export const REMOVE_WALLET = 'remove_wallet' +export const UPDATE_ACCOUNT = 'update_account' +export const UPDATE_ACCOUNT_CENTER = 'update_account_center' +export const UPDATE_CONNECT_MODAL = 'update_connect_modal' +export const SET_WALLET_MODULES = 'set_wallet_modules' +export const SET_LOCALE = 'set_locale' +export const UPDATE_NOTIFY = 'update_notify' +export const ADD_NOTIFICATION = 'add_notification' +export const REMOVE_NOTIFICATION = 'remove_notification' +export const UPDATE_ALL_WALLETS = 'update_balance' +export const UPDATE_APP_METADATA = 'update_app_metadata' +export const UPDATE_WAGMI_CONFIG = 'update_wagmi_config' diff --git a/packages/core-wagmi/src/store/index.ts b/packages/core-wagmi/src/store/index.ts new file mode 100644 index 000000000..7a97032cf --- /dev/null +++ b/packages/core-wagmi/src/store/index.ts @@ -0,0 +1,294 @@ +import { BehaviorSubject, Subject, Observable } from 'rxjs' +import { distinctUntilKeyChanged, pluck, filter } from 'rxjs/operators' +import { locale } from 'svelte-i18n' +import { APP_INITIAL_STATE } from '../constants.js' +import { notNullish } from '../utils.js' +import type { Chain, WalletModule } from '@web3-onboard/common' + +import type { + AppState, + WalletState, + Action, + UpdateWalletAction, + AddWalletAction, + UpdateAccountAction, + UpdateAccountCenterAction, + Locale, + UpdateNotifyAction, + AddNotificationAction, + RemoveNotificationAction, + UpdateAllWalletsAction, + UpdateConnectModalAction, + UpdateChainsAction, + UpdateAppMetadataAction, + UpdateWagmiConfigAction +} from '../types.js' + +import { + ADD_CHAINS, + ADD_WALLET, + UPDATE_WALLET, + REMOVE_WALLET, + RESET_STORE, + UPDATE_ACCOUNT, + UPDATE_CONNECT_MODAL, + UPDATE_ACCOUNT_CENTER, + UPDATE_NOTIFY, + SET_WALLET_MODULES, + SET_LOCALE, + ADD_NOTIFICATION, + REMOVE_NOTIFICATION, + UPDATE_ALL_WALLETS, + UPDATE_CHAINS, + UPDATE_APP_METADATA, + UPDATE_WAGMI_CONFIG +} from './constants.js' + +function reducer(state: AppState, action: Action): AppState { + const { type, payload } = action + + switch (type) { + case ADD_CHAINS: + return { + ...state, + chains: [...state.chains, ...(payload as Chain[])] + } + + case UPDATE_CHAINS: { + const updatedChain = payload as UpdateChainsAction['payload'] + const chains = state.chains + const index = chains.findIndex((chain) => chain.id === updatedChain.id) + chains[index] = updatedChain + return { + ...state, + chains + } + } + + case ADD_WALLET: { + const wallet = payload as AddWalletAction['payload'] + const existingWallet = state.wallets.find( + ({ label }) => label === wallet.label + ) + + return { + ...state, + wallets: [ + // add to front of wallets as it is now the primary wallet + existingWallet || (payload as WalletState), + // filter out wallet if it already existed + ...state.wallets.filter(({ label }) => label !== wallet.label) + ] + } + } + + case UPDATE_WALLET: { + const update = payload as UpdateWalletAction['payload'] + const { id, ...walletUpdate } = update + + const updatedWallets = state.wallets.map(wallet => + wallet.label === id ? { ...wallet, ...walletUpdate } : wallet + ) + + return { + ...state, + wallets: updatedWallets + } + } + + case REMOVE_WALLET: { + const update = payload as { id: string } + + return { + ...state, + wallets: state.wallets.filter(({ label }) => label !== update.id) + } + } + + case UPDATE_ACCOUNT: { + const update = payload as UpdateAccountAction['payload'] + const { id, address, ...accountUpdate } = update + + const updatedWallets = state.wallets.map(wallet => { + if (wallet.label === id) { + wallet.accounts = wallet.accounts.map(account => { + if (account.address === address) { + return { ...account, ...accountUpdate } + } + + return account + }) + } + + return wallet + }) + + return { + ...state, + wallets: updatedWallets + } + } + + case UPDATE_ALL_WALLETS: { + const updatedWallets = payload as UpdateAllWalletsAction['payload'] + return { + ...state, + wallets: updatedWallets + } + } + + case UPDATE_CONNECT_MODAL: { + const update = payload as UpdateConnectModalAction['payload'] + + return { + ...state, + connect: { + ...state.connect, + ...update + } + } + } + + case UPDATE_ACCOUNT_CENTER: { + const update = payload as UpdateAccountCenterAction['payload'] + + return { + ...state, + accountCenter: { + ...state.accountCenter, + ...update + } + } + } + + case UPDATE_NOTIFY: { + const update = payload as UpdateNotifyAction['payload'] + + return { + ...state, + notify: { + ...state.notify, + ...update + } + } + } + + case ADD_NOTIFICATION: { + const update = payload as AddNotificationAction['payload'] + const notificationsUpdate = [...state.notifications] + + const notificationExistsIndex = notificationsUpdate.findIndex( + ({ id }) => id === update.id + ) + + if (notificationExistsIndex !== -1) { + // if notification with same id, replace it with update + notificationsUpdate[notificationExistsIndex] = update + } else { + // otherwise add it to the beginning of array as new notification + notificationsUpdate.unshift(update) + } + + return { + ...state, + notifications: notificationsUpdate + } + } + + case REMOVE_NOTIFICATION: { + const id = payload as RemoveNotificationAction['payload'] + + return { + ...state, + notifications: state.notifications.filter( + notification => notification.id !== id + ) + } + } + + case SET_WALLET_MODULES: { + return { + ...state, + walletModules: payload as WalletModule[] + } + } + + case SET_LOCALE: { + // Set the locale in the svelte-i18n internal state + locale.set(payload as Locale) + return { + ...state, + locale: payload as Locale + } + } + + case UPDATE_APP_METADATA: { + const update = payload as UpdateAppMetadataAction['payload'] + + return { + ...state, + appMetadata: { + ...state.appMetadata, + ...update, + name: update.name || '' + } + } + } + + case UPDATE_WAGMI_CONFIG: { + const update = payload as UpdateWagmiConfigAction['payload'] + + return { + ...state, + wagmiConfig: update + } + } + + case RESET_STORE: + return APP_INITIAL_STATE + + default: + throw new Error(`Unknown type: ${type} in appStore reducer`) + } +} + +const _store = new BehaviorSubject(APP_INITIAL_STATE) +const _stateUpdates = new Subject() + +_stateUpdates.subscribe(_store) + +export function dispatch(action: Action): void { + const state = _store.getValue() + _stateUpdates.next(reducer(state, action)) +} + +function select(): Observable +function select(stateKey: T): Observable +function select( + stateKey?: keyof AppState +): Observable | Observable { + if (!stateKey) return _stateUpdates.asObservable() + + const validStateKeys = Object.keys(_store.getValue()) + + if (!validStateKeys.includes(String(stateKey))) { + throw new Error(`key: ${stateKey} does not exist on this store`) + } + + return _stateUpdates + .asObservable() + .pipe( + distinctUntilKeyChanged(stateKey), + pluck(stateKey), + filter(notNullish) + ) as Observable +} + +function get(): AppState { + return _store.getValue() +} + +export const state = { + select, + get +} diff --git a/packages/core-wagmi/src/streams.ts b/packages/core-wagmi/src/streams.ts new file mode 100644 index 000000000..7fc43acc1 --- /dev/null +++ b/packages/core-wagmi/src/streams.ts @@ -0,0 +1,99 @@ +import type { Chain } from '@web3-onboard/common' +import { onDestroy, onMount, beforeUpdate, afterUpdate } from 'svelte' +import { Observable, Subject, defer, BehaviorSubject } from 'rxjs' +import { + take, + takeUntil, + withLatestFrom, + pluck, + shareReplay +} from 'rxjs/operators' + +import { resetStore } from './store/actions.js' +import { state } from './store/index.js' + +import type { WalletState, ConnectOptions } from './types.js' +import type { EthereumTransactionData } from 'bnc-sdk' + +export const reset$ = new Subject() +export const disconnectWallet$ = new Subject() + +export const connectWallet$ = new BehaviorSubject<{ + autoSelect?: ConnectOptions['autoSelect'] + actionRequired?: string + inProgress: boolean +}>({ inProgress: false, actionRequired: '' }) + +export const switchChainModal$ = new BehaviorSubject(null) + +export const wallets$ = ( + state.select('wallets') as Observable +).pipe(shareReplay(1)) + +// reset logic +reset$.pipe(withLatestFrom(wallets$), pluck('1')).subscribe(wallets => { + // disconnect all wallets + wallets.forEach(({ label }) => { + disconnectWallet$.next(label) + }) + + resetStore() +}) + +// keep transactions for all notifications for replacement actions +export const transactions$ = new BehaviorSubject([]) + +export function updateTransaction(tx: EthereumTransactionData): void { + const currentTransactions = transactions$.getValue() + + const txIndex = currentTransactions.findIndex(({ hash }) => hash === tx.hash) + + if (txIndex !== -1) { + const updatedTransactions = currentTransactions.map((val, i) => + i === txIndex ? tx : val + ) + + transactions$.next(updatedTransactions) + } else { + transactions$.next([...currentTransactions, tx]) + } +} + +export function removeTransaction(hash: string): void { + const currentTransactions = transactions$.getValue() + transactions$.next(currentTransactions.filter(tx => tx.hash !== hash)) +} + +export const onMount$ = defer(() => { + const subject = new Subject() + onMount(() => { + subject.next() + }) + return subject.asObservable().pipe(take(1)) +}) + +export const onDestroy$ = defer(() => { + const subject = new Subject() + onDestroy(() => { + subject.next() + }) + return subject.asObservable().pipe(take(1)) +}) + +export const afterUpdate$ = defer(() => { + const subject = new Subject() + afterUpdate(() => { + subject.next() + }) + return subject.asObservable().pipe(takeUntil(onDestroy$)) +}) + +export const beforeUpdate$ = defer(() => { + const subject = new Subject() + beforeUpdate(() => { + subject.next() + }) + return subject.asObservable().pipe(takeUntil(onDestroy$)) +}) diff --git a/packages/core-wagmi/src/themes.ts b/packages/core-wagmi/src/themes.ts new file mode 100644 index 000000000..480bf3249 --- /dev/null +++ b/packages/core-wagmi/src/themes.ts @@ -0,0 +1,74 @@ +import { fromEvent, takeUntil } from 'rxjs' +import { reset$ } from './streams' +import type { BuiltInThemes, Theme, ThemingMap } from './types' + +export const themes = { + default: { + '--w3o-background-color': 'unset', + '--w3o-foreground-color': 'unset', + '--w3o-text-color': 'unset', + '--w3o-border-color': 'unset', + '--w3o-action-color': 'unset', + '--w3o-border-radius': 'unset', + '--w3o-font-family': 'inherit' + }, + light: { + '--w3o-background-color': '#ffffff', + '--w3o-foreground-color': '#EFF1FC', + '--w3o-text-color': '#1a1d26', + '--w3o-border-color': '#d0d4f7', + '--w3o-action-color': '#6370E5', + '--w3o-border-radius': '16px', + '--w3o-font-family': 'inherit' + }, + dark: { + '--w3o-background-color': '#1A1D26', + '--w3o-foreground-color': '#242835', + '--w3o-text-color': '#EFF1FC', + '--w3o-border-color': '#33394B', + '--w3o-action-color': '#929bed', + '--w3o-border-radius': '16px', + '--w3o-font-family': 'inherit' + } +} + +export const returnTheme = (theme: Theme): void | ThemingMap => { + if (typeof theme === 'string' && theme === 'system') { + return watchForSystemThemeChange() + } + return returnThemeMap(theme) +} + +export const returnThemeMap = (theme: Theme): void | ThemingMap => { + if (typeof theme === 'string' && theme in themes) { + return themes[theme as BuiltInThemes] + } + if (typeof theme === 'object') { + return theme + } +} + +export const handleThemeChange = (update: ThemingMap): void => { + Object.keys(update).forEach(targetStyle => { + document.documentElement.style.setProperty( + targetStyle, + update[targetStyle as keyof ThemingMap] || null + ) + }) +} + +export const watchForSystemThemeChange = (): void => { + const systemThemeDark = window.matchMedia('(prefers-color-scheme: dark)') + systemThemeDark.matches + ? handleThemeChange(themes['dark']) + : handleThemeChange(themes['light']) + + fromEvent(systemThemeDark, 'change') + .pipe(takeUntil(reset$)) + .subscribe((changes: Event) => { + const themeChange = changes as MediaQueryListEvent + themeChange.matches + ? handleThemeChange(themes['dark']) + : handleThemeChange(themes['light']) + }) +} diff --git a/packages/core-wagmi/src/types.ts b/packages/core-wagmi/src/types.ts new file mode 100644 index 000000000..7ecace4db --- /dev/null +++ b/packages/core-wagmi/src/types.ts @@ -0,0 +1,568 @@ +import type { SvelteComponent } from 'svelte' + +import type { + AppMetadata, + Address, + Device, + WalletInit, + EIP1193Provider, + WalletModule, + Chain, + TokenSymbol, + ChainWithDecimalId, + DeviceNotBrowser +} from '@web3-onboard/common' + +import type gas from '@web3-onboard/gas' +import type unstoppableResolution from '@web3-onboard/unstoppable-resolution' +import type { TransactionPreviewAPI } from '@web3-onboard/transaction-preview' + +import type en from './i18n/en.json' +import type { EthereumTransactionData, Network } from 'bnc-sdk' +import type { GetEnsTextReturnType } from 'viem' +import type { Config } from '@wagmi/core' + +export interface InitOptions { + /** + * Wallet modules to be initialized and added to wallet selection modal + */ + wallets: WalletInit[] + /** + * The chains that your app works with + */ + chains: (Chain | ChainWithDecimalId)[] + /** + * Additional metadata about your app to be displayed in the Onboard UI + */ + appMetadata?: AppMetadata + /** + * Define custom copy for the 'en' locale or add locales to i18n your app + */ + i18n?: i18nOptions + /** + * Customize the connect modal + */ + connect?: ConnectModalOptions + /** + * Customize the account center UI + */ + accountCenter?: AccountCenterOptions + /** + * Opt in to Blocknative value add services (transaction updates) by providing + * your Blocknative API key, head to https://explorer.blocknative.com/account + */ + apiKey?: string + /** + * Transaction notification options + */ + notify?: Partial | Partial + /** Gas module */ + gas?: typeof gas + /** + * Object mapping for W3O components with the key being the DOM + * element to mount the component to, this defines the DOM container + * element for svelte to attach the component + */ + containerElements?: Partial + /** + * Transaction Preview module + */ + transactionPreview?: TransactionPreviewAPI + /** + * Custom or predefined theme for Web3Onboard + * BuiltInThemes: ['default', 'dark', 'light', 'system'] + * or customize with a ThemingMap object. + */ + theme?: Theme + /** + * Defaults to False - use to reduce load time + * If set to true the Inter font will not be imported and + * instead the default 'sans-serif' font will be used + * To define the font used see `--w3o-font-family` prop within + * the Theme initialization object or set as css variable + */ + disableFontDownload?: boolean + /** + * Type of unstoppableResolution module + * A small module that can bee added to allow Unstoppable Domain + * address resolution similar to that of ens (Ethereum Name Service) + * ENS resolution will take president if available + */ + unstoppableResolution?: typeof unstoppableResolution +} + +export type Theme = ThemingMap | BuiltInThemes | 'system' + +export type BuiltInThemes = 'default' | 'dark' | 'light' + +export type ThemingMap = { + '--w3o-background-color'?: string + '--w3o-font-family'?: string + '--w3o-foreground-color'?: string + '--w3o-text-color'?: string + '--w3o-border-color'?: string + '--w3o-action-color'?: string + '--w3o-border-radius'?: string +} +export interface ConnectOptions { + autoSelect?: { label: string; disableModals: boolean } +} + +export interface ConnectOptionsString { + autoSelect?: string +} + +export interface DisconnectOptions { + label: string // wallet name to disconnect +} + +export interface WalletWithLoadedIcon extends Omit { + icon: string +} + +export interface WalletWithLoadingIcon + extends Omit { + icon: Promise +} + +export type ConnectedChain = { + id: Chain['id'] + namespace: Chain['namespace'] +} + +export interface WalletState { + label: string // wallet name + icon: string // wallet icon svg string + provider: EIP1193Provider + accounts: Account[] + // in future it will be possible that a wallet + // is connected to multiple chains at once + chains: ConnectedChain[] + instance?: unknown +} + +export type Account = { + address: Address + ens: Ens | null + uns: Uns | null + balance: Balances | null + secondaryTokens?: SecondaryTokenBalances[] | null +} + +export type Balances = Record | null + +export interface SecondaryTokenBalances { + name: TokenSymbol + balance: string + icon?: string +} + +export interface Ens { + name: string + avatar: string | null + contentHash: Address | null + ensResolver: Address | null + getText: (key: string) => Promise +} + +export interface Uns { + name: string +} + +export interface AppState { + chains: Chain[] + walletModules: WalletModule[] + wallets: WalletState[] + accountCenter: AccountCenter + locale: Locale + notify: Notify + notifications: Notification[] + connect: ConnectModalOptions + appMetadata: AppMetadata | null + wagmiConfig: Config | null +} + +export type Configuration = { + svelteInstance: SvelteComponent | null + device: Device | DeviceNotBrowser + initialWalletInit: WalletInit[] + appMetadata?: AppMetadata | null + apiKey?: string + gas?: typeof gas + containerElements?: ContainerElements + transactionPreview?: TransactionPreviewAPI + unstoppableResolution?: typeof unstoppableResolution +} + +export type Locale = string +export type i18nOptions = Record +/** + * RecursivePartial is a utility type that allows one to define a partial + * version of a type that also includes all nested properties as partial. + * This allows partial i18n override in TypeScript: + */ +type RecursivePartial = { + [P in keyof T]?: RecursivePartial +} +export type i18n = RecursivePartial + +export type ConnectModalOptions = { + /** + * Display the connect modal sidebar - only applies to desktop views + */ + showSidebar?: boolean + /** + * Disabled close of the connect modal with background click and + * hides the close button forcing an action from the connect modal + * Defaults to false + */ + disableClose?: boolean + /** + * If set to true, the most recently connected wallet will store in + * local storage. Then on init, onboard will try to reconnect to + * that wallet with no modals displayed + */ + autoConnectLastWallet?: boolean + /** + * If set to true, all previously connected wallets will store in + * local storage. Then on init, onboard will try to reconnect to + * each wallet with no modals displayed + */ + autoConnectAllPreviousWallet?: boolean + /** + * Customize the link for the `I don't have a wallet` flow shown on the + * select wallet modal. + * Defaults to `https://ethereum.org/en/wallets/find-wallet/#main-content` + */ + iDontHaveAWalletLink?: string + /** + * Customize the link for the `Where's My Wallet` info pop up shown on the + * select wallet modal. + * Defaults to `https://www.blocknative.com/blog/ + * metamask-wont-connect-web3-wallet-troubleshooting` + */ + wheresMyWalletLink?: string + /** + * Hide the "Where is my wallet?" link notice displayed in the connect modal + * at the bottom of the wallets list + */ + removeWhereIsMyWalletWarning?: boolean + /** + * Hide the "I don't have a wallet" link displayed + * on the left panel of the connect modal + */ + removeIDontHaveAWalletInfoLink?: boolean + /** + * @deprecated Has no effect unless `@web3-onboard/unstoppable-resolution` + * package has been added and passed into the web3-onboard initialization + * In this case remove the `@web3-onboard/unstoppable-resolution` package + * to remove unstoppableDomain resolution support + */ + disableUDResolution?: boolean +} + +export type CommonPositions = + | 'topRight' + | 'bottomRight' + | 'bottomLeft' + | 'topLeft' + +export type AccountCenterPosition = CommonPositions + +export type NotificationPosition = CommonPositions + +export type AccountCenter = { + enabled: boolean + /** + * false by default - This allows removal of the + * Enable Transaction Protection' button within the Account Center + * expanded when set to true + * Can be set as a global for Account Center or per interface (desktop/mobile) + */ + hideTransactionProtectionBtn?: boolean + /** + * Controls the visibility of the 'Enable Transaction Protection' button + * within the expanded Account Center. + * - When set to false (default), the button is visible. + * - When set to true, the button is hidden. + * This setting can be configured globally for the Account Center, or + * separately for different interfaces like desktop/mobile. + * defaults to + * `docs.blocknative.com/blocknative-mev-protection/transaction-boost-alpha` + * Use this property to override the default link to give users + * more information about transaction protection and the RPC be set + */ + transactionProtectionInfoLink?: string + position?: AccountCenterPosition + expanded?: boolean + minimal?: boolean + /** + * @deprecated Use top level containerElements property + * with the accountCenter prop set to the desired container El + */ + containerElement?: string +} + +export type AccountCenterOptions = { + desktop: Omit + mobile: Omit + /** + * Controls the visibility of the 'Enable Transaction Protection' button + * within the expanded Account Center. + * - When set to false (default), the button is visible. + * - When set to true, the button is hidden. + * This setting can be configured globally for the Account Center, or + * separately for different interfaces like desktop/mobile. + * defaults to + * `docs.blocknative.com/blocknative-mev-protection/transaction-boost-alpha` + * Use this property to override the default link to give users + * more information about transaction protection and the RPC be set + */ + transactionProtectionInfoLink?: string + /** + * false by default - This allows removal of the + * Enable Transaction Protection' button within the Account Center + * expanded when set to true + * Can be set as a global for Account Center or per interface (desktop/mobile) + */ + hideTransactionProtectionBtn?: boolean +} + +export type ContainerElements = { + /** When attaching the Connect Modal to a container el be aware that + * the modal was styled to be mounted through the app to the html body + * and will respond to screen width rather than container width + * This is specifically apparent on mobile so please test thoroughly + * Also consider that other DOM elements(specifically Notifications and + * Account Center) will also append to this DOM el if enabled and their + * own containerEl are not defined + */ + connectModal?: string + /** when using the accountCenter with a container el the accountCenter + * position properties are ignored + */ + accountCenter?: string +} + +export type Notify = { + /** + * Defines whether whether to subscribe to transaction events or not + * default: true + */ + enabled: boolean + /** + * Callback that receives all transaction events + * Return a custom notification based on the event + * Or return false to disable notification for this event + * Or return undefined for a default notification + */ + transactionHandler: ( + event: EthereumTransactionData + ) => TransactionHandlerReturn + /** + * Position of notifications that defaults to the same position as the + * Account Center (if enabled) of the top right if AC is disabled + * and notifications are enabled (enabled by default with API key) + */ + position?: NotificationPosition + replacement?: { + gasPriceProbability?: { + speedup?: number + cancel?: number + } + } +} + +export type NotifyOptions = { + desktop: Notify + mobile: Notify +} + +export type Notification = { + id: string + key: string + network: Network + startTime?: number + /** + * to completely customize the message shown + */ + message: string + /** + * handle codes in your own way - see codes here under the notify + * prop default en file at ./packages/core/src/i18n/en.json + */ + eventCode: string + /** + * icon type displayed (see `NotificationType` below for options) + */ + type: NotificationType + /** + * time (in ms) after which the notification will be dismissed. If set + * to `0` the notification will remain on screen until the user dismisses the + * notification, refreshes the page or navigates away from the site + * with the notifications + */ + autoDismiss: number + /** + * add link to the transaction hash. For instance, a link to the + * transaction on etherscan + */ + link?: string + /** + * onClick handler for when user clicks the notification element + */ + onClick?: (event: Event) => void +} + +export type TransactionHandlerReturn = CustomNotification | boolean | void + +export type CustomNotification = Partial< + Omit +> + +export type CustomNotificationUpdate = Partial< + Omit +> + +export type NotificationType = 'pending' | 'success' | 'error' | 'hint' + +export interface UpdateNotification { + (notificationObject: CustomNotification): { + dismiss: () => void + update: UpdateNotification + } +} + +export interface PreflightNotificationsOptions { + sendTransaction?: () => Promise + estimateGas?: () => Promise + gasPrice?: () => Promise + balance?: string | number + txDetails?: TxDetails + txApproveReminderTimeout?: number +} + +export interface TxDetails { + value: string | number + to?: string + from?: string +} + +// ==== ACTIONS ==== // +export type Action = + | AddChainsAction + | UpdateChainsAction + | AddWalletAction + | UpdateWalletAction + | RemoveWalletAction + | ResetStoreAction + | UpdateAccountAction + | UpdateAccountCenterAction + | SetWalletModulesAction + | SetLocaleAction + | UpdateNotifyAction + | AddNotificationAction + | RemoveNotificationAction + | UpdateAllWalletsAction + | UpdateConnectModalAction + | UpdateAppMetadataAction + | UpdateWagmiConfigAction + +export type AddChainsAction = { type: 'add_chains'; payload: Chain[] } +export type UpdateChainsAction = { type: 'update_chains'; payload: Chain } +export type AddWalletAction = { type: 'add_wallet'; payload: WalletState } + +export type UpdateWalletAction = { + type: 'update_wallet' + payload: { id: string } & Partial +} + +export type RemoveWalletAction = { + type: 'remove_wallet' + payload: { id: string } +} + +export type ResetStoreAction = { + type: 'reset_store' + payload: unknown +} + +export type UpdateAccountAction = { + type: 'update_account' + payload: { id: string; address: Address } & Partial +} + +export type UpdateAccountCenterAction = { + type: 'update_account_center' + payload: AccountCenter | Partial +} + +export type UpdateConnectModalAction = { + type: 'update_connect_modal' + payload: Partial +} + +export type SetWalletModulesAction = { + type: 'set_wallet_modules' + payload: WalletModule[] +} + +export type SetLocaleAction = { + type: 'set_locale' + payload: string +} + +export type UpdateNotifyAction = { + type: 'update_notify' + payload: Partial +} + +export type AddNotificationAction = { + type: 'add_notification' + payload: Notification +} + +export type RemoveNotificationAction = { + type: 'remove_notification' + payload: Notification['id'] +} + +export type UpdateAllWalletsAction = { + type: 'update_balance' + payload: WalletState[] +} + +export type UpdateAppMetadataAction = { + type: 'update_app_metadata' + payload: AppMetadata | Partial +} + +export type UpdateWagmiConfigAction = { + type: 'update_wagmi_config' + payload: Config +} + +// ==== MISC ==== // +export type ChainStyle = { + icon: string + color: string +} + +export type NotifyEventStyles = { + backgroundColor: string + borderColor: string + eventIcon: string + iconColor?: string +} + +export type WalletPermission = { + id: string + parentCapability: string + invoker: string + caveats: { + type: string + value: string[] + }[] + + date: number +} diff --git a/packages/core-wagmi/src/update-balances.ts b/packages/core-wagmi/src/update-balances.ts new file mode 100644 index 000000000..efea19995 --- /dev/null +++ b/packages/core-wagmi/src/update-balances.ts @@ -0,0 +1,127 @@ +import { state } from './store/index.js' +import { getBalance } from './provider.js' +import { updateAllWallets } from './store/actions.js' +import { + type AccountAddress, + type Address, + type Chain, + weiToEth +} from '@web3-onboard/common' +import type { SecondaryTokenBalances, WalletState } from './types' +import type { ReadContractParameters } from 'viem' +import type { Chain as ViemChain } from 'viem' +import { chainIdToViemImport } from './utils.js' + + +async function updateBalances(addresses?: string[]): Promise { + const { wallets, chains } = state.get() + const updatedWallets = await Promise.all( + wallets.map(async wallet => { + const chain = chains.find(({ id }) => id === wallet.chains[0].id) + if (!chain) return + + const updatedAccounts = await Promise.all( + wallet.accounts.map(async account => { + const secondaryTokens = await updateSecondaryTokens( + account.address, + chain + ) + // if no provided addresses, we want to update all balances + // otherwise check if address is in addresses array + if ( + !addresses || + addresses.some( + address => address.toLowerCase() === account.address.toLowerCase() + ) + ) { + const updatedBalance = await getBalance(account.address, chain) + return { ...account, balance: updatedBalance, secondaryTokens } + } + return { ...account, secondaryTokens } + }) + ) + return { ...wallet, accounts: updatedAccounts } + }) + ) + updateAllWallets(updatedWallets as WalletState[]) +} + +export const updateSecondaryTokens = async ( + accountAddress: AccountAddress, + chain: Chain +): Promise => { + if (!chain) return [] + const chainRPC = chain.rpcUrl + if (!chain.secondaryTokens || !chain.secondaryTokens.length || !chainRPC) + return [] + + const tokenBalances = await Promise.all( + chain.secondaryTokens.map(async token => { + try { + const { createPublicClient, http } = await import('viem') + + const viemChain = await chainIdToViemImport(chain) + const client = createPublicClient({ + chain: viemChain as ViemChain, + transport: http( + chain.providerConnectionInfo && chain.providerConnectionInfo.url + ? chain.providerConnectionInfo.url + : (chainRPC as string) + ) + }) + const viemTokenInterface = { + abi: [ + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'symbol', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function' + } + ], + address: token.address as Address + } + + const supplyProm = + (client as any).readContract({ + ...viemTokenInterface, + functionName: 'balanceOf', + args: [accountAddress] as unknown[] + } as ReadContractParameters) || '' + + const tokenProm = + (client as any).readContract({ + ...viemTokenInterface, + functionName: 'symbol', + args: [] + }) || '' + + const [tokenSupply, tokenName] = await Promise.all([ + supplyProm, + tokenProm + ]) + + return { + name: tokenName as string, + balance: weiToEth((tokenSupply as number).toString()), + icon: token.icon + } + } catch (error) { + console.error( + `There was an error fetching balance and/or symbol + for token contract: ${token.address} - ${error}` + ) + } + }) + ) + return tokenBalances as SecondaryTokenBalances[] +} + +export default updateBalances diff --git a/packages/core-wagmi/src/utils.ts b/packages/core-wagmi/src/utils.ts new file mode 100644 index 000000000..3996fd86d --- /dev/null +++ b/packages/core-wagmi/src/utils.ts @@ -0,0 +1,401 @@ +import bowser from 'bowser' +import { type Chain as ViemChain } from 'viem' + +import type { + Device, + DeviceBrowser, + DeviceOS, + DeviceType, + ChainId, + Chain, + ChainWithDecimalId, + DeviceNotBrowser, +} from '@web3-onboard/common' + +import { + hourglass, + gnosisIcon, + checkmark, + errorIcon, + infoIcon, + ethereumIcon, + polygonIcon, + binanceIcon, + questionIcon, + fantomIcon, + optimismIcon, + celoIcon, + avalancheIcon, + harmonyOneIcon, + arbitrumIcon, + baseIcon, + degenIcon +} from './icons/index.js' + +import type { ChainStyle, ConnectedChain, NotifyEventStyles } from './types.js' +import { configuration } from './configuration' + +export function getDevice(): Device | DeviceNotBrowser { + if (typeof window !== 'undefined') { + const parsed = bowser.getParser(window.navigator.userAgent) + const os = parsed.getOS() + const browser = parsed.getBrowser() + const { type } = parsed.getPlatform() + + return { + type: type as DeviceType, + os: os as DeviceOS, + browser: browser as DeviceBrowser + } + } else { + return { + type: null, + os: null, + browser: null + } + } +} + +export const notNullish = (value: T | null | undefined): value is T => + value != null + +export function isSVG(str: string): boolean { + return str.includes(' 11 + ? `${domain.slice(0, 4)}…${domain.slice(-6)}` + : domain +} + +export async function copyWalletAddress(text: string): Promise { + try { + const copy = await navigator.clipboard.writeText(text) + return copy + } catch (err) { + console.error('Failed to copy: ', err) + } +} + +export const toHexString = (val: number | string): string => + typeof val === 'number' ? `0x${val.toString(16)}` : val + +export function chainIdToHex(chains: (Chain | ChainWithDecimalId)[]): Chain[] { + return chains.map(({ id, ...rest }) => { + const hexId = toHexString(id) + return { id: hexId, ...rest } + }) +} + +export function gweiToWeiHex(gwei: number): string { + return `0x${(gwei * 1e9).toString(16)}` +} + +export const chainIdToLabel: Record = { + '0x1': 'Ethereum', + '0xaa36a7': 'Sepolia', + '0x38': 'Binance', + '0x89': 'Polygon', + '0xfa': 'Fantom', + '0xa': 'OP Mainnet', + '0x45': 'OP Kovan', + '0xa86a': 'Avalanche', + '0xa4ec': 'Celo', + '0x2105': 'Base', + '0x14a33': 'Base Goerli', + '0x64': 'Gnosis', + '0x63564C40': 'Harmony One', + '0xa4b1': 'Arbitrum One', + '0xa4ba': 'Arbitrum Nova', + '0x27bc86aa': 'Degen' +} + +export function validEnsChain(chainId: ChainId): string | null { + // return L2s as Eth for ens resolution + switch (chainId) { + case '0x1': + case '0x89': // Polygon + case '0xa': //Optimism + case '0xa4b1': // Arb + case '0x144': // zksync + return '0x1' + case '0x5': // Goerli + return chainId + case '0xaa36a7': // Sepolia + return chainId + default: + return null + } +} + +export const chainIdToViemENSImport = async ( + chainId: string +): Promise => { + switch (chainId) { + case '0x89': + case '0xa': + case '0xa4b1': + case '0x144': + case '0x1': { + const { mainnet } = await import('viem/chains') + return mainnet + } + case '0xaa36a7': { + const { sepolia } = await import('viem/chains') + return sepolia + } + default: + return null + } +} +export const chainIdToViemImport = async ( + w3oChain: Chain +): Promise => { + const { id } = w3oChain + switch (id) { + case '0x89': { + const { polygon } = await import('viem/chains') + return polygon + } + case '0xa': { + const { optimism } = await import('viem/chains') + return optimism + } + case '0xa4b1': { + const { arbitrum } = await import('viem/chains') + return arbitrum + } + case '0x144': { + const { zkSync } = await import('viem/chains') + return zkSync + } + case '0x38': { + const { bsc } = await import('viem/chains') + return bsc + } + case '0x1': { + const { mainnet } = await import('viem/chains') + return mainnet + } + case '0xaa36a7': { + const { sepolia } = await import('viem/chains') + return sepolia + } + case '0xfa': { + const { fantom } = await import('viem/chains') + return fantom + } + case '0xa86a': { + const { avalanche } = await import('viem/chains') + return avalanche + } + case '0xa4ec': { + const { celo } = await import('viem/chains') + return celo + } + case '0x2105': { + const { base } = await import('viem/chains') + return base + } + case '0x14a33': { + const { baseGoerli } = await import('viem/chains') + return baseGoerli + } + case '0x64': { + const { gnosis } = await import('viem/chains') + return gnosis + } + case '0x63564C40': { + const { harmonyOne } = await import('viem/chains') + return harmonyOne + } + case '0x27bc86aa': { + const { degen } = await import('viem/chains') + return degen + } + default: { + return createCustomViemChain(w3oChain) + + } + } +} + +async function createCustomViemChain(w3oChain: Chain): Promise { + const { id, label, token, publicRpcUrl, blockExplorerUrl, rpcUrl } = w3oChain + const { defineChain } = await import('viem') + + return defineChain({ + id: parseInt(id, 16), + name: label, + nativeCurrency: { + decimals: 18, + name: token, + symbol: token + }, + rpcUrls: { + default: { + http: [rpcUrl, publicRpcUrl] + } + }, + blockExplorers: { + default: { name: 'Explorer', url: blockExplorerUrl } + } + } as const satisfies ViemChain) +} + +export const networkToChainId: Record = { + main: '0x1', + sepolia: '0xaa36a7', + xdai: '0x64', + 'bsc-main': '0x38', + 'matic-main': '0x89', + 'fantom-main': '0xfa', + 'matic-mumbai': '0x80001', + degen: '0x27bc86aa' +} + +export const chainStyles: Record = { + '0x1': { + icon: ethereumIcon, + color: '#627EEA' + }, + '0xaa36a7': { + icon: ethereumIcon, + color: '#627EEA' + }, + '0x38': { + icon: binanceIcon, + color: '#F3BA2F' + }, + '0x89': { + icon: polygonIcon, + color: '#8247E5' + }, + '0xfa': { + icon: fantomIcon, + color: '#1969FF' + }, + '0xa': { + icon: optimismIcon, + color: '#FF0420' + }, + '0x45': { + icon: optimismIcon, + color: '#FF0420' + }, + '0xa86a': { + icon: avalancheIcon, + color: '#E84142' + }, + '0xa4ec': { + icon: celoIcon, + color: '#FBCC5C' + }, + '0x64': { + icon: gnosisIcon, + color: '#04795B' + }, + '0x63564C40': { + icon: harmonyOneIcon, + color: '#ffffff' + }, + '0xa4b1': { + icon: arbitrumIcon, + color: '#33394B' + }, + '0xa4ba': { + icon: arbitrumIcon, + color: '#33394B' + }, + '0x2105': { + icon: baseIcon, + color: '#0259F9' + }, + '0x14a33': { + icon: baseIcon, + color: '#0259F9' + }, + '0x80001': { + icon: polygonIcon, + color: '#8247E5' + }, + '0x27bc86aa': { + icon: degenIcon, + color: '#a36dfe' + } +} + +export const unrecognizedChainStyle = { icon: questionIcon, color: '#33394B' } + +export function getDefaultChainStyles(chainId: string): ChainStyle | undefined { + return chainId ? chainStyles[chainId.toLowerCase()] : undefined +} + +export function connectedToValidAppChain( + walletConnectedChain: ConnectedChain, + chains: Chain[] +): boolean { + return !!chains.find( + ({ id, namespace }) => + id === walletConnectedChain.id && + namespace === walletConnectedChain.namespace + ) +} + +export const defaultNotifyEventStyles: Record = { + pending: { + backgroundColor: 'var(--onboard-primary-700, var(--primary-700))', + borderColor: '#6370E5', + eventIcon: hourglass + }, + success: { + backgroundColor: '#052E17', + borderColor: 'var(--onboard-success-300, var(--success-300))', + eventIcon: checkmark + }, + error: { + backgroundColor: '#FDB1B11A', + borderColor: 'var(--onboard-danger-300, var(--danger-300))', + eventIcon: errorIcon + }, + hint: { + backgroundColor: 'var(--onboard-gray-500, var(--gray-500))', + borderColor: 'var(--onboard-gray-500, var(--gray-500))', + iconColor: 'var(--onboard-gray-100, var(--gray-100))', + eventIcon: infoIcon + } +} + +export const wait = (time: number): Promise => + new Promise(resolve => setTimeout(resolve, time)) + +export function getLocalStore(key: string): string | null { + try { + const result = localStorage.getItem(key) + return result + } catch (error) { + return null + } +} + +export function setLocalStore(key: string, value: string): void { + try { + localStorage.setItem(key, value) + } catch (error) { + return + } +} + +export function delLocalStore(key: string): void { + try { + localStorage.removeItem(key) + } catch (error) { + return + } +} + diff --git a/packages/core-wagmi/src/validation.ts b/packages/core-wagmi/src/validation.ts new file mode 100644 index 000000000..c1d7a3732 --- /dev/null +++ b/packages/core-wagmi/src/validation.ts @@ -0,0 +1,458 @@ +import Joi from 'joi' + +import { + type ChainId, + type DecimalChainId, + type WalletInit, + type WalletModule, + type ValidateReturn, + type AppMetadata, + chainNamespaceValidation, + chainIdValidation, + chainValidation, + validate +} from '@web3-onboard/common' + +import type { + InitOptions, + WalletState, + ConnectOptions, + DisconnectOptions, + ConnectOptionsString, + AccountCenter, + TransactionHandlerReturn, + NotifyOptions, + Notification, + CustomNotification, + CustomNotificationUpdate, + Notify, + PreflightNotificationsOptions, + ConnectModalOptions, + Theme +} from './types.js' + +const unknownObject = Joi.object().unknown() + +const connectedChain = Joi.object({ + namespace: chainNamespaceValidation.required(), + id: chainIdValidation.required() +}) + +const ens = Joi.any().allow( + Joi.object({ + name: Joi.string().required(), + avatar: Joi.string(), + contentHash: Joi.any().allow(Joi.string(), null), + getText: Joi.function().arity(1).required() + }), + null +) + +const uns = Joi.any().allow( + Joi.object({ + name: Joi.string().required() + }), + null +) + +const balance = Joi.any().allow( + Joi.object({ + eth: Joi.number() + }).unknown(), + null +) + +const secondaryTokens = Joi.any().allow( + Joi.object({ + balance: Joi.string().required(), + icon: Joi.string() + }), + null +) + +const account = Joi.object({ + address: Joi.string().required(), + ens, + uns, + balance, + secondaryTokens +}) + +const chains = Joi.array() + .items(chainValidation) + .unique((a, b) => a.id === b.id) + .error(e => { + if (e[0].code === 'array.unique') { + return new Error( + `There is a duplicate Chain ID in your Onboard Chains array: ${e}` + ) + } + return new Error(`${e}`) + }) + +const accounts = Joi.array().items(account) + +const wallet = Joi.object({ + label: Joi.string(), + icon: Joi.string(), + provider: unknownObject, + instance: unknownObject, + accounts, + chains: Joi.array().items(connectedChain) +}) + .required() + .error(new Error('wallet must be defined')) + +const wallets = Joi.array().items(wallet) + +const recommendedWallet = Joi.object({ + name: Joi.string().required(), + url: Joi.string().uri().required() +}) + +const agreement = Joi.object({ + version: Joi.string().required(), + termsUrl: Joi.string().uri(), + privacyUrl: Joi.string().uri() +}) + +const appMetadata = Joi.object({ + name: Joi.string().required(), + description: Joi.string().required(), + icon: Joi.string(), + logo: Joi.string(), + gettingStartedGuide: Joi.string(), + email: Joi.string(), + appUrl: Joi.string(), + explore: Joi.string(), + recommendedInjectedWallets: Joi.array().items(recommendedWallet), + agreement +}) + +const appMetadataUpdate = Joi.object({ + name: Joi.string(), + description: Joi.string(), + icon: Joi.string(), + logo: Joi.string(), + gettingStartedGuide: Joi.string(), + email: Joi.string(), + appUrl: Joi.string(), + explore: Joi.string(), + recommendedInjectedWallets: Joi.array().items(recommendedWallet), + agreement +}) + +const walletModule = Joi.object({ + label: Joi.string().required(), + getInfo: Joi.function().arity(1).required(), + getInterface: Joi.function().arity(1).required() +}) + +const walletInit = Joi.array().items(Joi.function()).required() + +const locale = Joi.string() + +const commonPositions = Joi.string().valid( + 'topRight', + 'bottomRight', + 'bottomLeft', + 'topLeft' +) + +const gasPriceProbabilities = [70, 80, 90, 95, 99] + +const notify = Joi.object({ + transactionHandler: Joi.function(), + enabled: Joi.boolean(), + position: commonPositions, + replacement: Joi.object({ + gasPriceProbability: Joi.object({ + speedup: Joi.number().valid(...gasPriceProbabilities), + cancel: Joi.number().valid(...gasPriceProbabilities) + }) + }) +}) + +const notifyOptions = Joi.object({ + desktop: notify, + mobile: notify +}) + +const accountCenterInitOptions = Joi.object({ + enabled: Joi.boolean(), + position: commonPositions, + minimal: Joi.boolean(), + containerElement: Joi.string(), + hideTransactionProtectionBtn: Joi.boolean(), + transactionProtectionInfoLink: Joi.string() +}) + +const accountCenter = Joi.object({ + enabled: Joi.boolean(), + position: commonPositions, + expanded: Joi.boolean(), + minimal: Joi.boolean(), + hideTransactionProtectionBtn: Joi.boolean(), + transactionProtectionInfoLink: Joi.string(), + containerElement: Joi.string() +}) + +const connectModalOptions = Joi.object({ + showSidebar: Joi.boolean(), + disableClose: Joi.boolean(), + autoConnectLastWallet: Joi.boolean(), + autoConnectAllPreviousWallet: Joi.boolean(), + iDontHaveAWalletLink: Joi.string(), + wheresMyWalletLink: Joi.string(), + removeWhereIsMyWalletWarning: Joi.boolean(), + removeIDontHaveAWalletInfoLink: Joi.boolean(), + disableUDResolution: Joi.boolean() +}) + +const containerElements = Joi.object({ + accountCenter: Joi.string(), + connectModal: Joi.string() +}) + +const themeMap = Joi.object({ + '--w3o-background-color': Joi.string(), + '--w3o-font-family': Joi.string(), + '--w3o-foreground-color': Joi.string(), + '--w3o-text-color': Joi.string(), + '--w3o-border-color': Joi.string(), + '--w3o-action-color': Joi.string(), + '--w3o-border-radius': Joi.string() +}) + +const presetTheme = Joi.string().valid('default', 'dark', 'light', 'system') + +const theme = Joi.alternatives().try(themeMap, presetTheme) + +const initOptions = Joi.object({ + wallets: walletInit, + chains: chains.required(), + appMetadata: appMetadata, + i18n: Joi.object().unknown(), + apiKey: Joi.string(), + accountCenter: Joi.object({ + desktop: accountCenterInitOptions, + mobile: accountCenterInitOptions, + hideTransactionProtectionBtn: Joi.boolean(), + transactionProtectionInfoLink: Joi.string() + }), + notify: [notifyOptions, notify], + gas: Joi.object({ + get: Joi.function().required(), + stream: Joi.function().required() + }), + connect: connectModalOptions, + containerElements: containerElements, + transactionPreview: Joi.object({ + patchProvider: Joi.function().required(), + init: Joi.function().required(), + previewTransaction: Joi.function() + }), + theme: theme, + disableFontDownload: Joi.boolean(), + unstoppableResolution: Joi.function() +}) + +const connectOptions = Joi.object({ + autoSelect: Joi.alternatives().try( + Joi.object({ + label: Joi.string().required(), + disableModals: Joi.boolean() + }), + Joi.string() + ) +}) + +const disconnectOptions = Joi.object({ + label: Joi.string().required() +}).required() + +const secondaryTokenValidation = Joi.object({ + address: Joi.string().required(), + icon: Joi.string().optional() +}) + +const setChainOptions = Joi.object({ + chainId: chainIdValidation.required(), + chainNamespace: chainNamespaceValidation, + wallet: Joi.string(), + rpcUrl: Joi.string(), + label: Joi.string(), + token: Joi.string(), + protectedRpcUrl: Joi.string(), + secondaryTokens: Joi.array().max(5).items(secondaryTokenValidation).optional() +}) + +const customNotificationUpdate = Joi.object({ + key: Joi.string().required(), + type: Joi.string().allow('pending', 'error', 'success', 'hint'), + eventCode: Joi.string(), + message: Joi.string().required(), + id: Joi.string().required(), + autoDismiss: Joi.number(), + onClick: Joi.function(), + link: Joi.string() +}) + +const preflightNotifications = Joi.object({ + sendTransaction: Joi.function(), + estimateGas: Joi.function(), + gasPrice: Joi.function(), + balance: Joi.alternatives(Joi.string(), Joi.number()), + txDetails: Joi.object({ + value: Joi.alternatives(Joi.string(), Joi.number()), + to: Joi.string(), + from: Joi.string() + }), + txApproveReminderTimeout: Joi.number() +}) + +const customNotification = Joi.object({ + key: Joi.string(), + type: Joi.string().allow('pending', 'error', 'success', 'hint'), + eventCode: Joi.string(), + message: Joi.string(), + id: Joi.string(), + autoDismiss: Joi.number(), + onClick: Joi.function(), + link: Joi.string() +}) + +const notification = Joi.object({ + id: Joi.string().required(), + key: Joi.string().required(), + type: Joi.string().allow('pending', 'error', 'success', 'hint').required(), + eventCode: Joi.string().required(), + message: Joi.string().required(), + autoDismiss: Joi.number().required(), + network: Joi.string().required(), + startTime: Joi.number(), + onClick: Joi.function(), + link: Joi.string() +}) + +const transactionHandlerReturn = Joi.any().allow( + customNotificationUpdate, + Joi.boolean().allow(false) +) + +export function validateWallet( + data: WalletState | Partial +): ValidateReturn { + return validate(wallet, data) +} + +export function validateInitOptions(data: InitOptions): ValidateReturn { + return validate(initOptions, data) +} + +export function validateWalletModule(data: WalletModule): ValidateReturn { + return validate(walletModule, data) +} + +export function validateConnectOptions( + data: ConnectOptions | ConnectOptionsString +): ValidateReturn { + return validate(connectOptions, data) +} + +export function validateDisconnectOptions( + data: DisconnectOptions +): ValidateReturn { + return validate(disconnectOptions, data) +} + +export function validateString(str: string, label?: string): ValidateReturn { + return validate( + Joi.string() + .required() + .label(label || 'value'), + str + ) +} + +export function validateSetChainOptions(data: { + chainId: ChainId | DecimalChainId + chainNamespace?: string + wallet?: WalletState['label'] + rpcUrl?: string + label?: string + token?: string +}): ValidateReturn { + return validate(setChainOptions, data) +} + +export function validateAccountCenterUpdate( + data: AccountCenter | Partial +): ValidateReturn { + return validate(accountCenter, data) +} + +export function validateConnectModalUpdate( + data: ConnectModalOptions | Partial +): ValidateReturn { + return validate(connectModalOptions, data) +} + +export function validateWalletInit(data: WalletInit[]): ValidateReturn { + return validate(walletInit, data) +} + +export function validateLocale(data: string): ValidateReturn { + return validate(locale, data) +} + +export function validateNotify(data: Partial): ValidateReturn { + return validate(notify, data) +} + +export function validateNotifyOptions( + data: Partial +): ValidateReturn { + return validate(notifyOptions, data) +} + +export function validateTransactionHandlerReturn( + data: TransactionHandlerReturn +): ValidateReturn { + return validate(transactionHandlerReturn, data) +} + +export function validateNotification(data: Notification): ValidateReturn { + return validate(notification, data) +} +export function validatePreflightNotifications( + data: PreflightNotificationsOptions +): ValidateReturn { + return validate(preflightNotifications, data) +} + +export function validateCustomNotificationUpdate( + data: CustomNotificationUpdate +): ValidateReturn { + return validate(customNotificationUpdate, data) +} + +export function validateCustomNotification( + data: CustomNotification +): ValidateReturn { + return validate(customNotification, data) +} + +export function validateUpdateBalances(data: WalletState[]): ValidateReturn { + return validate(wallets, data) +} + +export function validateUpdateTheme(data: Theme): ValidateReturn { + return validate(theme, data) +} + +export function validateAppMetadataUpdate( + data: AppMetadata | Partial +): ValidateReturn { + return validate(appMetadataUpdate, data) +} diff --git a/packages/core-wagmi/src/views/Index.svelte b/packages/core-wagmi/src/views/Index.svelte new file mode 100644 index 000000000..ae1c81818 --- /dev/null +++ b/packages/core-wagmi/src/views/Index.svelte @@ -0,0 +1,526 @@ + + + + +{#if $connectWallet$.inProgress} + +{/if} + +{#if $connectWallet$.actionRequired} + +{/if} + +{#if $switchChainModal$} + +{/if} + +{#if !$accountCenter$.enabled && !$notify$.enabled} +
+{/if} + +{#if displayAccountCenterNotifySameContainer} +
+ {#if $notify$.position.includes('bottom') && $accountCenter$.position.includes('bottom') && samePositionOrMobile} + {#await notifyComponent then Notify} + {#if Notify} + + {/if} + {/await} + {/if} + {#if $accountCenter$.position.includes('bottom')} +
+ {/if} +
+ {#await accountCenterComponent then AccountCenter} + {#if AccountCenter} + + {/if} + {/await} +
+ {#if $accountCenter$.position.includes('top')} +
+ {/if} + {#if $notify$.position.includes('top') && $accountCenter$.position.includes('top') && samePositionOrMobile} + {#await notifyComponent then Notify} + {#if Notify} + + {/if} + {/await} + {/if} +
+{/if} +{#if displayAccountCenterSeparate} +
+ {#if $accountCenter$.position.includes('bottom')} +
+ {/if} +
+ {#if $accountCenter$.enabled && $wallets$.length} + {#await accountCenterComponent then AccountCenter} + {#if AccountCenter} + + {/if} + {/await} + {/if} +
+ {#if $accountCenter$.position.includes('top')} +
+ {/if} +
+{/if} +{#if displayNotifySeparate} +
+ {#if $notify$.position.includes('top')} +
+ {/if} + {#await notifyComponent then Notify} + {#if Notify} + + {/if} + {/await} + {#if $notify$.position.includes('bottom')} +
+ {/if} +
+{/if} diff --git a/packages/core-wagmi/src/views/account-center/AccountCenterPanel.svelte b/packages/core-wagmi/src/views/account-center/AccountCenterPanel.svelte new file mode 100644 index 000000000..1342cb45d --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/AccountCenterPanel.svelte @@ -0,0 +1,571 @@ + + + + +{#if disconnectConfirmModal} + (disconnectConfirmModal = false)} + onConfirm={disconnectAllWallets} + /> +{/if} +{#if enableTransactionProtection} + (enableTransactionProtection = false)} + onEnable={() => enableProtectionRPC()} + infoLink={$accountCenter$.transactionProtectionInfoLink || + BN_BOOST_INFO_URL} + /> +{/if} + +{#if expanded} +
+ +
+ +
+
+ {#each $wallets$ as wallet, i (wallet.label)} + + {/each} +
+ +
+ + {#if device.type === 'desktop'} + +
connect()} + class="action-container flex items-center pointer" + > +
+ {@html plusCircleIcon} +
+ {$_('accountCenter.connectAnotherWallet', { + default: en.accountCenter.connectAnotherWallet + })} +
+ + +
(disconnectConfirmModal = true)} + class="action-container flex items-center mt pointer" + > +
+ {@html arrowForwardIcon} +
+ {$_('accountCenter.disconnectAllWallets', { + default: en.accountCenter.disconnectAllWallets + })} +
+ {/if} +
+
+ + +
+
+ +
+ + + {#if validAppChain} +
+ +
+ {/if} +
+ + +
+
+ {$_('accountCenter.currentNetwork', { + default: en.accountCenter.currentNetwork + })} +
+
+ +
+
+
+ + {#if !$accountCenter$.hideTransactionProtectionBtn && (primaryWalletOnMainnet || validAppChain?.protectedRpcUrl)} +
(enableTransactionProtection = true)} + class="protect action-container flex items-center pointer" + > +
+ {@html shieldIcon} +
+ {$_('accountCenter.enableTransactionProtection', { + default: en.accountCenter.enableTransactionProtection + })} +
+ {/if} +
+ + +
+ {#if $appMetadata$} +
+ +
+ +
+ {($appMetadata$ && $appMetadata$.name) || 'App Name'} +
+
+ +
+ {($appMetadata$ && $appMetadata$.description) || + 'This app has not added a description.'} +
+
+ + + {#if $appMetadata$.gettingStartedGuide || $appMetadata$.explore} +
+
+ {$_('accountCenter.appInfo', { + default: en.accountCenter.appInfo + })} +
+ + {#if $appMetadata$.gettingStartedGuide} +
+
+ {$_('accountCenter.learnMore', { + default: en.accountCenter.learnMore + })} +
+ + {$_('accountCenter.gettingStartedGuide', { + default: en.accountCenter.gettingStartedGuide + })} + +
+ {/if} + + {#if $appMetadata$.explore} +
+
+ {$_('accountCenter.smartContracts', { + default: en.accountCenter.smartContracts + })} +
+ + {$_('accountCenter.explore', { + default: en.accountCenter.explore + })} + +
+ {/if} +
+ {/if} + {/if} + {#if secondaryTokens && secondaryTokens.length} + + {/if} + +
+
+
+{/if} diff --git a/packages/core-wagmi/src/views/account-center/AcctCenterTriggerLarge.svelte b/packages/core-wagmi/src/views/account-center/AcctCenterTriggerLarge.svelte new file mode 100644 index 000000000..037cb9041 --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/AcctCenterTriggerLarge.svelte @@ -0,0 +1,256 @@ + + + + +
+
+
+
+ +
+ +
+ +
+ +
+ +
+
+ + +
+
+ {ensName + ? shortenDomain(ensName) + : unsName + ? shortenDomain(unsName) + : shortenedFirstAddress} +
+ {#if firstAddressBalance} +
+ {firstAddressBalance.length > 7 + ? firstAddressBalance.slice(0, 7) + : firstAddressBalance} + {firstAddressAsset} +
+ {/if} +
+ + +
+
+
+
+ +
+ + +
+
+
+
+
diff --git a/packages/core-wagmi/src/views/account-center/AcctCenterTriggerSmall.svelte b/packages/core-wagmi/src/views/account-center/AcctCenterTriggerSmall.svelte new file mode 100644 index 000000000..2d57fa663 --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/AcctCenterTriggerSmall.svelte @@ -0,0 +1,112 @@ + + + + +
+
+ +
+ +
+
+
+ +
+ +
+ +
+
+
+
diff --git a/packages/core-wagmi/src/views/account-center/DisconnectAllConfirm.svelte b/packages/core-wagmi/src/views/account-center/DisconnectAllConfirm.svelte new file mode 100644 index 000000000..b40e3decd --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/DisconnectAllConfirm.svelte @@ -0,0 +1,80 @@ + + + + + +
+
+ {@html warningIcon} +
+ +

+ {$_('modals.confirmDisconnectAll.heading', { + default: en.modals.confirmDisconnectAll.heading + })} +

+ +

+ {$_('modals.confirmDisconnectAll.description')} +

+ +
+ + +
+
+
diff --git a/packages/core-wagmi/src/views/account-center/EnableTransactionProtectionModal.svelte b/packages/core-wagmi/src/views/account-center/EnableTransactionProtectionModal.svelte new file mode 100644 index 000000000..d87116749 --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/EnableTransactionProtectionModal.svelte @@ -0,0 +1,114 @@ + + + + + +
+
+ {@html shieldIcon} +
+ +
+
+ {$_('modals.confirmTransactionProtection.heading', { + default: en.modals.confirmTransactionProtection.heading + })} +
+
+ {$_('modals.confirmTransactionProtection.description')} +
+ {$_('modals.confirmTransactionProtection.link', { + default: en.modals.confirmTransactionProtection.link + })} + +
+ +
+ + +
+
+
diff --git a/packages/core-wagmi/src/views/account-center/Index.svelte b/packages/core-wagmi/src/views/account-center/Index.svelte new file mode 100644 index 000000000..64c37cce3 --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/Index.svelte @@ -0,0 +1,54 @@ + + + + + + +
+ {#if $accountCenter$.position.includes('bottom')} + + {/if} + {#if $accountCenter$.minimal} + + {:else} + + {/if} + {#if $accountCenter$.position.includes('top')} + + {/if} +
diff --git a/packages/core-wagmi/src/views/account-center/SecondaryTokenTable.svelte b/packages/core-wagmi/src/views/account-center/SecondaryTokenTable.svelte new file mode 100644 index 000000000..e954fd3fa --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/SecondaryTokenTable.svelte @@ -0,0 +1,101 @@ + + + + +
+ + + + + + + + {#each secondaryTokens as token} + {#if token && token.name && token.balance} + + + + + {/if} + {/each} + +
Token Balances:
+
+ {#if token.icon} + {#await token.icon then iconLoaded} +
+ {#if isSVG(iconLoaded)} + + {@html iconLoaded} + {:else} + + logo + {/if} +
+ {/await} + {:else} +
+ {/if} + {token.name.toUpperCase()} +
+
+ {token.balance.length > 7 + ? token.balance.slice(0, 7) + : token.balance} +
+
diff --git a/packages/core-wagmi/src/views/account-center/WalletRow.svelte b/packages/core-wagmi/src/views/account-center/WalletRow.svelte new file mode 100644 index 000000000..734b6b58f --- /dev/null +++ b/packages/core-wagmi/src/views/account-center/WalletRow.svelte @@ -0,0 +1,306 @@ + + + + +{#each wallet.accounts as { address, ens, uns, balance }, i} +
+
setPrimaryWallet(wallet, address)} + class:primary={primary && i === 0} + class="container" + > +
+ + + {#if primary && i === 0} +
+ +
+ {/if} +
+ + + + +
+
+ (showMenu = showMenu === address ? '' : address)} + class="elipsis pointer flex items-center justify-center relative" + > + {@html elipsisIcon} +
+
+
+ + {#if showMenu === address} + + {/if} +
+{/each} diff --git a/packages/core-wagmi/src/views/chain/SwitchChain.svelte b/packages/core-wagmi/src/views/chain/SwitchChain.svelte new file mode 100644 index 000000000..6846d3149 --- /dev/null +++ b/packages/core-wagmi/src/views/chain/SwitchChain.svelte @@ -0,0 +1,69 @@ + + + + + +
+

+ {$_('modals.switchChain.heading', { + default: en.modals.switchChain.heading + })} +

+ +

+ {$_('modals.switchChain.paragraph1', { + default: en.modals.switchChain.paragraph1, + values: { + app: ($appMetadata$ && $appMetadata$.name) || 'This app', + nextNetworkName + } + })} +

+ +

+ {$_('modals.switchChain.paragraph2', { + default: en.modals.switchChain.paragraph2 + })} +

+ +
+
+
diff --git a/packages/core-wagmi/src/views/connect/ActionRequired.svelte b/packages/core-wagmi/src/views/connect/ActionRequired.svelte new file mode 100644 index 000000000..e524b0a0a --- /dev/null +++ b/packages/core-wagmi/src/views/connect/ActionRequired.svelte @@ -0,0 +1,96 @@ + + + + + +
+
+ +
+ +

+ {$_('modals.actionRequired.heading', { values: { wallet } })} +

+ +

+ {$_('modals.actionRequired.paragraph', { values: { wallet } })} + + {#if wallet === 'MetaMask'} + {$_('modals.actionRequired.linkText', { values: { wallet } })} + {/if} +

+ + +
+
diff --git a/packages/core-wagmi/src/views/connect/Agreement.svelte b/packages/core-wagmi/src/views/connect/Agreement.svelte new file mode 100644 index 000000000..f5d1eeeef --- /dev/null +++ b/packages/core-wagmi/src/views/connect/Agreement.svelte @@ -0,0 +1,80 @@ + + + + +{#if showTermsOfService} +
+ +
+{/if} diff --git a/packages/core-wagmi/src/views/connect/ConnectedWallet.svelte b/packages/core-wagmi/src/views/connect/ConnectedWallet.svelte new file mode 100644 index 000000000..24e21bd0b --- /dev/null +++ b/packages/core-wagmi/src/views/connect/ConnectedWallet.svelte @@ -0,0 +1,83 @@ + + + + +
+
+
+
+ + +
+ +
+ +
+ +
+
+
+ {$_('connect.connectedWallet.mainText', { + default: en.connect.connectedWallet.mainText, + values: { wallet: selectedWallet.label } + })} +
+
+ +
+ {@html successIcon} +
+
+
diff --git a/packages/core-wagmi/src/views/connect/ConnectingWallet.svelte b/packages/core-wagmi/src/views/connect/ConnectingWallet.svelte new file mode 100644 index 000000000..f642853ab --- /dev/null +++ b/packages/core-wagmi/src/views/connect/ConnectingWallet.svelte @@ -0,0 +1,173 @@ + + + + +
+
+
+
+ + +
+ +
+
+ +
+
+ {$_( + `connect.connectingWallet.${ + connectionRejected ? 'rejectedText' : 'mainText' + }`, + { + default: connectionRejected + ? en.connect.connectingWallet.rejectedText + : en.connect.connectingWallet.mainText, + values: { wallet: selectedWallet.label } + } + )} +
+ {#if connectionRejected} +
+ {$_('connect.connectingWallet.rejectedCTA', { + default: en.connect.connectingWallet.rejectedCTA, + values: { wallet: selectedWallet.label } + })} +
+ {:else} +
+ {$_( + `connect.connectingWallet.${ + previousConnectionRequest ? 'previousConnection' : 'paragraph' + }`, + { + default: en.connect.connectingWallet.paragraph, + values: { wallet: selectedWallet.label } + } + )} +
+ {/if} +
+
+
+ + +
diff --git a/packages/core-wagmi/src/views/connect/Index.svelte b/packages/core-wagmi/src/views/connect/Index.svelte new file mode 100644 index 000000000..2607da1ac --- /dev/null +++ b/packages/core-wagmi/src/views/connect/Index.svelte @@ -0,0 +1,652 @@ + + + + + + +{#if !autoSelect.disableModals} + +
+ {#if connect.showSidebar} + + {/if} + +
+ {#if windowWidth <= MOBILE_WINDOW_WIDTH} +
+
+ {#if $appMetadata$ && $appMetadata$.icon} + {#if isSVG($appMetadata$.icon)} + {@html $appMetadata$.icon} + {:else} + logo + {/if} + {:else} + {@html defaultBnIcon} + {/if} +
+
+
+ {$_( + $modalStep$ === 'connectingWallet' && selectedWallet + ? `connect.${$modalStep$}.header` + : `connect.${$modalStep$}.sidebar.subheading`, + { + default: + $modalStep$ === 'connectingWallet' && selectedWallet + ? en.connect[$modalStep$].header + : en.connect[$modalStep$].sidebar.subheading, + values: { + connectionRejected, + wallet: selectedWallet && selectedWallet.label + } + } + )} +
+
+ {$modalStep$ === 'selectingWallet' + ? `${availableWallets} available wallets` + : '1 account selected'} +
+
+
+ {:else} +
+
+ {$_(`connect.${$modalStep$}.header`, { + default: en.connect[$modalStep$].header, + values: { + connectionRejected, + wallet: selectedWallet && selectedWallet.label + } + })} + {$modalStep$ === 'selectingWallet' ? `(${availableWallets})` : ''} +
+
+ {/if} + {#if !connect.disableClose} + +
+ +
+ {/if} +
+ {#if $modalStep$ === 'selectingWallet' || windowWidth <= MOBILE_WINDOW_WIDTH} + {#if wallets.length} + + +
+ +
+ {:else} + + {/if} + {/if} + + {#if displayConnectingWallet} + + {/if} + + {#if $modalStep$ === 'connectedWallet' && selectedWallet && windowWidth >= MOBILE_WINDOW_WIDTH} + + {/if} +
+
+
+
+{/if} diff --git a/packages/core-wagmi/src/views/connect/InstallWallet.svelte b/packages/core-wagmi/src/views/connect/InstallWallet.svelte new file mode 100644 index 000000000..311437fda --- /dev/null +++ b/packages/core-wagmi/src/views/connect/InstallWallet.svelte @@ -0,0 +1,55 @@ + + + + +
+ + {#if $appMetadata$.recommendedInjectedWallets} + {$_('connect.selectingWallet.recommendedWalletsPart1', { + default: en.connect.selectingWallet.recommendedWalletsPart1, + values: { + app: $appMetadata$.name || 'This app' + } + })} + {#each $appMetadata$.recommendedInjectedWallets as { name, url }, i} + {name}{i < $appMetadata$.recommendedInjectedWallets.length - 1 ? ', ' : ''} + + {/each} + {$_('connect.selectingWallet.recommendedWalletsPart2', { + default: en.connect.selectingWallet.recommendedWalletsPart2 + })} + {:else} + {$_('connect.selectingWallet.installWallet', { + default: en.connect.selectingWallet.installWallet, + values: { + app: $appMetadata$.name || 'this app' + } + })} + {/if} + +
diff --git a/packages/core-wagmi/src/views/connect/SelectingWallet.svelte b/packages/core-wagmi/src/views/connect/SelectingWallet.svelte new file mode 100644 index 000000000..f0cacd639 --- /dev/null +++ b/packages/core-wagmi/src/views/connect/SelectingWallet.svelte @@ -0,0 +1,112 @@ + + + + + + +
+ {#if connectingErrorMessage} +
+ {@html connectingErrorMessage} +
+ {/if} + +
+ {#each wallets as wallet} + selectWallet(wallet)} + disabled={windowWidth <= MOBILE_WINDOW_WIDTH && + connectingWalletLabel && + connectingWalletLabel !== wallet.label} + /> + {/each} + {#if !connect.removeWhereIsMyWalletWarning} +
+ +
+ {$_('connect.selectingWallet.whyDontISeeMyWallet', { + default: en.connect.selectingWallet.whyDontISeeMyWallet + })} +
+ {$_('connect.selectingWallet.learnMore', { + default: en.connect.selectingWallet.learnMore + })} +
+
+ {/if} +
+
diff --git a/packages/core-wagmi/src/views/connect/Sidebar.svelte b/packages/core-wagmi/src/views/connect/Sidebar.svelte new file mode 100644 index 000000000..62c8c110a --- /dev/null +++ b/packages/core-wagmi/src/views/connect/Sidebar.svelte @@ -0,0 +1,289 @@ + + + + + + +