From 75b93f1768f780c045a006af41d8c7e5c8a7c4f6 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 8 Apr 2025 14:46:20 -0300 Subject: [PATCH 01/91] chore: cloned scaffold and renamed to appkit-react-native --- packages/appkit/.eslintrc.json | 3 + packages/appkit/.npmignore | 10 + packages/appkit/CHANGELOG.md | 153 +++++++ packages/appkit/readme.md | 9 + packages/appkit/src/client.ts | 384 ++++++++++++++++++ packages/appkit/src/config/animations.ts | 7 + .../appkit/src/hooks/useCustomDimensions.ts | 21 + .../appkit/src/hooks/useDebounceCallback.ts | 45 ++ packages/appkit/src/hooks/useKeyboard.ts | 62 +++ packages/appkit/src/hooks/useProvider.ts | 11 + packages/appkit/src/hooks/useTimeout.ts | 34 ++ packages/appkit/src/index.ts | 22 + .../src/modal/w3m-account-button/index.tsx | 52 +++ .../appkit/src/modal/w3m-button/index.tsx | 45 ++ .../src/modal/w3m-connect-button/index.tsx | 43 ++ packages/appkit/src/modal/w3m-modal/index.tsx | 157 +++++++ packages/appkit/src/modal/w3m-modal/styles.ts | 13 + .../src/modal/w3m-network-button/index.tsx | 48 +++ .../appkit/src/modal/w3m-router/index.tsx | 136 +++++++ .../partials/w3m-account-activity/index.tsx | 172 ++++++++ .../partials/w3m-account-activity/styles.ts | 29 ++ .../partials/w3m-account-activity/utils.ts | 25 ++ .../src/partials/w3m-account-tokens/index.tsx | 100 +++++ .../w3m-account-wallet-features/index.tsx | 156 +++++++ .../w3m-account-wallet-features/styles.ts | 41 ++ .../partials/w3m-all-wallets-list/index.tsx | 179 ++++++++ .../partials/w3m-all-wallets-list/styles.ts | 26 ++ .../partials/w3m-all-wallets-search/index.tsx | 147 +++++++ .../partials/w3m-all-wallets-search/styles.ts | 30 ++ .../partials/w3m-connecting-body/index.tsx | 32 ++ .../src/partials/w3m-connecting-body/utils.ts | 34 ++ .../partials/w3m-connecting-header/index.tsx | 53 +++ .../components/StoreLink.tsx | 38 ++ .../partials/w3m-connecting-mobile/index.tsx | 158 +++++++ .../partials/w3m-connecting-mobile/styles.ts | 24 ++ .../partials/w3m-connecting-qrcode/index.tsx | 76 ++++ .../partials/w3m-connecting-qrcode/styles.ts | 8 + .../src/partials/w3m-connecting-web/index.tsx | 111 +++++ .../src/partials/w3m-connecting-web/styles.ts | 20 + .../appkit/src/partials/w3m-header/index.tsx | 146 +++++++ .../appkit/src/partials/w3m-header/styles.ts | 8 + .../partials/w3m-information-modal/index.tsx | 65 +++ .../partials/w3m-information-modal/styles.ts | 22 + .../src/partials/w3m-otp-code/index.tsx | 81 ++++ .../src/partials/w3m-otp-code/styles.ts | 15 + .../src/partials/w3m-placeholder/index.tsx | 77 ++++ .../src/partials/w3m-selector-modal/index.tsx | 124 ++++++ .../src/partials/w3m-selector-modal/styles.ts | 42 ++ .../partials/w3m-send-input-address/index.tsx | 74 ++++ .../partials/w3m-send-input-address/styles.ts | 14 + .../partials/w3m-send-input-token/index.tsx | 119 ++++++ .../partials/w3m-send-input-token/styles.ts | 20 + .../partials/w3m-send-input-token/utils.ts | 21 + .../src/partials/w3m-snackbar/index.tsx | 49 +++ .../src/partials/w3m-snackbar/styles.ts | 12 + .../src/partials/w3m-swap-details/index.tsx | 160 ++++++++ .../src/partials/w3m-swap-details/styles.ts | 26 ++ .../src/partials/w3m-swap-details/utils.ts | 33 ++ .../src/partials/w3m-swap-input/index.tsx | 152 +++++++ .../src/partials/w3m-swap-input/styles.ts | 20 + packages/appkit/src/utils/ConnectorUtil.ts | 22 + packages/appkit/src/utils/UiUtil.ts | 42 ++ .../components/auth-buttons.tsx | 68 ++++ .../components/upgrade-wallet-button.tsx | 67 +++ .../views/w3m-account-default-view/index.tsx | 334 +++++++++++++++ .../views/w3m-account-default-view/styles.ts | 28 ++ .../src/views/w3m-account-view/index.tsx | 105 +++++ .../src/views/w3m-account-view/styles.ts | 32 ++ .../src/views/w3m-all-wallets-view/index.tsx | 107 +++++ .../src/views/w3m-all-wallets-view/styles.ts | 21 + .../views/w3m-connect-socials-view/index.tsx | 58 +++ .../views/w3m-connect-socials-view/styles.ts | 12 + .../components/all-wallet-list.tsx | 60 +++ .../components/all-wallets-button.tsx | 33 ++ .../components/connect-email-input.tsx | 69 ++++ .../components/connectors-list.tsx | 45 ++ .../components/custom-wallet-list.tsx | 41 ++ .../components/recent-wallet-list.tsx | 44 ++ .../components/social-login-list.tsx | 95 +++++ .../components/wallet-guide.tsx | 50 +++ .../src/views/w3m-connect-view/index.tsx | 140 +++++++ .../src/views/w3m-connect-view/styles.ts | 18 + .../src/views/w3m-connect-view/utils.ts | 14 + .../w3m-connecting-external-view/index.tsx | 131 ++++++ .../w3m-connecting-external-view/styles.ts | 20 + .../w3m-connecting-farcaster-view/index.tsx | 140 +++++++ .../w3m-connecting-farcaster-view/styles.ts | 18 + .../w3m-connecting-social-view/index.tsx | 153 +++++++ .../w3m-connecting-social-view/styles.ts | 15 + .../src/views/w3m-connecting-view/index.tsx | 158 +++++++ .../src/views/w3m-create-view/index.tsx | 35 ++ .../w3m-email-verify-device-view/index.tsx | 80 ++++ .../w3m-email-verify-device-view/styles.ts | 19 + .../views/w3m-email-verify-otp-view/index.tsx | 75 ++++ .../src/views/w3m-get-wallet-view/index.tsx | 50 +++ .../src/views/w3m-get-wallet-view/styles.ts | 8 + .../views/w3m-network-switch-view/index.tsx | 138 +++++++ .../views/w3m-network-switch-view/styles.ts | 23 ++ .../src/views/w3m-networks-view/index.tsx | 111 +++++ .../src/views/w3m-networks-view/styles.ts | 12 + .../views/w3m-onramp-checkout-view/index.tsx | 266 ++++++++++++ .../views/w3m-onramp-loading-view/index.tsx | 157 +++++++ .../views/w3m-onramp-loading-view/styles.ts | 23 ++ .../components/Country.tsx | 65 +++ .../views/w3m-onramp-settings-view/index.tsx | 145 +++++++ .../views/w3m-onramp-settings-view/styles.ts | 25 ++ .../views/w3m-onramp-settings-view/utils.ts | 90 ++++ .../w3m-onramp-transaction-view/index.tsx | 120 ++++++ .../w3m-onramp-transaction-view/styles.ts | 18 + .../w3m-onramp-view/components/Currency.tsx | 86 ++++ .../components/CurrencyInput.tsx | 169 ++++++++ .../w3m-onramp-view/components/Header.tsx | 47 +++ .../components/LoadingView.tsx | 43 ++ .../components/PaymentMethod.tsx | 97 +++++ .../w3m-onramp-view/components/Quote.tsx | 94 +++++ .../components/SelectPaymentModal.tsx | 255 ++++++++++++ .../src/views/w3m-onramp-view/index.tsx | 293 +++++++++++++ .../src/views/w3m-onramp-view/styles.ts | 41 ++ .../appkit/src/views/w3m-onramp-view/utils.ts | 124 ++++++ .../src/views/w3m-swap-preview-view/index.tsx | 145 +++++++ .../src/views/w3m-swap-preview-view/styles.ts | 18 + .../w3m-swap-select-token-view/index.tsx | 137 +++++++ .../w3m-swap-select-token-view/styles.ts | 30 ++ .../views/w3m-swap-select-token-view/utils.ts | 33 ++ .../appkit/src/views/w3m-swap-view/index.tsx | 209 ++++++++++ .../appkit/src/views/w3m-swap-view/styles.ts | 23 ++ .../src/views/w3m-transactions-view/index.tsx | 14 + .../w3m-unsupported-chain-view/index.tsx | 92 +++++ .../w3m-unsupported-chain-view/styles.ts | 21 + .../index.tsx | 55 +++ .../index.tsx | 56 +++ .../w3m-update-email-wallet-view/index.tsx | 96 +++++ .../w3m-update-email-wallet-view/styles.ts | 24 ++ .../w3m-upgrade-email-wallet-view/index.tsx | 38 ++ .../index.tsx | 106 +++++ .../styles.ts | 29 ++ .../index.tsx | 48 +++ .../styles.ts | 8 + .../views/w3m-wallet-receive-view/index.tsx | 94 +++++ .../views/w3m-wallet-receive-view/styles.ts | 8 + .../components/preview-send-details.tsx | 101 +++++ .../components/preview-send-pill.tsx | 36 ++ .../w3m-wallet-send-preview-view/index.tsx | 134 ++++++ .../w3m-wallet-send-preview-view/styles.ts | 35 ++ .../index.tsx | 83 ++++ .../styles.ts | 15 + .../src/views/w3m-wallet-send-view/index.tsx | 129 ++++++ .../src/views/w3m-wallet-send-view/styles.ts | 21 + .../w3m-what-is-a-network-view/index.tsx | 49 +++ .../w3m-what-is-a-network-view/styles.ts | 14 + .../views/w3m-what-is-a-wallet-view/index.tsx | 68 ++++ .../views/w3m-what-is-a-wallet-view/styles.ts | 15 + packages/appkit/tsconfig.json | 5 + packages/wagmi/package.json | 1 + 154 files changed, 10833 insertions(+) create mode 100644 packages/appkit/.eslintrc.json create mode 100644 packages/appkit/.npmignore create mode 100644 packages/appkit/CHANGELOG.md create mode 100644 packages/appkit/readme.md create mode 100644 packages/appkit/src/client.ts create mode 100644 packages/appkit/src/config/animations.ts create mode 100644 packages/appkit/src/hooks/useCustomDimensions.ts create mode 100644 packages/appkit/src/hooks/useDebounceCallback.ts create mode 100644 packages/appkit/src/hooks/useKeyboard.ts create mode 100644 packages/appkit/src/hooks/useProvider.ts create mode 100644 packages/appkit/src/hooks/useTimeout.ts create mode 100644 packages/appkit/src/index.ts create mode 100644 packages/appkit/src/modal/w3m-account-button/index.tsx create mode 100644 packages/appkit/src/modal/w3m-button/index.tsx create mode 100644 packages/appkit/src/modal/w3m-connect-button/index.tsx create mode 100644 packages/appkit/src/modal/w3m-modal/index.tsx create mode 100644 packages/appkit/src/modal/w3m-modal/styles.ts create mode 100644 packages/appkit/src/modal/w3m-network-button/index.tsx create mode 100644 packages/appkit/src/modal/w3m-router/index.tsx create mode 100644 packages/appkit/src/partials/w3m-account-activity/index.tsx create mode 100644 packages/appkit/src/partials/w3m-account-activity/styles.ts create mode 100644 packages/appkit/src/partials/w3m-account-activity/utils.ts create mode 100644 packages/appkit/src/partials/w3m-account-tokens/index.tsx create mode 100644 packages/appkit/src/partials/w3m-account-wallet-features/index.tsx create mode 100644 packages/appkit/src/partials/w3m-account-wallet-features/styles.ts create mode 100644 packages/appkit/src/partials/w3m-all-wallets-list/index.tsx create mode 100644 packages/appkit/src/partials/w3m-all-wallets-list/styles.ts create mode 100644 packages/appkit/src/partials/w3m-all-wallets-search/index.tsx create mode 100644 packages/appkit/src/partials/w3m-all-wallets-search/styles.ts create mode 100644 packages/appkit/src/partials/w3m-connecting-body/index.tsx create mode 100644 packages/appkit/src/partials/w3m-connecting-body/utils.ts create mode 100644 packages/appkit/src/partials/w3m-connecting-header/index.tsx create mode 100644 packages/appkit/src/partials/w3m-connecting-mobile/components/StoreLink.tsx create mode 100644 packages/appkit/src/partials/w3m-connecting-mobile/index.tsx create mode 100644 packages/appkit/src/partials/w3m-connecting-mobile/styles.ts create mode 100644 packages/appkit/src/partials/w3m-connecting-qrcode/index.tsx create mode 100644 packages/appkit/src/partials/w3m-connecting-qrcode/styles.ts create mode 100644 packages/appkit/src/partials/w3m-connecting-web/index.tsx create mode 100644 packages/appkit/src/partials/w3m-connecting-web/styles.ts create mode 100644 packages/appkit/src/partials/w3m-header/index.tsx create mode 100644 packages/appkit/src/partials/w3m-header/styles.ts create mode 100644 packages/appkit/src/partials/w3m-information-modal/index.tsx create mode 100644 packages/appkit/src/partials/w3m-information-modal/styles.ts create mode 100644 packages/appkit/src/partials/w3m-otp-code/index.tsx create mode 100644 packages/appkit/src/partials/w3m-otp-code/styles.ts create mode 100644 packages/appkit/src/partials/w3m-placeholder/index.tsx create mode 100644 packages/appkit/src/partials/w3m-selector-modal/index.tsx create mode 100644 packages/appkit/src/partials/w3m-selector-modal/styles.ts create mode 100644 packages/appkit/src/partials/w3m-send-input-address/index.tsx create mode 100644 packages/appkit/src/partials/w3m-send-input-address/styles.ts create mode 100644 packages/appkit/src/partials/w3m-send-input-token/index.tsx create mode 100644 packages/appkit/src/partials/w3m-send-input-token/styles.ts create mode 100644 packages/appkit/src/partials/w3m-send-input-token/utils.ts create mode 100644 packages/appkit/src/partials/w3m-snackbar/index.tsx create mode 100644 packages/appkit/src/partials/w3m-snackbar/styles.ts create mode 100644 packages/appkit/src/partials/w3m-swap-details/index.tsx create mode 100644 packages/appkit/src/partials/w3m-swap-details/styles.ts create mode 100644 packages/appkit/src/partials/w3m-swap-details/utils.ts create mode 100644 packages/appkit/src/partials/w3m-swap-input/index.tsx create mode 100644 packages/appkit/src/partials/w3m-swap-input/styles.ts create mode 100644 packages/appkit/src/utils/ConnectorUtil.ts create mode 100644 packages/appkit/src/utils/UiUtil.ts create mode 100644 packages/appkit/src/views/w3m-account-default-view/components/auth-buttons.tsx create mode 100644 packages/appkit/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx create mode 100644 packages/appkit/src/views/w3m-account-default-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-account-default-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-account-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-account-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-all-wallets-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-all-wallets-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-connect-socials-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-connect-socials-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-connect-view/components/all-wallet-list.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/components/all-wallets-button.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/components/connect-email-input.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/components/connectors-list.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/components/custom-wallet-list.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/components/recent-wallet-list.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/components/social-login-list.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/components/wallet-guide.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-connect-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-connect-view/utils.ts create mode 100644 packages/appkit/src/views/w3m-connecting-external-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-connecting-external-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-connecting-farcaster-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-connecting-farcaster-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-connecting-social-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-connecting-social-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-connecting-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-create-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-email-verify-device-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-email-verify-device-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-email-verify-otp-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-get-wallet-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-get-wallet-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-network-switch-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-network-switch-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-networks-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-networks-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-loading-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-loading-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-onramp-settings-view/components/Country.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-settings-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-settings-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-onramp-settings-view/utils.ts create mode 100644 packages/appkit/src/views/w3m-onramp-transaction-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-transaction-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-onramp-view/components/Currency.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/components/CurrencyInput.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/components/Header.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/components/LoadingView.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/components/PaymentMethod.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/components/Quote.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-onramp-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-onramp-view/utils.ts create mode 100644 packages/appkit/src/views/w3m-swap-preview-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-swap-preview-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-swap-select-token-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-swap-select-token-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-swap-select-token-view/utils.ts create mode 100644 packages/appkit/src/views/w3m-swap-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-swap-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-transactions-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-unsupported-chain-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-update-email-primary-otp-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-update-email-secondary-otp-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-update-email-wallet-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-update-email-wallet-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-upgrade-email-wallet-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-upgrade-to-smart-account-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-upgrade-to-smart-account-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-wallet-compatible-networks-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-wallet-receive-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-wallet-receive-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx create mode 100644 packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx create mode 100644 packages/appkit/src/views/w3m-wallet-send-preview-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-wallet-send-preview-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-wallet-send-select-token-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-wallet-send-select-token-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-wallet-send-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-wallet-send-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-what-is-a-network-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-what-is-a-network-view/styles.ts create mode 100644 packages/appkit/src/views/w3m-what-is-a-wallet-view/index.tsx create mode 100644 packages/appkit/src/views/w3m-what-is-a-wallet-view/styles.ts create mode 100644 packages/appkit/tsconfig.json diff --git a/packages/appkit/.eslintrc.json b/packages/appkit/.eslintrc.json new file mode 100644 index 00000000..b9233ee4 --- /dev/null +++ b/packages/appkit/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} diff --git a/packages/appkit/.npmignore b/packages/appkit/.npmignore new file mode 100644 index 00000000..e203f76a --- /dev/null +++ b/packages/appkit/.npmignore @@ -0,0 +1,10 @@ +*.log +*.env +npm-debug.log* +node_modules +package-lock.json +src +tests +index.ts +.eslintrc.json +.turbo diff --git a/packages/appkit/CHANGELOG.md b/packages/appkit/CHANGELOG.md new file mode 100644 index 00000000..cc85a1b2 --- /dev/null +++ b/packages/appkit/CHANGELOG.md @@ -0,0 +1,153 @@ +# @reown/appkit-scaffold-react-native + +## 1.2.3 + +### Patch Changes + +- [#322](https://github.com/reown-com/appkit-react-native/pull/322) [`e0b59fb`](https://github.com/reown-com/appkit-react-native/commit/e0b59fb217fab570efc876265d13647844bb79d2) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: clear balance if the sdk fails to get it + +- [#327](https://github.com/reown-com/appkit-react-native/pull/327) [`bdc0388`](https://github.com/reown-com/appkit-react-native/commit/bdc038847695f45ed2e5b88f70fdec4ae17abcf7) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: deduplicate wallets in all wallet list + +- [#331](https://github.com/reown-com/appkit-react-native/pull/331) [`c75e94a`](https://github.com/reown-com/appkit-react-native/commit/c75e94ae97d2e7b5017a2fa879a58fb1d3a03821) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: solved issue with wallet page loaders + +- Updated dependencies [[`e0b59fb`](https://github.com/reown-com/appkit-react-native/commit/e0b59fb217fab570efc876265d13647844bb79d2), [`bdc0388`](https://github.com/reown-com/appkit-react-native/commit/bdc038847695f45ed2e5b88f70fdec4ae17abcf7), [`c75e94a`](https://github.com/reown-com/appkit-react-native/commit/c75e94ae97d2e7b5017a2fa879a58fb1d3a03821)]: + - @reown/appkit-common-react-native@1.2.3 + - @reown/appkit-core-react-native@1.2.3 + - @reown/appkit-siwe-react-native@1.2.3 + - @reown/appkit-ui-react-native@1.2.3 + +## 1.2.2 + +### Patch Changes + +- [#316](https://github.com/reown-com/appkit-react-native/pull/316) [`f3f4f37`](https://github.com/reown-com/appkit-react-native/commit/f3f4f3754673e0e85c110f41d9c77e27e30caa68) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: load transactions when needed + +- [#312](https://github.com/reown-com/appkit-react-native/pull/312) [`52484c4`](https://github.com/reown-com/appkit-react-native/commit/52484c4ee17d7651c966d7fe939c51a7af516c14) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show wallet info in useWalletInfo hook for ethers and ethers5 + +- [#314](https://github.com/reown-com/appkit-react-native/pull/314) [`d3a9604`](https://github.com/reown-com/appkit-react-native/commit/d3a9604b59fd177465fbf5b04ee3c26200e40384) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: improved state array updates in connection and router controllers + +- [#315](https://github.com/reown-com/appkit-react-native/pull/315) [`19e17ef`](https://github.com/reown-com/appkit-react-native/commit/19e17ef9b85e4d81f424438d83c3f3c89dd80c92) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: send expo info in useragent + +- Updated dependencies [[`f3f4f37`](https://github.com/reown-com/appkit-react-native/commit/f3f4f3754673e0e85c110f41d9c77e27e30caa68), [`52484c4`](https://github.com/reown-com/appkit-react-native/commit/52484c4ee17d7651c966d7fe939c51a7af516c14), [`d3a9604`](https://github.com/reown-com/appkit-react-native/commit/d3a9604b59fd177465fbf5b04ee3c26200e40384), [`19e17ef`](https://github.com/reown-com/appkit-react-native/commit/19e17ef9b85e4d81f424438d83c3f3c89dd80c92)]: + - @reown/appkit-common-react-native@1.2.2 + - @reown/appkit-core-react-native@1.2.2 + - @reown/appkit-siwe-react-native@1.2.2 + - @reown/appkit-ui-react-native@1.2.2 + +## 1.2.1 + +### Patch Changes + +- [#308](https://github.com/reown-com/appkit-react-native/pull/308) [`cdc1af3`](https://github.com/reown-com/appkit-react-native/commit/cdc1af3878a9193b34c530d2a867397941ed0bbf) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: added event subscription hook + +- [#307](https://github.com/reown-com/appkit-react-native/pull/307) [`bec3342`](https://github.com/reown-com/appkit-react-native/commit/bec3342e41d80a93e6eb5fca4883d97dd7dc64c2) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed condition for initial modal loading + +- Updated dependencies [[`cdc1af3`](https://github.com/reown-com/appkit-react-native/commit/cdc1af3878a9193b34c530d2a867397941ed0bbf), [`bec3342`](https://github.com/reown-com/appkit-react-native/commit/bec3342e41d80a93e6eb5fca4883d97dd7dc64c2)]: + - @reown/appkit-core-react-native@1.2.1 + - @reown/appkit-common-react-native@1.2.1 + - @reown/appkit-siwe-react-native@1.2.1 + - @reown/appkit-ui-react-native@1.2.1 + +## 1.2.0 + +### Minor Changes + +- [#299](https://github.com/reown-com/appkit-react-native/pull/299) [`278023f`](https://github.com/reown-com/appkit-react-native/commit/278023fa03c4a09be4c2b2b0d2b65e86d05e589b) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: swaps feature + +- [#300](https://github.com/reown-com/appkit-react-native/pull/300) [`db1e2a8`](https://github.com/reown-com/appkit-react-native/commit/db1e2a88d2c263fca438a99e0020aa1b2c55e360) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: added ability to change themeMode and override accent color + +### Patch Changes + +- fix: set loading when account data is being synced in appkit-wagmi + +- Updated dependencies [[`278023f`](https://github.com/reown-com/appkit-react-native/commit/278023fa03c4a09be4c2b2b0d2b65e86d05e589b), [`db1e2a8`](https://github.com/reown-com/appkit-react-native/commit/db1e2a88d2c263fca438a99e0020aa1b2c55e360)]: + - @reown/appkit-common-react-native@1.2.0 + - @reown/appkit-core-react-native@1.2.0 + - @reown/appkit-ui-react-native@1.2.0 + - @reown/appkit-siwe-react-native@1.2.0 + +## 1.1.1 + +### Patch Changes + +- [#292](https://github.com/reown-com/appkit-react-native/pull/292) [`ff75ba8`](https://github.com/reown-com/appkit-react-native/commit/ff75ba8ce9828b85f0c38a08e0ce33e3020c48d8) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: solved auth ethers connector type error + +- Updated dependencies [[`ff75ba8`](https://github.com/reown-com/appkit-react-native/commit/ff75ba8ce9828b85f0c38a08e0ce33e3020c48d8)]: + - @reown/appkit-common-react-native@1.1.1 + - @reown/appkit-core-react-native@1.1.1 + - @reown/appkit-siwe-react-native@1.1.1 + - @reown/appkit-ui-react-native@1.1.1 + +## 1.1.0 + +### Minor Changes + +- [#265](https://github.com/reown-com/appkit-react-native/pull/265) [`fb85422`](https://github.com/reown-com/appkit-react-native/commit/fb85422e0d544efc21a686287d91b16a638cf99c) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: social login + +- [#230](https://github.com/reown-com/appkit-react-native/pull/230) [`b02a7d3`](https://github.com/reown-com/appkit-react-native/commit/b02a7d39f4c23802dd4619671fe263a2faa17775) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: added wallet features for universal wallets + +- [#266](https://github.com/reown-com/appkit-react-native/pull/266) [`3193c6f`](https://github.com/reown-com/appkit-react-native/commit/3193c6fac3a2d7f83f53bf982180469e23f156c4) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: smart accounts for social login + +### Patch Changes + +- [#276](https://github.com/reown-com/appkit-react-native/pull/276) [`1fe0f3f`](https://github.com/reown-com/appkit-react-native/commit/1fe0f3fa0f96d14f63f2ffc96092b2bc239c4f92) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed siwe imports to solve issues on some android devices + +- [#273](https://github.com/reown-com/appkit-react-native/pull/273) [`aa3ba46`](https://github.com/reown-com/appkit-react-native/commit/aa3ba464c88cf38aae54b5e2185b64b513418b1b) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: watch token balance + +- [#268](https://github.com/reown-com/appkit-react-native/pull/268) [`0d2bae4`](https://github.com/reown-com/appkit-react-native/commit/0d2bae4a91063fc9fc6feb10e9fe780924cb670f) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: removed nft tab + +- [#272](https://github.com/reown-com/appkit-react-native/pull/272) [`5f7b88c`](https://github.com/reown-com/appkit-react-native/commit/5f7b88c220a312b07c1787dbd8aae2ee8d3a1120) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: small changes for web + +- [#274](https://github.com/reown-com/appkit-react-native/pull/274) [`f7e04d4`](https://github.com/reown-com/appkit-react-native/commit/f7e04d48a546d6c2b647abf417761d45570f11ef) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show custom wallets in all wallets view + +- [#275](https://github.com/reown-com/appkit-react-native/pull/275) [`2cb59a4`](https://github.com/reown-com/appkit-react-native/commit/2cb59a46a90bea3b919f47f4be8f485d867890ef) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: improved loading message in social connections + +- [#262](https://github.com/reown-com/appkit-react-native/pull/262) [`19bbffd`](https://github.com/reown-com/appkit-react-native/commit/19bbffde847b8d962d30e7dedacd93a4ef0c543c) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: get custom token balance if provided + +- [#240](https://github.com/reown-com/appkit-react-native/pull/240) [`6f8ddfa`](https://github.com/reown-com/appkit-react-native/commit/6f8ddfa292a122356565f710de1d2daf19210369) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed headers function in event controller + +- [#267](https://github.com/reown-com/appkit-react-native/pull/267) [`37983e7`](https://github.com/reown-com/appkit-react-native/commit/37983e71f78f85bcbd6b3c1708ddb359b920319b) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: bump walletconnect deps to 2.17.2 in ethers packages + +- [#238](https://github.com/reown-com/appkit-react-native/pull/238) [`68dc856`](https://github.com/reown-com/appkit-react-native/commit/68dc856c4ab39ebd17e1885b8536fa3a1a391186) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: updated packages info + +- [#277](https://github.com/reown-com/appkit-react-native/pull/277) [`285a64b`](https://github.com/reown-com/appkit-react-native/commit/285a64bfb310913c79b1ba9a85a82387e41a63ff) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: ui changes in social webview to solve android issues + +- [#271](https://github.com/reown-com/appkit-react-native/pull/271) [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show error message and retry button in case wallet fetch fails + +- [#289](https://github.com/reown-com/appkit-react-native/pull/289) [`047cb8e`](https://github.com/reown-com/appkit-react-native/commit/047cb8e2d4a435e1728cf1918c3754c217a60be2) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: updated ethereum provider to 2.17.3 + +- [#269](https://github.com/reown-com/appkit-react-native/pull/269) [`f5ca754`](https://github.com/reown-com/appkit-react-native/commit/f5ca7548115d25e121931961295d822ecd13833c) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show wallet view for email wallets + +- [#271](https://github.com/reown-com/appkit-react-native/pull/271) [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: improved provider error handling + +- Updated dependencies [[`1fe0f3f`](https://github.com/reown-com/appkit-react-native/commit/1fe0f3fa0f96d14f63f2ffc96092b2bc239c4f92), [`fb85422`](https://github.com/reown-com/appkit-react-native/commit/fb85422e0d544efc21a686287d91b16a638cf99c), [`aa3ba46`](https://github.com/reown-com/appkit-react-native/commit/aa3ba464c88cf38aae54b5e2185b64b513418b1b), [`0d2bae4`](https://github.com/reown-com/appkit-react-native/commit/0d2bae4a91063fc9fc6feb10e9fe780924cb670f), [`5f7b88c`](https://github.com/reown-com/appkit-react-native/commit/5f7b88c220a312b07c1787dbd8aae2ee8d3a1120), [`f7e04d4`](https://github.com/reown-com/appkit-react-native/commit/f7e04d48a546d6c2b647abf417761d45570f11ef), [`2cb59a4`](https://github.com/reown-com/appkit-react-native/commit/2cb59a46a90bea3b919f47f4be8f485d867890ef), [`b02a7d3`](https://github.com/reown-com/appkit-react-native/commit/b02a7d39f4c23802dd4619671fe263a2faa17775), [`19bbffd`](https://github.com/reown-com/appkit-react-native/commit/19bbffde847b8d962d30e7dedacd93a4ef0c543c), [`6f8ddfa`](https://github.com/reown-com/appkit-react-native/commit/6f8ddfa292a122356565f710de1d2daf19210369), [`37983e7`](https://github.com/reown-com/appkit-react-native/commit/37983e71f78f85bcbd6b3c1708ddb359b920319b), [`3193c6f`](https://github.com/reown-com/appkit-react-native/commit/3193c6fac3a2d7f83f53bf982180469e23f156c4), [`68dc856`](https://github.com/reown-com/appkit-react-native/commit/68dc856c4ab39ebd17e1885b8536fa3a1a391186), [`285a64b`](https://github.com/reown-com/appkit-react-native/commit/285a64bfb310913c79b1ba9a85a82387e41a63ff), [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150), [`047cb8e`](https://github.com/reown-com/appkit-react-native/commit/047cb8e2d4a435e1728cf1918c3754c217a60be2), [`f5ca754`](https://github.com/reown-com/appkit-react-native/commit/f5ca7548115d25e121931961295d822ecd13833c), [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150)]: + - @reown/appkit-common-react-native@1.1.0 + - @reown/appkit-core-react-native@1.1.0 + - @reown/appkit-siwe-react-native@1.1.0 + - @reown/appkit-ui-react-native@1.1.0 + +## 1.0.2 + +### Patch Changes + +- [#260](https://github.com/reown-com/appkit-react-native/pull/260) [`d5c55ed`](https://github.com/reown-com/appkit-react-native/commit/d5c55eda869ff874f44770110f2381d144d2c3b4) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed init config of email provider + +- Updated dependencies [[`d5c55ed`](https://github.com/reown-com/appkit-react-native/commit/d5c55eda869ff874f44770110f2381d144d2c3b4)]: + - @reown/appkit-common-react-native@1.0.2 + - @reown/appkit-core-react-native@1.0.2 + - @reown/appkit-siwe-react-native@1.0.2 + - @reown/appkit-ui-react-native@1.0.2 + +## 1.0.1 + +### Patch Changes + +- [#257](https://github.com/reown-com/appkit-react-native/pull/257) [`4b1ce96`](https://github.com/reown-com/appkit-react-native/commit/4b1ce966d087b7bf79efdda7d3e63bd89e5b9433) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: added timeout for secure site + +- [#259](https://github.com/reown-com/appkit-react-native/pull/259) [`656ed84`](https://github.com/reown-com/appkit-react-native/commit/656ed849754ad90dbb51b4e7a79183ccf8ae11de) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed siwe import package + +- Updated dependencies [[`4b1ce96`](https://github.com/reown-com/appkit-react-native/commit/4b1ce966d087b7bf79efdda7d3e63bd89e5b9433), [`656ed84`](https://github.com/reown-com/appkit-react-native/commit/656ed849754ad90dbb51b4e7a79183ccf8ae11de)]: + - @reown/appkit-common-react-native@1.0.1 + - @reown/appkit-core-react-native@1.0.1 + - @reown/appkit-siwe-react-native@1.0.1 + - @reown/appkit-ui-react-native@1.0.1 diff --git a/packages/appkit/readme.md b/packages/appkit/readme.md new file mode 100644 index 00000000..60524ccd --- /dev/null +++ b/packages/appkit/readme.md @@ -0,0 +1,9 @@ +#### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) + +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) + +#### 🔗 [Website](https://reown.com/appkit) + +# AppKit + +Your on-ramp to web3 multichain. AppKit is a versatile library that makes it super easy to connect users with your Dapp and start interacting with the blockchain. diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts new file mode 100644 index 00000000..3cd6b06b --- /dev/null +++ b/packages/appkit/src/client.ts @@ -0,0 +1,384 @@ +import './config/animations'; + +import type { + AccountControllerState, + ConnectionControllerClient, + ModalControllerState, + NetworkControllerClient, + NetworkControllerState, + OptionsControllerState, + EventsControllerState, + PublicStateControllerState, + ThemeControllerState, + Connector, + ConnectedWalletInfo, + Features, + EventName +} from '@reown/appkit-core-react-native'; +import { SIWEController, type SIWEControllerClient } from '@reown/appkit-siwe-react-native'; +import { + AccountController, + BlockchainApiController, + ConnectionController, + ConnectorController, + EnsController, + EventsController, + ModalController, + NetworkController, + OptionsController, + PublicStateController, + SnackController, + StorageUtil, + ThemeController, + TransactionsController +} from '@reown/appkit-core-react-native'; +import { + ConstantsUtil, + ErrorUtil, + type ThemeMode, + type ThemeVariables +} from '@reown/appkit-common-react-native'; +import { Appearance } from 'react-native'; + +// -- Types --------------------------------------------------------------------- +export interface LibraryOptions { + projectId: OptionsControllerState['projectId']; + metadata: OptionsControllerState['metadata']; + themeMode?: ThemeMode; + themeVariables?: ThemeVariables; + includeWalletIds?: OptionsControllerState['includeWalletIds']; + excludeWalletIds?: OptionsControllerState['excludeWalletIds']; + featuredWalletIds?: OptionsControllerState['featuredWalletIds']; + customWallets?: OptionsControllerState['customWallets']; + defaultChain?: NetworkControllerState['caipNetwork']; + tokens?: OptionsControllerState['tokens']; + clipboardClient?: OptionsControllerState['_clipboardClient']; + enableAnalytics?: OptionsControllerState['enableAnalytics']; + _sdkVersion: OptionsControllerState['sdkVersion']; + debug?: OptionsControllerState['debug']; + features?: Features; +} + +export interface ScaffoldOptions extends LibraryOptions { + networkControllerClient: NetworkControllerClient; + connectionControllerClient: ConnectionControllerClient; + siweControllerClient?: SIWEControllerClient; +} + +export interface OpenOptions { + view: 'Account' | 'Connect' | 'Networks' | 'Swap' | 'OnRamp'; +} + +// -- Client -------------------------------------------------------------------- +export class AppKitScaffold { + public reportedAlertErrors: Record = {}; + + public constructor(options: ScaffoldOptions) { + this.initControllers(options); + } + + // -- Public ------------------------------------------------------------------- + public async open(options?: OpenOptions) { + ModalController.open(options); + } + + public async close() { + ModalController.close(); + } + + public getThemeMode() { + return ThemeController.state.themeMode; + } + + public getThemeVariables() { + return ThemeController.state.themeVariables; + } + + public setThemeMode(themeMode: ThemeControllerState['themeMode']) { + ThemeController.setThemeMode(themeMode); + } + + public setThemeVariables(themeVariables: ThemeControllerState['themeVariables']) { + ThemeController.setThemeVariables(themeVariables); + } + + public subscribeTheme(callback: (newState: ThemeControllerState) => void) { + return ThemeController.subscribe(callback); + } + + public getWalletInfo() { + return AccountController.state.connectedWalletInfo; + } + + public subscribeWalletInfo(callback: (newState: ConnectedWalletInfo) => void) { + return AccountController.subscribeKey('connectedWalletInfo', callback); + } + + public getState() { + return { ...PublicStateController.state }; + } + + public subscribeState(callback: (newState: PublicStateControllerState) => void) { + return PublicStateController.subscribe(callback); + } + + public subscribeStateKey( + key: K, + callback: (value: PublicStateControllerState[K]) => void + ) { + return PublicStateController.subscribeKey(key, callback); + } + + public subscribeConnection( + callback: (isConnected: AccountControllerState['isConnected']) => void + ) { + return AccountController.subscribeKey('isConnected', callback); + } + + public setLoading(loading: ModalControllerState['loading']) { + ModalController.setLoading(loading); + } + + public getEvent() { + return { ...EventsController.state }; + } + + public subscribeEvents(callback: (newEvent: EventsControllerState) => void) { + return EventsController.subscribe(callback); + } + + public subscribeEvent(event: EventName, callback: (newEvent: EventsControllerState) => void) { + return EventsController.subscribeEvent(event, callback); + } + + public resolveReownName = async (name: string) => { + const wcNameAddress = await EnsController.resolveName(name); + const networkNameAddresses = wcNameAddress?.addresses + ? Object.values(wcNameAddress?.addresses) + : []; + + return networkNameAddresses[0]?.address || false; + }; + + // -- Protected ---------------------------------------------------------------- + protected setIsConnected: (typeof AccountController)['setIsConnected'] = isConnected => { + AccountController.setIsConnected(isConnected); + }; + + protected setCaipAddress: (typeof AccountController)['setCaipAddress'] = caipAddress => { + AccountController.setCaipAddress(caipAddress); + }; + + protected getCaipAddress = () => AccountController.state.caipAddress; + + protected setBalance: (typeof AccountController)['setBalance'] = (balance, balanceSymbol) => { + AccountController.setBalance(balance, balanceSymbol); + }; + + protected setProfileName: (typeof AccountController)['setProfileName'] = profileName => { + AccountController.setProfileName(profileName); + }; + + protected setProfileImage: (typeof AccountController)['setProfileImage'] = profileImage => { + AccountController.setProfileImage(profileImage); + }; + + protected resetAccount: (typeof AccountController)['resetAccount'] = () => { + AccountController.resetAccount(); + }; + + protected setCaipNetwork: (typeof NetworkController)['setCaipNetwork'] = caipNetwork => { + NetworkController.setCaipNetwork(caipNetwork); + }; + + protected getCaipNetwork = () => NetworkController.state.caipNetwork; + + protected setRequestedCaipNetworks: (typeof NetworkController)['setRequestedCaipNetworks'] = + requestedCaipNetworks => { + NetworkController.setRequestedCaipNetworks(requestedCaipNetworks); + }; + + protected getApprovedCaipNetworksData: (typeof NetworkController)['getApprovedCaipNetworksData'] = + () => NetworkController.getApprovedCaipNetworksData(); + + protected resetNetwork: (typeof NetworkController)['resetNetwork'] = () => { + NetworkController.resetNetwork(); + }; + + protected setConnectors: (typeof ConnectorController)['setConnectors'] = ( + connectors: Connector[] + ) => { + ConnectorController.setConnectors(connectors); + this.setConnectorExcludedWallets(connectors); + }; + + protected addConnector: (typeof ConnectorController)['addConnector'] = (connector: Connector) => { + ConnectorController.addConnector(connector); + }; + + protected getConnectors: (typeof ConnectorController)['getConnectors'] = () => + ConnectorController.getConnectors(); + + protected resetWcConnection: (typeof ConnectionController)['resetWcConnection'] = () => { + ConnectionController.resetWcConnection(); + TransactionsController.resetTransactions(); + }; + + protected fetchIdentity: (typeof BlockchainApiController)['fetchIdentity'] = request => + BlockchainApiController.fetchIdentity(request); + + protected setAddressExplorerUrl: (typeof AccountController)['setAddressExplorerUrl'] = + addressExplorerUrl => { + AccountController.setAddressExplorerUrl(addressExplorerUrl); + }; + + protected setConnectedWalletInfo: (typeof AccountController)['setConnectedWalletInfo'] = + connectedWalletInfo => { + AccountController.setConnectedWalletInfo(connectedWalletInfo); + }; + + protected setClientId: (typeof BlockchainApiController)['setClientId'] = clientId => { + BlockchainApiController.setClientId(clientId); + }; + + protected setPreferredAccountType: (typeof AccountController)['setPreferredAccountType'] = + preferredAccountType => { + AccountController.setPreferredAccountType(preferredAccountType); + }; + + protected handleAlertError(error?: string | { shortMessage: string; longMessage: string }) { + if (!error) return; + + if (typeof error === 'object') { + SnackController.showInternalError(error); + + return; + } + + // Check if the error is a universal provider error + const matchedUniversalProviderError = Object.entries(ErrorUtil.UniversalProviderErrors).find( + ([, { message }]) => error?.includes(message) + ); + + const [errorKey, errorValue] = matchedUniversalProviderError ?? []; + + const { message, alertErrorKey } = errorValue ?? {}; + + if (errorKey && message && !this.reportedAlertErrors[errorKey]) { + const alertError = + ErrorUtil.ALERT_ERRORS[alertErrorKey as keyof typeof ErrorUtil.ALERT_ERRORS]; + + if (alertError) { + SnackController.showInternalError(alertError); + this.reportedAlertErrors[errorKey] = true; + } + } + } + + // -- Private ------------------------------------------------------------------ + private async initControllers(options: ScaffoldOptions) { + this.initAsyncValues(options); + NetworkController.setClient(options.networkControllerClient); + NetworkController.setDefaultCaipNetwork(options.defaultChain); + + OptionsController.setProjectId(options.projectId); + OptionsController.setIncludeWalletIds(options.includeWalletIds); + OptionsController.setExcludeWalletIds(options.excludeWalletIds); + OptionsController.setFeaturedWalletIds(options.featuredWalletIds); + OptionsController.setTokens(options.tokens); + OptionsController.setCustomWallets(options.customWallets); + OptionsController.setEnableAnalytics(options.enableAnalytics); + OptionsController.setSdkVersion(options._sdkVersion); + OptionsController.setDebug(options.debug); + + if (options.clipboardClient) { + OptionsController.setClipboardClient(options.clipboardClient); + } + + ConnectionController.setClient(options.connectionControllerClient); + + if (options.themeMode) { + ThemeController.setThemeMode(options.themeMode); + } else { + ThemeController.setThemeMode(Appearance.getColorScheme() as ThemeMode); + } + + if (options.themeVariables) { + ThemeController.setThemeVariables(options.themeVariables); + } + if (options.metadata) { + OptionsController.setMetadata(options.metadata); + } + + if (options.siweControllerClient) { + SIWEController.setSIWEClient(options.siweControllerClient); + } + + if (options.features) { + OptionsController.setFeatures(options.features); + } + + if ( + (options.features?.onramp === true || options.features?.onramp === undefined) && + (options.metadata?.redirect?.universal || options.metadata?.redirect?.native) + ) { + OptionsController.setIsOnRampEnabled(true); + } + } + + private async setConnectorExcludedWallets(connectors: Connector[]) { + const excludedWallets = OptionsController.state.excludeWalletIds || []; + + // Exclude Coinbase if the connector is not implemented + const excludeCoinbase = + connectors.findIndex(connector => connector.id === ConstantsUtil.COINBASE_CONNECTOR_ID) === + -1; + + if (excludeCoinbase) { + excludedWallets.push(ConstantsUtil.COINBASE_EXPLORER_ID); + } + + OptionsController.setExcludeWalletIds(excludedWallets); + } + + private async initRecentWallets(options: ScaffoldOptions) { + const wallets = await StorageUtil.getRecentWallets(); + const connectedWalletImage = await StorageUtil.getConnectedWalletImageUrl(); + + const filteredWallets = wallets.filter(wallet => { + const { includeWalletIds, excludeWalletIds } = options; + if (includeWalletIds) { + return includeWalletIds.includes(wallet.id); + } + if (excludeWalletIds) { + return !excludeWalletIds.includes(wallet.id); + } + + return true; + }); + + ConnectionController.setRecentWallets(filteredWallets); + + if (connectedWalletImage) { + ConnectionController.setConnectedWalletImageUrl(connectedWalletImage); + } + } + + private async initConnectedConnector() { + const connectedConnector = await StorageUtil.getConnectedConnector(); + if (connectedConnector) { + ConnectorController.setConnectedConnector(connectedConnector, false); + } + } + + private async initSocial() { + const connectedSocialProvider = await StorageUtil.getConnectedSocialProvider(); + ConnectionController.setConnectedSocialProvider(connectedSocialProvider); + } + + private async initAsyncValues(options: ScaffoldOptions) { + await this.initConnectedConnector(); + await this.initRecentWallets(options); + await this.initSocial(); + } +} diff --git a/packages/appkit/src/config/animations.ts b/packages/appkit/src/config/animations.ts new file mode 100644 index 00000000..ff7034f0 --- /dev/null +++ b/packages/appkit/src/config/animations.ts @@ -0,0 +1,7 @@ +import { Platform, UIManager } from 'react-native'; + +if (Platform.OS === 'android') { + if (UIManager.setLayoutAnimationEnabledExperimental) { + UIManager.setLayoutAnimationEnabledExperimental(true); + } +} diff --git a/packages/appkit/src/hooks/useCustomDimensions.ts b/packages/appkit/src/hooks/useCustomDimensions.ts new file mode 100644 index 00000000..c446a39d --- /dev/null +++ b/packages/appkit/src/hooks/useCustomDimensions.ts @@ -0,0 +1,21 @@ +import { useState, useEffect } from 'react'; +import { useWindowDimensions } from 'react-native'; + +/** + * Hook used to get the width of the screen and the padding needed to accomplish portrait and landscape modes. + * @returns { width: number, isPortrait: boolean, isLandscape: boolean, padding: number } + */ +export function useCustomDimensions() { + const { width, height } = useWindowDimensions(); + const [maxWidth, setMaxWidth] = useState(Math.min(width, height)); + const [isPortrait, setIsPortrait] = useState(height > width); + const [padding, setPadding] = useState(0); + + useEffect(() => { + setMaxWidth(Math.min(width, height)); + setIsPortrait(height > width); + setPadding(width < height ? 0 : (width - height) / 2); + }, [width, height]); + + return { maxWidth, isPortrait, isLandscape: !isPortrait, padding }; +} diff --git a/packages/appkit/src/hooks/useDebounceCallback.ts b/packages/appkit/src/hooks/useDebounceCallback.ts new file mode 100644 index 00000000..684ca1ad --- /dev/null +++ b/packages/appkit/src/hooks/useDebounceCallback.ts @@ -0,0 +1,45 @@ +import { useCallback, useEffect, useRef } from 'react'; + +interface Props { + callback: ((args?: any) => any) | ((args?: any) => Promise); + delay?: number; +} + +export function useDebounceCallback({ callback, delay = 250 }: Props) { + const timeoutRef = useRef(null); + const callbackRef = useRef(callback); + + useEffect(() => { + callbackRef.current = callback; + }, [callback]); + + const abort = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }, []); + + const debouncedCallback = useCallback( + (args?: any) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = setTimeout(() => { + callbackRef.current(args); + }, delay); + }, + [delay] + ); + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + return { debouncedCallback, abort }; +} diff --git a/packages/appkit/src/hooks/useKeyboard.ts b/packages/appkit/src/hooks/useKeyboard.ts new file mode 100644 index 00000000..ba064536 --- /dev/null +++ b/packages/appkit/src/hooks/useKeyboard.ts @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react'; +import { Keyboard, type KeyboardEventListener, type KeyboardMetrics } from 'react-native'; + +const emptyCoordinates = Object.freeze({ + screenX: 0, + screenY: 0, + width: 0, + height: 0 +}); +const initialValue = { + start: emptyCoordinates, + end: emptyCoordinates +}; + +export function useKeyboard() { + const [shown, setShown] = useState(false); + const [coordinates, setCoordinates] = useState<{ + start: undefined | KeyboardMetrics; + end: KeyboardMetrics; + }>(initialValue); + const [keyboardHeight, setKeyboardHeight] = useState(0); + + const handleKeyboardWillShow: KeyboardEventListener = e => { + setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); + }; + const handleKeyboardDidShow: KeyboardEventListener = e => { + setShown(true); + setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); + setKeyboardHeight(e.endCoordinates.height); + }; + const handleKeyboardWillHide: KeyboardEventListener = e => { + setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); + }; + const handleKeyboardDidHide: KeyboardEventListener = e => { + setShown(false); + if (e) { + setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); + } else { + setCoordinates(initialValue); + setKeyboardHeight(0); + } + }; + + useEffect(() => { + const subscriptions = [ + Keyboard.addListener('keyboardWillShow', handleKeyboardWillShow), + Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow), + Keyboard.addListener('keyboardWillHide', handleKeyboardWillHide), + Keyboard.addListener('keyboardDidHide', handleKeyboardDidHide) + ]; + + return () => { + subscriptions.forEach(subscription => subscription.remove()); + }; + }, []); + + return { + keyboardShown: shown, + coordinates, + keyboardHeight + }; +} diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts new file mode 100644 index 00000000..882046ce --- /dev/null +++ b/packages/appkit/src/hooks/useProvider.ts @@ -0,0 +1,11 @@ +import { useSnapshot } from 'valtio'; +import { ConnectionController } from '../controllers/ConnectionController'; + +export function useProvider(namespace: string): T | null { + const { connections } = useSnapshot(ConnectionController.state); + const connection = connections[namespace]; + + if (!connection) return null; + + return (connection.adapter as any).currentConnector?.getProvider() as T; +} \ No newline at end of file diff --git a/packages/appkit/src/hooks/useTimeout.ts b/packages/appkit/src/hooks/useTimeout.ts new file mode 100644 index 00000000..90e02795 --- /dev/null +++ b/packages/appkit/src/hooks/useTimeout.ts @@ -0,0 +1,34 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +function useTimeout(delay: number) { + const timeLeftRef = useRef(delay); + const [timeLeft, setTimeLeft] = useState(delay); + const interval = useRef(); + + const startTimer = useCallback((newDelay: number) => { + timeLeftRef.current = newDelay; + setTimeLeft(newDelay); + interval.current = setInterval(() => { + if (timeLeftRef.current > 0) { + timeLeftRef.current -= 1; + setTimeLeft(timeLeftRef.current); + } else { + if (typeof interval.current === 'number') { + clearInterval(interval.current); + } + } + }, 1000); + }, []); + + useEffect(() => { + return () => { + if (typeof interval.current === 'number') { + clearInterval(interval.current); + } + }; + }, [interval]); + + return { timeLeft, startTimer }; +} + +export default useTimeout; diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts new file mode 100644 index 00000000..444c35d5 --- /dev/null +++ b/packages/appkit/src/index.ts @@ -0,0 +1,22 @@ +export { + AccountButton as AccountButton, + type AccountButtonProps +} from './modal/w3m-account-button'; +export { AppKitButton, type AppKitButtonProps } from './modal/w3m-button'; +export { + ConnectButton as ConnectButton, + type ConnectButtonProps as ConnectButtonProps +} from './modal/w3m-connect-button'; +export { + NetworkButton as NetworkButton, + type NetworkButtonProps as NetworkButtonProps +} from './modal/w3m-network-button'; +export { AppKit } from './modal/w3m-modal'; +export { AppKitRouter } from './modal/w3m-router'; + +export { AppKitScaffold } from './client'; +export type { LibraryOptions, ScaffoldOptions } from './client'; + +export type * from '@reown/appkit-core-react-native'; +export { CoreHelperUtil } from '@reown/appkit-core-react-native'; + diff --git a/packages/appkit/src/modal/w3m-account-button/index.tsx b/packages/appkit/src/modal/w3m-account-button/index.tsx new file mode 100644 index 00000000..8bb37376 --- /dev/null +++ b/packages/appkit/src/modal/w3m-account-button/index.tsx @@ -0,0 +1,52 @@ +import { useSnapshot } from 'valtio'; +import { + AccountController, + CoreHelperUtil, + NetworkController, + ModalController, + AssetUtil, + ThemeController +} from '@reown/appkit-core-react-native'; + +import { AccountButton as AccountButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; +import { ApiController } from '@reown/appkit-core-react-native'; +import type { StyleProp, ViewStyle } from 'react-native'; + +export interface AccountButtonProps { + balance?: 'show' | 'hide'; + disabled?: boolean; + style?: StyleProp; + testID?: string; +} + +export function AccountButton({ balance, disabled, style, testID }: AccountButtonProps) { + const { + address, + balance: balanceVal, + balanceSymbol, + profileImage, + profileName + } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { themeMode, themeVariables } = useSnapshot(ThemeController.state); + + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const showBalance = balance === 'show'; + + return ( + + ModalController.open()} + address={address} + profileName={profileName} + networkSrc={networkImage} + imageHeaders={ApiController._getApiHeaders()} + avatarSrc={profileImage} + disabled={disabled} + style={style} + balance={showBalance ? CoreHelperUtil.formatBalance(balanceVal, balanceSymbol) : ''} + testID={testID} + /> + + ); +} diff --git a/packages/appkit/src/modal/w3m-button/index.tsx b/packages/appkit/src/modal/w3m-button/index.tsx new file mode 100644 index 00000000..e6bf0481 --- /dev/null +++ b/packages/appkit/src/modal/w3m-button/index.tsx @@ -0,0 +1,45 @@ +import { useSnapshot } from 'valtio'; +import { AccountButton, type AccountButtonProps } from '../w3m-account-button'; +import { ConnectButton, type ConnectButtonProps } from '../w3m-connect-button'; +import { AccountController, ModalController } from '@reown/appkit-core-react-native'; + +export interface AppKitButtonProps { + balance?: AccountButtonProps['balance']; + disabled?: AccountButtonProps['disabled']; + size?: ConnectButtonProps['size']; + label?: ConnectButtonProps['label']; + loadingLabel?: ConnectButtonProps['loadingLabel']; + accountStyle?: AccountButtonProps['style']; + connectStyle?: ConnectButtonProps['style']; +} + +export function AppKitButton({ + balance, + disabled, + size, + label = 'Connect', + loadingLabel = 'Connecting', + accountStyle, + connectStyle +}: AppKitButtonProps) { + const { isConnected } = useSnapshot(AccountController.state); + const { loading } = useSnapshot(ModalController.state); + + return !loading && isConnected ? ( + + ) : ( + + ); +} diff --git a/packages/appkit/src/modal/w3m-connect-button/index.tsx b/packages/appkit/src/modal/w3m-connect-button/index.tsx new file mode 100644 index 00000000..98f0c0e1 --- /dev/null +++ b/packages/appkit/src/modal/w3m-connect-button/index.tsx @@ -0,0 +1,43 @@ +import { useSnapshot } from 'valtio'; +import { ModalController, ThemeController } from '@reown/appkit-core-react-native'; +import { + ConnectButton as ConnectButtonUI, + ThemeProvider, + type ConnectButtonProps as ConnectButtonUIProps +} from '@reown/appkit-ui-react-native'; + +export interface ConnectButtonProps { + label: string; + loadingLabel: string; + size?: ConnectButtonUIProps['size']; + style?: ConnectButtonUIProps['style']; + disabled?: ConnectButtonUIProps['disabled']; + testID?: string; +} + +export function ConnectButton({ + label, + loadingLabel, + size = 'md', + style, + disabled, + testID +}: ConnectButtonProps) { + const { open, loading } = useSnapshot(ModalController.state); + const { themeMode, themeVariables } = useSnapshot(ThemeController.state); + + return ( + + ModalController.open()} + size={size} + loading={loading || open} + style={style} + testID={testID} + disabled={disabled} + > + {loading || open ? loadingLabel : label} + + + ); +} diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx new file mode 100644 index 00000000..1d8d29d7 --- /dev/null +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -0,0 +1,157 @@ +import { useSnapshot } from 'valtio'; +import { useCallback, useEffect } from 'react'; +import { useWindowDimensions, StatusBar } from 'react-native'; +import Modal from 'react-native-modal'; +import { Card, ThemeProvider } from '@reown/appkit-ui-react-native'; +import { + AccountController, + ApiController, + ConnectionController, + ConnectorController, + CoreHelperUtil, + EventsController, + ModalController, + OptionsController, + RouterController, + TransactionsController, + type CaipAddress, + type AppKitFrameProvider, + WebviewController, + ThemeController +} from '@reown/appkit-core-react-native'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; + +import { AppKitRouter } from '../w3m-router'; +import { Header } from '../../partials/w3m-header'; +import { Snackbar } from '../../partials/w3m-snackbar'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function AppKit() { + const { open, loading } = useSnapshot(ModalController.state); + const { connectors, connectedConnector } = useSnapshot(ConnectorController.state); + const { caipAddress, isConnected } = useSnapshot(AccountController.state); + const { frameViewVisible, webviewVisible } = useSnapshot(WebviewController.state); + const { themeMode, themeVariables } = useSnapshot(ThemeController.state); + const { height } = useWindowDimensions(); + const { isLandscape } = useCustomDimensions(); + const portraitHeight = height - 80; + const landScapeHeight = height * 0.95 - (StatusBar.currentHeight ?? 0); + const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; + const AuthView = authProvider?.AuthView; + const SocialView = authProvider?.Webview; + const showAuth = !connectedConnector || connectedConnector === 'AUTH'; + + const onBackButtonPress = () => { + if (RouterController.state.history.length > 1) { + return RouterController.goBack(); + } + + return handleClose(); + }; + + const prefetch = async () => { + await ApiController.prefetch(); + EventsController.sendEvent({ type: 'track', event: 'MODAL_LOADED' }); + }; + + const handleClose = async () => { + if (OptionsController.state.isSiweEnabled) { + if (SIWEController.state.status !== 'success' && AccountController.state.isConnected) { + await ConnectionController.disconnect(); + } + } + + if ( + RouterController.state.view === 'OnRampLoading' && + EventsController.state.data.event === 'BUY_SUBMITTED' + ) { + // Send event only if the onramp url was already created + EventsController.sendEvent({ type: 'track', event: 'BUY_CANCEL' }); + } + }; + + const onNewAddress = useCallback( + async (address?: CaipAddress) => { + if (!isConnected || loading) { + return; + } + + const newAddress = CoreHelperUtil.getPlainAddress(address); + TransactionsController.resetTransactions(); + + if (OptionsController.state.isSiweEnabled) { + const newNetworkId = CoreHelperUtil.getNetworkId(address); + + const { signOutOnAccountChange, signOutOnNetworkChange } = + SIWEController.state._client?.options ?? {}; + const session = await SIWEController.getSession(); + + if (session && newAddress && signOutOnAccountChange) { + // If the address has changed and signOnAccountChange is enabled, sign out + await SIWEController.signOut(); + onSiweNavigation(); + } else if ( + newNetworkId && + session?.chainId.toString() !== newNetworkId && + signOutOnNetworkChange + ) { + // If the network has changed and signOnNetworkChange is enabled, sign out + await SIWEController.signOut(); + onSiweNavigation(); + } else if (!session) { + // If it's connected but there's no session, show sign view + onSiweNavigation(); + } + } + }, + [isConnected, loading] + ); + + const onSiweNavigation = () => { + if (ModalController.state.open) { + RouterController.push('ConnectingSiwe'); + } else { + ModalController.open({ view: 'ConnectingSiwe' }); + } + }; + + useEffect(() => { + prefetch(); + }, []); + + useEffect(() => { + onNewAddress(caipAddress); + }, [caipAddress, onNewAddress]); + + return ( + <> + + + +
+ + + + + {!!showAuth && AuthView && } + {!!showAuth && SocialView && } + + + ); +} diff --git a/packages/appkit/src/modal/w3m-modal/styles.ts b/packages/appkit/src/modal/w3m-modal/styles.ts new file mode 100644 index 00000000..ac3a35a0 --- /dev/null +++ b/packages/appkit/src/modal/w3m-modal/styles.ts @@ -0,0 +1,13 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + modal: { + margin: 0, + justifyContent: 'flex-end' + }, + card: { + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + maxHeight: '80%' + } +}); diff --git a/packages/appkit/src/modal/w3m-network-button/index.tsx b/packages/appkit/src/modal/w3m-network-button/index.tsx new file mode 100644 index 00000000..353a1804 --- /dev/null +++ b/packages/appkit/src/modal/w3m-network-button/index.tsx @@ -0,0 +1,48 @@ +import { useSnapshot } from 'valtio'; +import type { StyleProp, ViewStyle } from 'react-native'; +import { + AccountController, + ApiController, + AssetUtil, + EventsController, + ModalController, + NetworkController, + ThemeController +} from '@reown/appkit-core-react-native'; +import { NetworkButton as NetworkButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; + +export interface NetworkButtonProps { + disabled?: boolean; + style?: StyleProp; +} + +export function NetworkButton({ disabled, style }: NetworkButtonProps) { + const { isConnected } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { loading } = useSnapshot(ModalController.state); + const { themeMode, themeVariables } = useSnapshot(ThemeController.state); + + const onNetworkPress = () => { + ModalController.open({ view: 'Networks' }); + EventsController.sendEvent({ + type: 'track', + event: 'CLICK_NETWORKS' + }); + }; + + return ( + + + {caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} + + + ); +} diff --git a/packages/appkit/src/modal/w3m-router/index.tsx b/packages/appkit/src/modal/w3m-router/index.tsx new file mode 100644 index 00000000..761770ef --- /dev/null +++ b/packages/appkit/src/modal/w3m-router/index.tsx @@ -0,0 +1,136 @@ +import { useLayoutEffect, useMemo } from 'react'; +import { useSnapshot } from 'valtio'; +import { RouterController } from '@reown/appkit-core-react-native'; + +import { AccountDefaultView } from '../../views/w3m-account-default-view'; +import { AccountView } from '../../views/w3m-account-view'; +import { AllWalletsView } from '../../views/w3m-all-wallets-view'; +import { ConnectView } from '../../views/w3m-connect-view'; +import { ConnectSocialsView } from '../../views/w3m-connect-socials-view'; +import { ConnectingView } from '../../views/w3m-connecting-view'; +import { ConnectingExternalView } from '../../views/w3m-connecting-external-view'; +import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; +import { ConnectingSocialView } from '../../views/w3m-connecting-social-view'; +import { CreateView } from '../../views/w3m-create-view'; +import { ConnectingSiweView } from '@reown/appkit-siwe-react-native'; +import { EmailVerifyOtpView } from '../../views/w3m-email-verify-otp-view'; +import { EmailVerifyDeviceView } from '../../views/w3m-email-verify-device-view'; +import { GetWalletView } from '../../views/w3m-get-wallet-view'; +import { NetworksView } from '../../views/w3m-networks-view'; +import { NetworkSwitchView } from '../../views/w3m-network-switch-view'; +import { OnRampLoadingView } from '../../views/w3m-onramp-loading-view'; +import { OnRampView } from '../../views/w3m-onramp-view'; +import { OnRampCheckoutView } from '../../views/w3m-onramp-checkout-view'; +import { OnRampSettingsView } from '../../views/w3m-onramp-settings-view'; +import { OnRampTransactionView } from '../../views/w3m-onramp-transaction-view'; +import { SwapView } from '../../views/w3m-swap-view'; +import { SwapPreviewView } from '../../views/w3m-swap-preview-view'; +import { SwapSelectTokenView } from '../../views/w3m-swap-select-token-view'; +import { TransactionsView } from '../../views/w3m-transactions-view'; +import { UnsupportedChainView } from '../../views/w3m-unsupported-chain-view'; +import { UpdateEmailWalletView } from '../../views/w3m-update-email-wallet-view'; +import { UpdateEmailPrimaryOtpView } from '../../views/w3m-update-email-primary-otp-view'; +import { UpdateEmailSecondaryOtpView } from '../../views/w3m-update-email-secondary-otp-view'; +import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-view'; +import { UpgradeToSmartAccountView } from '../../views/w3m-upgrade-to-smart-account-view'; +import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-networks-view'; +import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; +import { WalletSendView } from '../../views/w3m-wallet-send-view'; +import { WalletSendPreviewView } from '../../views/w3m-wallet-send-preview-view'; +import { WalletSendSelectTokenView } from '../../views/w3m-wallet-send-select-token-view'; +import { WhatIsANetworkView } from '../../views/w3m-what-is-a-network-view'; +import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; +import { UiUtil } from '../../utils/UiUtil'; + +export function AppKitRouter() { + const { view } = useSnapshot(RouterController.state); + + useLayoutEffect(() => { + UiUtil.createViewTransition(); + }, [view]); + + const ViewComponent = useMemo(() => { + switch (view) { + case 'Account': + return AccountView; + case 'AccountDefault': + return AccountDefaultView; + case 'AllWallets': + return AllWalletsView; + case 'Connect': + return ConnectView; + case 'ConnectSocials': + return ConnectSocialsView; + case 'ConnectingExternal': + return ConnectingExternalView; + case 'ConnectingSiwe': + return ConnectingSiweView; + case 'ConnectingSocial': + return ConnectingSocialView; + case 'ConnectingFarcaster': + return ConnectingFarcasterView; + case 'ConnectingWalletConnect': + return ConnectingView; + case 'Create': + return CreateView; + case 'EmailVerifyDevice': + return EmailVerifyDeviceView; + case 'EmailVerifyOtp': + return EmailVerifyOtpView; + case 'GetWallet': + return GetWalletView; + case 'Networks': + return NetworksView; + case 'OnRamp': + return OnRampView; + case 'OnRampCheckout': + return OnRampCheckoutView; + case 'OnRampSettings': + return OnRampSettingsView; + case 'OnRampLoading': + return OnRampLoadingView; + case 'SwitchNetwork': + return NetworkSwitchView; + case 'OnRampTransaction': + return OnRampTransactionView; + case 'Swap': + return SwapView; + case 'SwapPreview': + return SwapPreviewView; + case 'SwapSelectToken': + return SwapSelectTokenView; + case 'Transactions': + return TransactionsView; + case 'UnsupportedChain': + return UnsupportedChainView; + case 'UpdateEmailPrimaryOtp': + return UpdateEmailPrimaryOtpView; + case 'UpdateEmailSecondaryOtp': + return UpdateEmailSecondaryOtpView; + case 'UpdateEmailWallet': + return UpdateEmailWalletView; + case 'UpgradeEmailWallet': + return UpgradeEmailWalletView; + case 'UpgradeToSmartAccount': + return UpgradeToSmartAccountView; + case 'WalletCompatibleNetworks': + return WalletCompatibleNetworks; + case 'WalletReceive': + return WalletReceiveView; + case 'WalletSend': + return WalletSendView; + case 'WalletSendPreview': + return WalletSendPreviewView; + case 'WalletSendSelectToken': + return WalletSendSelectTokenView; + case 'WhatIsANetwork': + return WhatIsANetworkView; + case 'WhatIsAWallet': + return WhatIsAWalletView; + default: + return ConnectView; + } + }, [view]); + + return ; +} diff --git a/packages/appkit/src/partials/w3m-account-activity/index.tsx b/packages/appkit/src/partials/w3m-account-activity/index.tsx new file mode 100644 index 00000000..3ec7ee05 --- /dev/null +++ b/packages/appkit/src/partials/w3m-account-activity/index.tsx @@ -0,0 +1,172 @@ +import { useSnapshot } from 'valtio'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { ScrollView, View, type StyleProp, type ViewStyle, RefreshControl } from 'react-native'; +import { + FlexView, + Link, + ListTransaction, + LoadingSpinner, + Text, + TransactionUtil, + useTheme +} from '@reown/appkit-ui-react-native'; +import { type Transaction, type TransactionImage } from '@reown/appkit-common-react-native'; +import { + AccountController, + AssetUtil, + EventsController, + NetworkController, + OptionsController, + TransactionsController +} from '@reown/appkit-core-react-native'; +import { Placeholder } from '../w3m-placeholder'; +import { getTransactionListItemProps } from './utils'; +import styles from './styles'; + +interface Props { + style?: StyleProp; +} + +export function AccountActivity({ style }: Props) { + const Theme = useTheme(); + const [refreshing, setRefreshing] = useState(false); + const [initialLoad, setInitialLoad] = useState(true); + const { loading, transactions, next } = useSnapshot(TransactionsController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const handleLoadMore = () => { + TransactionsController.fetchTransactions(AccountController.state.address); + EventsController.sendEvent({ + type: 'track', + event: 'LOAD_MORE_TRANSACTIONS', + properties: { + address: AccountController.state.address, + projectId: OptionsController.state.projectId, + cursor: TransactionsController.state.next, + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + }; + + const onRefresh = useCallback(async () => { + setRefreshing(true); + await TransactionsController.fetchTransactions(AccountController.state.address, true); + setRefreshing(false); + }, []); + + const transactionsByYear = useMemo(() => { + return TransactionsController.getTransactionsByYearAndMonth(transactions as Transaction[]); + }, [transactions]); + + useEffect(() => { + if (!TransactionsController.state.transactions.length) { + TransactionsController.fetchTransactions(AccountController.state.address, true); + } + // Set initial load to false after first fetch + const timer = setTimeout(() => setInitialLoad(false), 100); + + return () => clearTimeout(timer); + }, []); + + // Show loading spinner during initial load or when loading with no transactions + if ((initialLoad || loading) && !transactions.length) { + return ( + + + + ); + } + + // Only show placeholder when we're not in initial load or loading state + if (!Object.keys(transactionsByYear).length && !loading && !initialLoad) { + return ( + + ); + } + + return ( + + } + > + {Object.keys(transactionsByYear) + .reverse() + .map(year => ( + + {Object.keys(transactionsByYear[year] || {}) + .reverse() + .map(month => ( + + + {TransactionUtil.getTransactionGroupTitle(year, month)} + + {transactionsByYear[year]?.[month]?.map((transaction: Transaction) => { + const { date, type, descriptions, status, images, isAllNFT, transfers } = + getTransactionListItemProps(transaction); + const hasMultipleTransfers = transfers?.length > 2; + + // Show only the first transfer + if (hasMultipleTransfers) { + const description = TransactionUtil.getTransferDescription(transfers[0]); + + return ( + + ); + } + + return ( + + ); + })} + + ))} + + ))} + {(next || loading) && !refreshing && ( + + {next && !loading && ( + + Load more + + )} + {loading && } + + )} + + ); +} diff --git a/packages/appkit/src/partials/w3m-account-activity/styles.ts b/packages/appkit/src/partials/w3m-account-activity/styles.ts new file mode 100644 index 00000000..64c13aab --- /dev/null +++ b/packages/appkit/src/partials/w3m-account-activity/styles.ts @@ -0,0 +1,29 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + paddingHorizontal: Spacing.xs + }, + contentContainer: { + paddingBottom: Spacing.m + }, + separatorText: { + marginVertical: Spacing.xs + }, + transactionItem: { + marginVertical: Spacing.xs + }, + footer: { + height: 40 + }, + placeholder: { + minHeight: 200, + flex: 0 + }, + loadMoreButton: { + alignSelf: 'center', + width: 100, + marginVertical: Spacing.xs + } +}); diff --git a/packages/appkit/src/partials/w3m-account-activity/utils.ts b/packages/appkit/src/partials/w3m-account-activity/utils.ts new file mode 100644 index 00000000..be865523 --- /dev/null +++ b/packages/appkit/src/partials/w3m-account-activity/utils.ts @@ -0,0 +1,25 @@ +import { DateUtil, type Transaction } from '@reown/appkit-common-react-native'; +import { TransactionUtil } from '@reown/appkit-ui-react-native'; +import type { TransactionType } from '@reown/appkit-ui-react-native/lib/typescript/utils/TypesUtil'; + +export function getTransactionListItemProps(transaction: Transaction) { + const date = DateUtil.formatDate(transaction?.metadata?.minedAt); + const descriptions = TransactionUtil.getTransactionDescriptions(transaction); + + const transfers = transaction?.transfers; + const transfer = transaction?.transfers?.[0]; + const isAllNFT = + Boolean(transfer) && transaction?.transfers?.every(item => Boolean(item.nft_info)); + const images = TransactionUtil.getTransactionImages(transfers); + + return { + date, + direction: transfer?.direction, + descriptions, + isAllNFT, + images, + status: transaction.metadata?.status, + transfers, + type: transaction.metadata?.operationType as TransactionType + }; +} diff --git a/packages/appkit/src/partials/w3m-account-tokens/index.tsx b/packages/appkit/src/partials/w3m-account-tokens/index.tsx new file mode 100644 index 00000000..26db07f9 --- /dev/null +++ b/packages/appkit/src/partials/w3m-account-tokens/index.tsx @@ -0,0 +1,100 @@ +import { useCallback, useState } from 'react'; +import { + RefreshControl, + ScrollView, + StyleSheet, + type StyleProp, + type ViewStyle +} from 'react-native'; +import { useSnapshot } from 'valtio'; +import { + AccountController, + AssetUtil, + NetworkController, + RouterController +} from '@reown/appkit-core-react-native'; +import { + FlexView, + ListItem, + Text, + ListToken, + useTheme, + Spacing +} from '@reown/appkit-ui-react-native'; + +interface Props { + style?: StyleProp; +} + +export function AccountTokens({ style }: Props) { + const Theme = useTheme(); + const [refreshing, setRefreshing] = useState(false); + const { tokenBalance } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const onRefresh = useCallback(async () => { + setRefreshing(true); + AccountController.fetchTokenBalance(); + setRefreshing(false); + }, []); + + const onReceivePress = () => { + RouterController.push('WalletReceive'); + }; + + if (!tokenBalance?.length) { + return ( + + + + Receive funds + + + Transfer tokens on your wallet + + + + ); + } + + return ( + + } + > + {tokenBalance.map(token => ( + + ))} + + ); +} + +const styles = StyleSheet.create({ + receiveButton: { + width: 'auto', + marginHorizontal: Spacing.s + } +}); diff --git a/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx b/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx new file mode 100644 index 00000000..66de6277 --- /dev/null +++ b/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx @@ -0,0 +1,156 @@ +import { useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { Balance, FlexView, IconLink, Tabs } from '@reown/appkit-ui-react-native'; +import { + AccountController, + ConstantsUtil, + CoreHelperUtil, + EventsController, + NetworkController, + OnRampController, + OptionsController, + RouterController, + SwapController +} from '@reown/appkit-core-react-native'; +import type { Balance as BalanceType } from '@reown/appkit-common-react-native'; +import { AccountActivity } from '../w3m-account-activity'; +import { AccountTokens } from '../w3m-account-tokens'; +import styles from './styles'; + +export interface AccountWalletFeaturesProps { + value: string; +} + +export function AccountWalletFeatures() { + const [activeTab, setActiveTab] = useState(0); + const { tokenBalance } = useSnapshot(AccountController.state); + const { features, isOnRampEnabled } = useSnapshot(OptionsController.state); + const balance = CoreHelperUtil.calculateAndFormatBalance(tokenBalance as BalanceType[]); + const isSwapsEnabled = features?.swaps; + + const onTabChange = (index: number) => { + setActiveTab(index); + if (index === 2) { + onTransactionsPress(); + } + }; + + const onTransactionsPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'CLICK_TRANSACTIONS', + properties: { + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + }; + + const onSwapPress = () => { + if ( + NetworkController.state.caipNetwork?.id && + !ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(`${NetworkController.state.caipNetwork.id}`) + ) { + RouterController.push('UnsupportedChain'); + } else { + SwapController.resetState(); + EventsController.sendEvent({ + type: 'track', + event: 'OPEN_SWAP', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + RouterController.push('Swap'); + } + }; + + const onSendPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'OPEN_SEND', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + RouterController.push('WalletSend'); + }; + + const onReceivePress = () => { + RouterController.push('WalletReceive'); + }; + + const onBuyPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'SELECT_BUY_CRYPTO' + }); + OnRampController.resetState(); + RouterController.push('OnRamp'); + }; + + return ( + + + + {isOnRampEnabled && ( + + )} + {isSwapsEnabled && ( + + )} + + + + + + + + {activeTab === 0 && } + {activeTab === 1 && } + + + ); +} diff --git a/packages/appkit/src/partials/w3m-account-wallet-features/styles.ts b/packages/appkit/src/partials/w3m-account-wallet-features/styles.ts new file mode 100644 index 00000000..6722e5bf --- /dev/null +++ b/packages/appkit/src/partials/w3m-account-wallet-features/styles.ts @@ -0,0 +1,41 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: 400 + }, + balanceText: { + fontSize: 40, + fontWeight: '500' + }, + actionsContainer: { + width: '100%', + marginTop: Spacing.s, + marginBottom: Spacing.l + }, + action: { + flex: 1, + height: 52 + }, + actionLeft: { + marginRight: 8 + }, + actionRight: { + marginLeft: 8 + }, + actionCenter: { + marginHorizontal: 8 + }, + tab: { + width: '100%', + paddingHorizontal: Spacing.s + }, + tabContainer: { + flex: 1, + width: '100%' + }, + tabContent: { + paddingHorizontal: Spacing.m + } +}); diff --git a/packages/appkit/src/partials/w3m-all-wallets-list/index.tsx b/packages/appkit/src/partials/w3m-all-wallets-list/index.tsx new file mode 100644 index 00000000..d2d6040a --- /dev/null +++ b/packages/appkit/src/partials/w3m-all-wallets-list/index.tsx @@ -0,0 +1,179 @@ +import { useEffect, useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { FlatList, View } from 'react-native'; +import { + ApiController, + AssetUtil, + OptionsController, + SnackController, + type OptionsControllerState, + type WcWallet +} from '@reown/appkit-core-react-native'; +import { + CardSelect, + CardSelectLoader, + CardSelectHeight, + FlexView, + Spacing +} from '@reown/appkit-ui-react-native'; +import styles from './styles'; +import { UiUtil } from '../../utils/UiUtil'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { Placeholder } from '../w3m-placeholder'; + +interface AllWalletsListProps { + columns: number; + onItemPress: (wallet: WcWallet) => void; + itemWidth?: number; +} + +export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsListProps) { + const [loading, setLoading] = useState(ApiController.state.wallets.length === 0); + const [loadingError, setLoadingError] = useState(false); + const [pageLoading, setPageLoading] = useState(false); + const { maxWidth, padding } = useCustomDimensions(); + const { installed, featured, recommended, wallets } = useSnapshot(ApiController.state); + const { customWallets } = useSnapshot(OptionsController.state) as OptionsControllerState; + const imageHeaders = ApiController._getApiHeaders(); + const preloadedWallets = installed.length + featured.length + recommended.length; + const loadingItems = columns - ((100 + preloadedWallets) % columns); + + const combinedWallets = [ + ...(customWallets ?? []), + ...installed, + ...featured, + ...recommended, + ...wallets + ]; + + // Deduplicate by wallet ID + const uniqueWallets = Array.from( + new Map(combinedWallets.map(wallet => [wallet?.id, wallet])).values() + ).filter(wallet => wallet?.id); // Filter out any undefined wallets + + const walletList = [ + ...uniqueWallets, + ...(pageLoading ? (Array.from({ length: loadingItems }) as WcWallet[]) : []) + ]; + + const ITEM_HEIGHT = CardSelectHeight + Spacing.xs * 2; + + const loadingTemplate = (items: number) => { + return ( + + {Array.from({ length: items }).map((_, index) => ( + + + + ))} + + ); + }; + + const walletTemplate = ({ item }: { item: WcWallet; index: number }) => { + const isInstalled = ApiController.state.installed.find(wallet => wallet?.id === item?.id); + if (!item?.id) { + return ( + + + + ); + } + + return ( + + onItemPress(item)} + installed={!!isInstalled} + /> + + ); + }; + + const initialFetch = async () => { + try { + setLoading(true); + setLoadingError(false); + await ApiController.fetchWallets({ page: 1 }); + UiUtil.createViewTransition(); + setLoading(false); + } catch (error) { + SnackController.showError('Failed to load wallets'); + setLoading(false); + setLoadingError(true); + } + }; + + const fetchNextPage = async () => { + try { + if ( + walletList.length < ApiController.state.count && + !pageLoading && + !loading && + ApiController.state.page > 0 + ) { + setPageLoading(true); + await ApiController.fetchWallets({ page: ApiController.state.page + 1 }); + setPageLoading(false); + } + } catch (error) { + SnackController.showError('Failed to load more wallets'); + setPageLoading(false); + } + }; + + useEffect(() => { + if (!ApiController.state.wallets.length) { + initialFetch(); + } + }, []); + + if (loading) { + return loadingTemplate(20); + } + + if (loadingError) { + return ( + + ); + } + + return ( + item?.id ?? index} + getItemLayout={(_, index) => ({ + length: ITEM_HEIGHT, + offset: ITEM_HEIGHT * index, + index + })} + /> + ); +} diff --git a/packages/appkit/src/partials/w3m-all-wallets-list/styles.ts b/packages/appkit/src/partials/w3m-all-wallets-list/styles.ts new file mode 100644 index 00000000..4e6c30f0 --- /dev/null +++ b/packages/appkit/src/partials/w3m-all-wallets-list/styles.ts @@ -0,0 +1,26 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: '100%' + }, + contentContainer: { + paddingBottom: Spacing['2xl'] + }, + itemContainer: { + alignItems: 'center', + justifyContent: 'center', + marginVertical: Spacing.xs + }, + pageLoader: { + marginTop: Spacing.xl + }, + errorContainer: { + height: '90%' + }, + placeholderContainer: { + flex: 0, + height: '90%' + } +}); diff --git a/packages/appkit/src/partials/w3m-all-wallets-search/index.tsx b/packages/appkit/src/partials/w3m-all-wallets-search/index.tsx new file mode 100644 index 00000000..172f3b09 --- /dev/null +++ b/packages/appkit/src/partials/w3m-all-wallets-search/index.tsx @@ -0,0 +1,147 @@ +import { useCallback, useEffect, useState } from 'react'; +import { FlatList, View } from 'react-native'; +import { + ApiController, + AssetUtil, + SnackController, + type WcWallet +} from '@reown/appkit-core-react-native'; +import { + CardSelect, + CardSelectHeight, + CardSelectLoader, + FlexView, + Spacing +} from '@reown/appkit-ui-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { Placeholder } from '../w3m-placeholder'; +import styles from './styles'; + +export interface AllWalletsSearchProps { + columns: number; + onItemPress: (wallet: WcWallet) => void; + itemWidth?: number; + searchQuery?: string; +} + +export function AllWalletsSearch({ + searchQuery, + columns, + itemWidth, + onItemPress +}: AllWalletsSearchProps) { + const [loading, setLoading] = useState(false); + const [loadingError, setLoadingError] = useState(false); + const [prevSearchQuery, setPrevSearchQuery] = useState(''); + const imageHeaders = ApiController._getApiHeaders(); + const { maxWidth, padding, isLandscape } = useCustomDimensions(); + + const ITEM_HEIGHT = CardSelectHeight + Spacing.xs * 2; + + const walletTemplate = ({ item }: { item: WcWallet }) => { + const isInstalled = ApiController.state.installed.find(wallet => wallet?.id === item?.id); + + return ( + + onItemPress(item)} + installed={!!isInstalled} + testID={`wallet-search-item-${item?.id}`} + /> + + ); + }; + + const loadingTemplate = (items: number) => { + return ( + + {Array.from({ length: items }).map((_, index) => ( + + + + ))} + + ); + }; + + const emptyTemplate = () => { + return ( + + ); + }; + + const searchFetch = useCallback(async () => { + try { + setLoading(true); + setLoadingError(false); + await ApiController.searchWallet({ search: searchQuery }); + setLoading(false); + } catch (error) { + SnackController.showError('Failed to load wallets'); + setLoading(false); + setLoadingError(true); + } + }, [searchQuery]); + + useEffect(() => { + if (prevSearchQuery !== searchQuery) { + setPrevSearchQuery(searchQuery || ''); + searchFetch(); + } + }, [searchQuery, prevSearchQuery, searchFetch]); + + if (loading) { + return loadingTemplate(20); + } + + if (loadingError) { + return ( + + ); + } + + if (ApiController.state.search.length === 0) { + return emptyTemplate(); + } + + return ( + item.id} + getItemLayout={(_, index) => ({ + length: ITEM_HEIGHT, + offset: ITEM_HEIGHT * index, + index + })} + /> + ); +} diff --git a/packages/appkit/src/partials/w3m-all-wallets-search/styles.ts b/packages/appkit/src/partials/w3m-all-wallets-search/styles.ts new file mode 100644 index 00000000..d425dea3 --- /dev/null +++ b/packages/appkit/src/partials/w3m-all-wallets-search/styles.ts @@ -0,0 +1,30 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: '100%' + }, + contentContainer: { + paddingBottom: Spacing['2xl'] + }, + placeholderContainer: { + flex: 0, + height: '90%' + }, + emptyContainer: { + flex: 0, + height: '90%' + }, + emptyLandscape: { + paddingTop: '10%' + }, + itemContainer: { + alignItems: 'center', + justifyContent: 'center', + marginVertical: Spacing.xs + }, + text: { + marginTop: Spacing.xs + } +}); diff --git a/packages/appkit/src/partials/w3m-connecting-body/index.tsx b/packages/appkit/src/partials/w3m-connecting-body/index.tsx new file mode 100644 index 00000000..0d0e8c2e --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-body/index.tsx @@ -0,0 +1,32 @@ +import { StyleSheet } from 'react-native'; +import { FlexView, Spacing, Text } from '@reown/appkit-ui-react-native'; + +export * from './utils'; + +export interface ConnectingBodyProps { + title: string; + description?: string; +} + +export function ConnectingBody({ title, description }: ConnectingBodyProps) { + return ( + + {title} + {description && ( + + {description} + + )} + + ); +} + +const styles = StyleSheet.create({ + textContainer: { + marginVertical: Spacing.xs + }, + descriptionText: { + marginTop: Spacing.xs, + marginHorizontal: Spacing['3xl'] + } +}); diff --git a/packages/appkit/src/partials/w3m-connecting-body/utils.ts b/packages/appkit/src/partials/w3m-connecting-body/utils.ts new file mode 100644 index 00000000..49f60b72 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-body/utils.ts @@ -0,0 +1,34 @@ +export type BodyErrorType = 'not_installed' | 'default' | 'declined' | undefined; + +interface Props { + walletName?: string; + declined?: boolean; + errorType?: BodyErrorType; + isWeb?: boolean; +} + +export const getMessage = ({ walletName, declined, errorType, isWeb }: Props) => { + if (declined || errorType === 'declined') { + return { + title: 'Connection declined', + description: 'Connection can be declined if a previous request is still active' + }; + } + + switch (errorType) { + case 'not_installed': + return { title: 'App not installed' }; + case 'default': + return { + title: 'Connection error', + description: 'There was an unexpected connection error.' + }; + default: + return { + title: `Continue in ${walletName ?? 'Wallet'}`, + description: isWeb + ? 'Open and continue in a browser tab' + : 'Accept connection request in the wallet' + }; + } +}; diff --git a/packages/appkit/src/partials/w3m-connecting-header/index.tsx b/packages/appkit/src/partials/w3m-connecting-header/index.tsx new file mode 100644 index 00000000..45a11931 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-header/index.tsx @@ -0,0 +1,53 @@ +import type { Platform } from '@reown/appkit-core-react-native'; +import { FlexView, Tabs, type IconType } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export interface ConnectingHeaderProps { + platforms: Platform[]; + onSelectPlatform: (platform: Platform) => void; +} + +interface Tab { + label: string; + icon: IconType; + platform: Platform; +} + +export function ConnectingHeader({ platforms, onSelectPlatform }: ConnectingHeaderProps) { + const generateTabs = () => { + const tabs = platforms + .map(platform => { + if (platform === 'mobile') { + return { label: 'Mobile', icon: 'mobile', platform: 'mobile' } as const; + } else if (platform === 'web') { + return { label: 'Web', icon: 'browser', platform: 'web' } as const; + } else { + return undefined; + } + }) + .filter(Boolean) as Tab[]; + + return tabs; + }; + + const onTabChange = (index: number) => { + const platform = platforms[index]; + if (platform) { + onSelectPlatform(platform); + } + }; + + const tabs = generateTabs(); + + return ( + + + + ); +} + +const styles = StyleSheet.create({ + tab: { + maxWidth: '50%' + } +}); diff --git a/packages/appkit/src/partials/w3m-connecting-mobile/components/StoreLink.tsx b/packages/appkit/src/partials/w3m-connecting-mobile/components/StoreLink.tsx new file mode 100644 index 00000000..c1f72476 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-mobile/components/StoreLink.tsx @@ -0,0 +1,38 @@ +import { ActionEntry, Button, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export interface StoreLinkProps { + visible: boolean; + walletName?: string; + onPress: () => void; +} + +export function StoreLink({ visible, walletName = 'Wallet', onPress }: StoreLinkProps) { + if (!visible) return null; + + return ( + + + {`Don't have ${walletName}?`} + + + + ); +} + +const styles = StyleSheet.create({ + storeButton: { + justifyContent: 'space-between', + paddingHorizontal: Spacing.l, + marginHorizontal: Spacing.xl, + marginTop: Spacing.l + } +}); diff --git a/packages/appkit/src/partials/w3m-connecting-mobile/index.tsx b/packages/appkit/src/partials/w3m-connecting-mobile/index.tsx new file mode 100644 index 00000000..fe52031d --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-mobile/index.tsx @@ -0,0 +1,158 @@ +import { useSnapshot } from 'valtio'; +import { useCallback, useEffect, useState } from 'react'; +import { Platform, ScrollView } from 'react-native'; +import { + RouterController, + ApiController, + AssetUtil, + ConnectionController, + CoreHelperUtil, + OptionsController, + EventsController, + ConstantsUtil +} from '@reown/appkit-core-react-native'; +import { + Button, + FlexView, + LoadingThumbnail, + WalletImage, + Link, + IconBox +} from '@reown/appkit-ui-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { UiUtil } from '../../utils/UiUtil'; +import { StoreLink } from './components/StoreLink'; +import { ConnectingBody, getMessage, type BodyErrorType } from '../w3m-connecting-body'; +import styles from './styles'; + +interface Props { + onRetry: () => void; + onCopyUri: (uri?: string) => void; + isInstalled?: boolean; +} + +export function ConnectingMobile({ onRetry, onCopyUri, isInstalled }: Props) { + const { data } = RouterController.state; + const { maxWidth: width } = useCustomDimensions(); + const { wcUri, wcError } = useSnapshot(ConnectionController.state); + const [errorType, setErrorType] = useState(); + const showCopy = + OptionsController.isClipboardAvailable() && + errorType !== 'not_installed' && + !CoreHelperUtil.isLinkModeURL(wcUri); + + const showRetry = errorType !== 'not_installed'; + const bodyMessage = getMessage({ walletName: data?.wallet?.name, errorType, declined: wcError }); + + const storeUrl = Platform.select({ + ios: data?.wallet?.app_store, + android: data?.wallet?.play_store + }); + + const onRetryPress = () => { + setErrorType(undefined); + ConnectionController.setWcError(false); + onRetry?.(); + }; + + const onStorePress = () => { + if (storeUrl) { + CoreHelperUtil.openLink(storeUrl); + } + }; + + const onConnect = useCallback(async () => { + try { + const { name, mobile_link } = data?.wallet ?? {}; + if (name && mobile_link && wcUri) { + const { redirect, href } = CoreHelperUtil.formatNativeUrl(mobile_link, wcUri); + const wcLinking = { name, href }; + ConnectionController.setWcLinking(wcLinking); + ConnectionController.setPressedWallet(data?.wallet); + await CoreHelperUtil.openLink(redirect); + await ConnectionController.state.wcPromise; + UiUtil.storeConnectedWallet(wcLinking, data?.wallet); + EventsController.sendEvent({ + type: 'track', + event: 'CONNECT_SUCCESS', + properties: { + method: 'mobile', + name: data?.wallet?.name ?? 'Unknown', + explorer_id: data?.wallet?.id + } + }); + } + } catch (error: any) { + if (error.message.includes(ConstantsUtil.LINKING_ERROR)) { + setErrorType('not_installed'); + } else { + setErrorType('default'); + } + } + }, [wcUri, data]); + + useEffect(() => { + if (wcUri) { + onConnect(); + } + }, [wcUri, onConnect]); + + return ( + + + + + {wcError && ( + + )} + + + {showRetry && ( + + )} + + {showCopy && ( + onCopyUri(wcUri)} + > + Copy link + + )} + + + ); +} diff --git a/packages/appkit/src/partials/w3m-connecting-mobile/styles.ts b/packages/appkit/src/partials/w3m-connecting-mobile/styles.ts new file mode 100644 index 00000000..d84e2bc6 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-mobile/styles.ts @@ -0,0 +1,24 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + paddingBottom: Spacing['3xl'] + }, + retryButton: { + marginTop: Spacing.m + }, + retryIcon: { + transform: [{ rotateY: '180deg' }] + }, + copyButton: { + alignSelf: 'center', + marginTop: Spacing.m + }, + errorIcon: { + position: 'absolute', + bottom: 5, + right: 5, + zIndex: 2 + } +}); diff --git a/packages/appkit/src/partials/w3m-connecting-qrcode/index.tsx b/packages/appkit/src/partials/w3m-connecting-qrcode/index.tsx new file mode 100644 index 00000000..3a035706 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-qrcode/index.tsx @@ -0,0 +1,76 @@ +import { useEffect } from 'react'; +import { useSnapshot } from 'valtio'; +import { + AssetUtil, + ConnectionController, + ConnectorController, + EventsController, + OptionsController, + SnackController +} from '@reown/appkit-core-react-native'; +import { FlexView, Link, QrCode, Text, Spacing } from '@reown/appkit-ui-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectingQrCode() { + const { wcUri } = useSnapshot(ConnectionController.state); + const showCopy = OptionsController.isClipboardAvailable(); + const { maxWidth: windowSize, isPortrait } = useCustomDimensions(); + const qrSize = (windowSize - Spacing.xl * 2) / (isPortrait ? 1 : 1.5); + + const onCopyAddress = () => { + if (ConnectionController.state.wcUri) { + OptionsController.copyToClipboard(ConnectionController.state.wcUri); + SnackController.showSuccess('Link copied'); + } + }; + + const onConnect = async () => { + await ConnectionController.state.wcPromise; + + EventsController.sendEvent({ + type: 'track', + event: 'CONNECT_SUCCESS', + properties: { + method: 'qrcode', + name: 'WalletConnect' + } + }); + + const connectors = ConnectorController.state.connectors; + const connector = connectors.find(c => c.type === 'WALLET_CONNECT'); + const url = AssetUtil.getConnectorImage(connector); + ConnectionController.setConnectedWalletImageUrl(url); + }; + + useEffect(() => { + if (wcUri) { + onConnect(); + } + }, [wcUri]); + + return ( + + + + Scan this QR code with your phone + {showCopy && ( + + Copy link + + )} + + + ); +} diff --git a/packages/appkit/src/partials/w3m-connecting-qrcode/styles.ts b/packages/appkit/src/partials/w3m-connecting-qrcode/styles.ts new file mode 100644 index 00000000..c6a0df01 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-qrcode/styles.ts @@ -0,0 +1,8 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + copyButton: { + marginTop: Spacing.m + } +}); diff --git a/packages/appkit/src/partials/w3m-connecting-web/index.tsx b/packages/appkit/src/partials/w3m-connecting-web/index.tsx new file mode 100644 index 00000000..6d7a4825 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-web/index.tsx @@ -0,0 +1,111 @@ +import { useSnapshot } from 'valtio'; +import { useCallback } from 'react'; +import { Linking, ScrollView } from 'react-native'; +import { + RouterController, + ApiController, + AssetUtil, + ConnectionController, + CoreHelperUtil, + OptionsController, + EventsController +} from '@reown/appkit-core-react-native'; +import { + Button, + FlexView, + LoadingThumbnail, + WalletImage, + Link, + IconBox +} from '@reown/appkit-ui-react-native'; + +import { UiUtil } from '../../utils/UiUtil'; +import { ConnectingBody, getMessage } from '../w3m-connecting-body'; +import styles from './styles'; + +interface ConnectingWebProps { + onCopyUri: (uri?: string) => void; +} + +export function ConnectingWeb({ onCopyUri }: ConnectingWebProps) { + const { data } = RouterController.state; + const { wcUri, wcError } = useSnapshot(ConnectionController.state); + const showCopy = OptionsController.isClipboardAvailable(); + const bodyMessage = getMessage({ + walletName: data?.wallet?.name, + declined: wcError, + isWeb: true + }); + + const onConnect = useCallback(async () => { + try { + const { name, webapp_link } = data?.wallet ?? {}; + if (name && webapp_link && wcUri) { + ConnectionController.setWcError(false); + const { redirect, href } = CoreHelperUtil.formatUniversalUrl(webapp_link, wcUri); + const wcLinking = { name, href }; + ConnectionController.setWcLinking(wcLinking); + ConnectionController.setPressedWallet(data?.wallet); + await Linking.openURL(redirect); + await ConnectionController.state.wcPromise; + + UiUtil.storeConnectedWallet(wcLinking, data?.wallet); + + EventsController.sendEvent({ + type: 'track', + event: 'CONNECT_SUCCESS', + properties: { + method: 'web', + name: data?.wallet?.name ?? 'Unknown', + explorer_id: data?.wallet?.id + } + }); + } + } catch {} + }, [data?.wallet, wcUri]); + + return ( + + + + + {wcError && ( + + )} + + + + {showCopy && ( + onCopyUri(wcUri)} + > + Copy link + + )} + + + ); +} diff --git a/packages/appkit/src/partials/w3m-connecting-web/styles.ts b/packages/appkit/src/partials/w3m-connecting-web/styles.ts new file mode 100644 index 00000000..5247da44 --- /dev/null +++ b/packages/appkit/src/partials/w3m-connecting-web/styles.ts @@ -0,0 +1,20 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + openButton: { + marginTop: Spacing.m + }, + copyButton: { + marginTop: Spacing.m + }, + errorIcon: { + position: 'absolute', + bottom: 5, + right: 5, + zIndex: 2 + }, + marginBottom: { + marginBottom: Spacing.xs + } +}); diff --git a/packages/appkit/src/partials/w3m-header/index.tsx b/packages/appkit/src/partials/w3m-header/index.tsx new file mode 100644 index 00000000..7ce32ce6 --- /dev/null +++ b/packages/appkit/src/partials/w3m-header/index.tsx @@ -0,0 +1,146 @@ +import { useSnapshot } from 'valtio'; +import { + RouterController, + ModalController, + EventsController, + type RouterControllerState, + ConnectionController, + ConnectorController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { IconLink, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { StringUtil } from '@reown/appkit-common-react-native'; + +import styles from './styles'; + +export function Header() { + const { data, view } = useSnapshot(RouterController.state); + const onHelpPress = () => { + RouterController.push('WhatIsAWallet'); + EventsController.sendEvent({ type: 'track', event: 'CLICK_WALLET_HELP' }); + }; + + const headings = (_data: RouterControllerState['data'], _view: RouterControllerState['view']) => { + const connectorName = _data?.connector?.name; + const walletName = _data?.wallet?.name; + const networkName = _data?.network?.name; + const socialName = ConnectionController.state.selectedSocialProvider + ? StringUtil.capitalize(ConnectionController.state.selectedSocialProvider) + : undefined; + + return { + Account: undefined, + AccountDefault: undefined, + AllWallets: 'All wallets', + Connect: 'Connect wallet', + ConnectSocials: 'All socials', + ConnectingExternal: connectorName ?? 'Connect wallet', + ConnectingSiwe: undefined, + ConnectingFarcaster: socialName ?? 'Connecting Social', + ConnectingSocial: socialName ?? 'Connecting Social', + ConnectingWalletConnect: walletName ?? 'WalletConnect', + Create: 'Create wallet', + EmailVerifyDevice: ' ', + EmailVerifyOtp: 'Confirm email', + GetWallet: 'Get a wallet', + Networks: 'Select network', + OnRamp: undefined, + OnRampCheckout: 'Checkout', + OnRampSettings: 'Preferences', + OnRampLoading: undefined, + OnRampTransaction: ' ', + SwitchNetwork: networkName ?? 'Switch network', + Swap: 'Swap', + SwapSelectToken: 'Select token', + SwapPreview: 'Review swap', + Transactions: 'Activity', + UnsupportedChain: 'Switch network', + UpdateEmailPrimaryOtp: 'Confirm current email', + UpdateEmailSecondaryOtp: 'Confirm new email', + UpdateEmailWallet: 'Edit email', + UpgradeEmailWallet: 'Upgrade wallet', + UpgradeToSmartAccount: undefined, + WalletCompatibleNetworks: 'Compatible networks', + WalletReceive: 'Receive', + WalletSend: 'Send', + WalletSendPreview: 'Review send', + WalletSendSelectToken: 'Select token', + WhatIsANetwork: 'What is a network?', + WhatIsAWallet: 'What is a wallet?' + }[_view]; + }; + + const noCloseViews = ['OnRampSettings']; + const showClose = !noCloseViews.includes(view); + const header = headings(data, view); + + const checkSocial = () => { + if ( + RouterController.state.view === 'ConnectingFarcaster' || + RouterController.state.view === 'ConnectingSocial' + ) { + const socialProvider = ConnectionController.state.selectedSocialProvider; + const authProvider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + + if (authProvider && socialProvider === 'farcaster') { + // TODO: remove this once Farcaster session refresh is implemented + // @ts-expect-error + authProvider.webviewRef?.current?.reload(); + } + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_CANCELED', + properties: { provider: ConnectionController.state.selectedSocialProvider! } + }); + } + }; + + const handleGoBack = () => { + checkSocial(); + RouterController.goBack(); + }; + + const handleClose = () => { + checkSocial(); + ModalController.close(); + }; + + const dynamicButtonTemplate = () => { + const showBack = RouterController.state.history.length > 1; + const showHelp = RouterController.state.view === 'Connect'; + + if (showHelp) { + return ; + } + + if (showBack) { + return ; + } + + return ; + }; + + if (!header) return null; + + const bottomPadding = header === ' ' ? '0' : '4xs'; + + return ( + + {dynamicButtonTemplate()} + + {header} + + {showClose ? ( + + ) : ( + + )} + + ); +} diff --git a/packages/appkit/src/partials/w3m-header/styles.ts b/packages/appkit/src/partials/w3m-header/styles.ts new file mode 100644 index 00000000..f26ba320 --- /dev/null +++ b/packages/appkit/src/partials/w3m-header/styles.ts @@ -0,0 +1,8 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + iconPlaceholder: { + height: 32, + width: 32 + } +}); diff --git a/packages/appkit/src/partials/w3m-information-modal/index.tsx b/packages/appkit/src/partials/w3m-information-modal/index.tsx new file mode 100644 index 00000000..2392c6aa --- /dev/null +++ b/packages/appkit/src/partials/w3m-information-modal/index.tsx @@ -0,0 +1,65 @@ +import Modal from 'react-native-modal'; +import { + FlexView, + Text, + type IconType, + IconBox, + useTheme, + Button +} from '@reown/appkit-ui-react-native'; +import styles from './styles'; + +interface InformationModalProps { + iconName: IconType; + title?: string; + description?: string; + visible: boolean; + onClose: () => void; +} + +export function InformationModal({ + iconName, + title, + description, + visible, + onClose +}: InformationModalProps) { + const Theme = useTheme(); + + return ( + + + + {!!title && ( + + {title} + + )} + + {!!description && ( + + {description} + + )} + + + + ); +} diff --git a/packages/appkit/src/partials/w3m-information-modal/styles.ts b/packages/appkit/src/partials/w3m-information-modal/styles.ts new file mode 100644 index 00000000..5fe4bd34 --- /dev/null +++ b/packages/appkit/src/partials/w3m-information-modal/styles.ts @@ -0,0 +1,22 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + modal: { + margin: 0, + justifyContent: 'flex-end' + }, + content: { + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + alignItems: 'center' + }, + title: { + marginTop: Spacing.s, + marginBottom: Spacing.xs + }, + button: { + marginTop: Spacing.xl, + width: '100%' + } +}); diff --git a/packages/appkit/src/partials/w3m-otp-code/index.tsx b/packages/appkit/src/partials/w3m-otp-code/index.tsx new file mode 100644 index 00000000..bc88503b --- /dev/null +++ b/packages/appkit/src/partials/w3m-otp-code/index.tsx @@ -0,0 +1,81 @@ +import { Platform } from 'react-native'; +import { FlexView, Link, LoadingSpinner, Otp, Spacing, Text } from '@reown/appkit-ui-react-native'; + +import { useKeyboard } from '../../hooks/useKeyboard'; +import styles from './styles'; + +interface Props { + onCodeChange?: (code: string) => void; + onSubmit: (code: string) => void; + onRetry: () => void; + loading?: boolean; + error?: string; + email?: string; + timeLeft?: number; + codeExpiry?: number; + retryLabel?: string; + retryDisabledButtonLabel?: string; + retryButtonLabel?: string; +} + +export function OtpCodeView({ + onCodeChange, + onSubmit, + onRetry, + error, + loading, + email, + timeLeft = 0, + codeExpiry = 20, + retryLabel = "Didn't receive it?", + retryDisabledButtonLabel = 'Resend', + retryButtonLabel = 'Resend code' +}: Props) { + const { keyboardShown, keyboardHeight } = useKeyboard(); + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing.l : Spacing.l, + default: Spacing.l + }); + + const handleCodeChange = (code: string) => { + onCodeChange?.(code); + + if (code.length === 6) { + onSubmit?.(code); + } + }; + + return ( + + + Enter the code we sent to{' '} + + {email ?? 'your email'} + + {`The code expires in ${codeExpiry} minutes`} + + + {loading ? ( + + ) : ( + + )} + + {error && ( + + {error} + + )} + {!loading && ( + + + {retryLabel} + + 0 || loading}> + {timeLeft > 0 ? `${retryDisabledButtonLabel} in ${timeLeft}s` : retryButtonLabel} + + + )} + + ); +} diff --git a/packages/appkit/src/partials/w3m-otp-code/styles.ts b/packages/appkit/src/partials/w3m-otp-code/styles.ts new file mode 100644 index 00000000..07c9153c --- /dev/null +++ b/packages/appkit/src/partials/w3m-otp-code/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + expiryText: { + marginTop: Spacing.s, + marginBottom: Spacing.l + }, + otpContainer: { + height: 60 + }, + errorText: { + marginTop: Spacing['2xs'] + } +}); diff --git a/packages/appkit/src/partials/w3m-placeholder/index.tsx b/packages/appkit/src/partials/w3m-placeholder/index.tsx new file mode 100644 index 00000000..8fed2e11 --- /dev/null +++ b/packages/appkit/src/partials/w3m-placeholder/index.tsx @@ -0,0 +1,77 @@ +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { + IconBox, + Text, + FlexView, + Spacing, + type IconType, + Button, + type ColorType +} from '@reown/appkit-ui-react-native'; + +interface Props { + icon?: IconType; + iconColor?: ColorType; + title?: string; + description?: string; + style?: StyleProp; + actionIcon?: IconType; + actionPress?: () => void; + actionTitle?: string; +} + +export function Placeholder({ + icon, + iconColor = 'fg-175', + title, + description, + style, + actionPress, + actionTitle, + actionIcon +}: Props) { + return ( + + {icon && ( + + )} + {title && ( + + {title} + + )} + {description && ( + + {description} + + )} + {actionPress && ( + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + minHeight: 200 + }, + icon: { + marginBottom: Spacing.l + }, + title: { + marginBottom: Spacing['2xs'] + }, + button: { + marginTop: Spacing.m + } +}); diff --git a/packages/appkit/src/partials/w3m-selector-modal/index.tsx b/packages/appkit/src/partials/w3m-selector-modal/index.tsx new file mode 100644 index 00000000..37c8c94e --- /dev/null +++ b/packages/appkit/src/partials/w3m-selector-modal/index.tsx @@ -0,0 +1,124 @@ +import { useSnapshot } from 'valtio'; +import Modal from 'react-native-modal'; +import { FlatList, View } from 'react-native'; +import { + FlexView, + IconBox, + IconLink, + Image, + SearchBar, + Separator, + Spacing, + Text, + useTheme +} from '@reown/appkit-ui-react-native'; +import styles from './styles'; +import { AssetUtil, NetworkController } from '@reown/appkit-core-react-native'; + +interface SelectorModalProps { + title?: string; + visible: boolean; + onClose: () => void; + items: any[]; + selectedItem?: any; + renderItem: ({ item }: { item: any }) => React.ReactElement; + keyExtractor: (item: any, index: number) => string; + onSearch: (value: string) => void; + itemHeight?: number; + showNetwork?: boolean; + searchPlaceholder?: string; +} + +const SEPARATOR_HEIGHT = Spacing.s; + +export function SelectorModal({ + title, + visible, + onClose, + items, + selectedItem, + renderItem, + onSearch, + searchPlaceholder, + keyExtractor, + itemHeight, + showNetwork +}: SelectorModalProps) { + const Theme = useTheme(); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const renderSeparator = () => { + return ; + }; + + return ( + + + + + {!!title && {title}} + {showNetwork ? ( + networkImage ? ( + + + + ) : ( + + ) + ) : ( + + )} + + + {selectedItem && ( + + {renderItem({ item: selectedItem })} + + + )} + ({ + length: itemHeight + SEPARATOR_HEIGHT, + offset: (itemHeight + SEPARATOR_HEIGHT) * index, + index + }) + : undefined + } + /> + + + ); +} diff --git a/packages/appkit/src/partials/w3m-selector-modal/styles.ts b/packages/appkit/src/partials/w3m-selector-modal/styles.ts new file mode 100644 index 00000000..3520474c --- /dev/null +++ b/packages/appkit/src/partials/w3m-selector-modal/styles.ts @@ -0,0 +1,42 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + modal: { + margin: 0, + justifyContent: 'flex-end' + }, + header: { + marginBottom: Spacing.s, + paddingHorizontal: Spacing.m + }, + container: { + height: '80%', + borderTopLeftRadius: BorderRadius.l, + borderTopRightRadius: BorderRadius.l, + paddingTop: Spacing.m + }, + selectedContainer: { + paddingHorizontal: Spacing.m + }, + listContent: { + paddingTop: Spacing.s, + paddingHorizontal: Spacing.m + }, + iconPlaceholder: { + height: 32, + width: 32 + }, + networkImage: { + height: 20, + width: 20, + borderRadius: BorderRadius.full + }, + searchBar: { + marginBottom: Spacing.s, + marginHorizontal: Spacing.s + }, + separator: { + marginTop: Spacing.m + } +}); diff --git a/packages/appkit/src/partials/w3m-send-input-address/index.tsx b/packages/appkit/src/partials/w3m-send-input-address/index.tsx new file mode 100644 index 00000000..2cec2af3 --- /dev/null +++ b/packages/appkit/src/partials/w3m-send-input-address/index.tsx @@ -0,0 +1,74 @@ +import { useState } from 'react'; +import { TextInput } from 'react-native'; +import { FlexView, useTheme } from '@reown/appkit-ui-react-native'; +import { ConnectionController, SendController } from '@reown/appkit-core-react-native'; + +import { useDebounceCallback } from '../../hooks/useDebounceCallback'; +import styles from './styles'; + +export interface SendInputAddressProps { + value?: string; +} + +export function SendInputAddress({ value }: SendInputAddressProps) { + const Theme = useTheme(); + const [inputValue, setInputValue] = useState(value); + + const onSearch = async (search: string) => { + SendController.setLoading(true); + const address = await ConnectionController.getEnsAddress(search); + SendController.setLoading(false); + + if (address) { + SendController.setReceiverProfileName(search); + SendController.setReceiverAddress(address); + const avatar = await ConnectionController.getEnsAvatar(search); + SendController.setReceiverProfileImageUrl(avatar || undefined); + } else { + SendController.setReceiverAddress(search); + SendController.setReceiverProfileName(undefined); + SendController.setReceiverProfileImageUrl(undefined); + } + }; + + const { debouncedCallback: onDebounceSearch } = useDebounceCallback({ + callback: onSearch, + delay: 800 + }); + + const onInputChange = (address: string) => { + setInputValue(address); + SendController.setReceiverAddress(address); + onDebounceSearch(address); + }; + + return ( + + + + ); +} diff --git a/packages/appkit/src/partials/w3m-send-input-address/styles.ts b/packages/appkit/src/partials/w3m-send-input-address/styles.ts new file mode 100644 index 00000000..58ff557a --- /dev/null +++ b/packages/appkit/src/partials/w3m-send-input-address/styles.ts @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + height: 100, + width: '100%', + borderRadius: BorderRadius.s, + borderWidth: StyleSheet.hairlineWidth + }, + input: { + fontSize: 18 + } +}); diff --git a/packages/appkit/src/partials/w3m-send-input-token/index.tsx b/packages/appkit/src/partials/w3m-send-input-token/index.tsx new file mode 100644 index 00000000..8c5eb250 --- /dev/null +++ b/packages/appkit/src/partials/w3m-send-input-token/index.tsx @@ -0,0 +1,119 @@ +import { useRef, useState } from 'react'; +import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; +import { FlexView, Link, Text, useTheme, TokenButton } from '@reown/appkit-ui-react-native'; +import { NumberUtil, type Balance } from '@reown/appkit-common-react-native'; +import { ConstantsUtil, SendController } from '@reown/appkit-core-react-native'; + +import { getMaxAmount, getSendValue } from './utils'; +import styles from './styles'; + +export interface SendInputTokenProps { + token?: Balance; + sendTokenAmount?: number; + gasPrice?: number; + style?: StyleProp; + onTokenPress?: () => void; +} + +export function SendInputToken({ + token, + sendTokenAmount, + gasPrice, + style, + onTokenPress +}: SendInputTokenProps) { + const Theme = useTheme(); + const valueInputRef = useRef(null); + const [inputValue, setInputValue] = useState(sendTokenAmount?.toString()); + const sendValue = getSendValue(token, sendTokenAmount); + const maxAmount = getMaxAmount(token); + const maxError = token && sendTokenAmount && sendTokenAmount > Number(token.quantity.numeric); + + const onInputChange = (value: string) => { + const formattedValue = value.replace(/,/g, '.'); + + if (Number(formattedValue) >= 0 || formattedValue === '') { + setInputValue(formattedValue); + SendController.setTokenAmount(Number(formattedValue)); + } + }; + + const onMaxPress = () => { + if (token && gasPrice) { + const isNetworkToken = + token.address === undefined || + Object.values(ConstantsUtil.NATIVE_TOKEN_ADDRESS).some( + nativeAddress => token?.address === nativeAddress + ); + + const numericGas = NumberUtil.bigNumber(gasPrice).shiftedBy(-token.quantity.decimals); + + const maxValue = isNetworkToken + ? NumberUtil.bigNumber(token.quantity.numeric).minus(numericGas) + : NumberUtil.bigNumber(token.quantity.numeric); + + SendController.setTokenAmount(Number(maxValue.toFixed(20))); + setInputValue(maxValue.toFixed(20)); + valueInputRef.current?.blur(); + } + }; + + return ( + + + + + + {token && ( + + + {sendValue ?? ''} + + + + {maxAmount ?? ''} + + Max + + + )} + + ); +} diff --git a/packages/appkit/src/partials/w3m-send-input-token/styles.ts b/packages/appkit/src/partials/w3m-send-input-token/styles.ts new file mode 100644 index 00000000..e35dc185 --- /dev/null +++ b/packages/appkit/src/partials/w3m-send-input-token/styles.ts @@ -0,0 +1,20 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: 100, + width: '100%', + borderRadius: BorderRadius.s, + borderWidth: StyleSheet.hairlineWidth + }, + input: { + fontSize: 32, + flex: 1, + marginRight: Spacing.xs + }, + sendValue: { + flex: 1, + marginRight: Spacing.xs + } +}); diff --git a/packages/appkit/src/partials/w3m-send-input-token/utils.ts b/packages/appkit/src/partials/w3m-send-input-token/utils.ts new file mode 100644 index 00000000..38085ed3 --- /dev/null +++ b/packages/appkit/src/partials/w3m-send-input-token/utils.ts @@ -0,0 +1,21 @@ +import { type Balance, NumberUtil } from '@reown/appkit-common-react-native'; +import { UiUtil } from '@reown/appkit-ui-react-native'; + +export function getSendValue(token?: Balance, sendTokenAmount?: number) { + if (token && sendTokenAmount) { + const price = token.price; + const totalValue = price * sendTokenAmount; + + return totalValue ? `$${UiUtil.formatNumberToLocalString(totalValue, 2)}` : 'Incorrect value'; + } + + return null; +} + +export function getMaxAmount(token?: Balance) { + if (token) { + return NumberUtil.roundNumber(Number(token.quantity.numeric), 6, 5); + } + + return null; +} diff --git a/packages/appkit/src/partials/w3m-snackbar/index.tsx b/packages/appkit/src/partials/w3m-snackbar/index.tsx new file mode 100644 index 00000000..ccf004a7 --- /dev/null +++ b/packages/appkit/src/partials/w3m-snackbar/index.tsx @@ -0,0 +1,49 @@ +import { useSnapshot } from 'valtio'; +import { useEffect, useMemo } from 'react'; +import { Animated } from 'react-native'; +import { SnackController, type SnackControllerState } from '@reown/appkit-core-react-native'; +import { Snackbar as SnackbarComponent } from '@reown/appkit-ui-react-native'; + +import styles from './styles'; + +const getIcon = (variant: SnackControllerState['variant']) => { + if (variant === 'loading') return 'loading'; + + return variant === 'success' ? 'checkmark' : 'close'; +}; + +export function Snackbar() { + const { open, message, variant, long } = useSnapshot(SnackController.state); + const componentOpacity = useMemo(() => new Animated.Value(0), []); + + useEffect(() => { + if (open) { + Animated.timing(componentOpacity, { + toValue: 1, + duration: 150, + useNativeDriver: true + }).start(); + setTimeout( + () => { + Animated.timing(componentOpacity, { + toValue: 0, + duration: 300, + useNativeDriver: true + }).start(() => { + SnackController.hide(); + }); + }, + long ? 15000 : 2200 + ); + } + }, [open, long, componentOpacity]); + + return ( + + ); +} diff --git a/packages/appkit/src/partials/w3m-snackbar/styles.ts b/packages/appkit/src/partials/w3m-snackbar/styles.ts new file mode 100644 index 00000000..c5765d09 --- /dev/null +++ b/packages/appkit/src/partials/w3m-snackbar/styles.ts @@ -0,0 +1,12 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + marginTop: Spacing.s, + justifyContent: 'center', + alignItems: 'center', + position: 'absolute', + alignSelf: 'center' + } +}); diff --git a/packages/appkit/src/partials/w3m-swap-details/index.tsx b/packages/appkit/src/partials/w3m-swap-details/index.tsx new file mode 100644 index 00000000..dd97e87a --- /dev/null +++ b/packages/appkit/src/partials/w3m-swap-details/index.tsx @@ -0,0 +1,160 @@ +import { useSnapshot } from 'valtio'; +import { useState } from 'react'; +import { ConstantsUtil, NetworkController, SwapController } from '@reown/appkit-core-react-native'; +import { + FlexView, + Text, + UiUtil, + Toggle, + useTheme, + Pressable, + Icon +} from '@reown/appkit-ui-react-native'; +import { NumberUtil } from '@reown/appkit-common-react-native'; + +import { InformationModal } from '../w3m-information-modal'; +import styles from './styles'; +import { getModalData } from './utils'; + +interface SwapDetailsProps { + initialOpen?: boolean; + canClose?: boolean; +} + +// -- Constants ----------------------------------------- // +const slippageRate = ConstantsUtil.CONVERT_SLIPPAGE_TOLERANCE; + +export function SwapDetails({ initialOpen, canClose }: SwapDetailsProps) { + const Theme = useTheme(); + const { + maxSlippage = 0, + sourceToken, + toToken, + gasPriceInUSD = 0, + priceImpact, + toTokenAmount + } = useSnapshot(SwapController.state); + + const [modalData, setModalData] = useState<{ title: string; description: string } | undefined>(); + + const toTokenSwappedAmount = + SwapController.state.sourceTokenPriceInUSD && SwapController.state.toTokenPriceInUSD + ? (1 / SwapController.state.toTokenPriceInUSD) * SwapController.state.sourceTokenPriceInUSD + : 0; + + const renderTitle = () => ( + + + 1 {SwapController.state.sourceToken?.symbol} = {''} + {UiUtil.formatNumberToLocalString(toTokenSwappedAmount, 3)}{' '} + {SwapController.state.toToken?.symbol} + + + ~$ + {UiUtil.formatNumberToLocalString(SwapController.state.sourceTokenPriceInUSD)} + + + ); + + const minimumReceive = NumberUtil.parseLocalStringToNumber(toTokenAmount) - maxSlippage; + const providerFee = SwapController.getProviderFeePrice(); + + const onPriceImpactPress = () => { + setModalData(getModalData('priceImpact')); + }; + + const onSlippagePress = () => { + const minimumString = UiUtil.formatNumberToLocalString( + minimumReceive, + minimumReceive < 1 ? 8 : 2 + ); + setModalData( + getModalData('slippage', { + minimumReceive: minimumString, + toTokenSymbol: SwapController.state.toToken?.symbol + }) + ); + }; + + const onNetworkCostPress = () => { + setModalData( + getModalData('networkCost', { + networkSymbol: SwapController.state.networkTokenSymbol, + networkName: NetworkController.state.caipNetwork?.name + }) + ); + }; + + return ( + <> + + + + + Network cost + + + + + + + ${UiUtil.formatNumberToLocalString(gasPriceInUSD, gasPriceInUSD < 1 ? 8 : 2)} + + + {!!priceImpact && ( + + + + Price impact + + + + + + + ~{UiUtil.formatNumberToLocalString(priceImpact, 3)}% + + + )} + {maxSlippage !== undefined && maxSlippage > 0 && !!sourceToken?.symbol && ( + + + + Max. slippage + + + + + + + {UiUtil.formatNumberToLocalString(maxSlippage, 6)} {toToken?.symbol}{' '} + + {slippageRate}% + + + + )} + + + Included provider fee + + + ${UiUtil.formatNumberToLocalString(providerFee, providerFee < 1 ? 8 : 2)} + + + + setModalData(undefined)} + /> + + ); +} diff --git a/packages/appkit/src/partials/w3m-swap-details/styles.ts b/packages/appkit/src/partials/w3m-swap-details/styles.ts new file mode 100644 index 00000000..92fe6b17 --- /dev/null +++ b/packages/appkit/src/partials/w3m-swap-details/styles.ts @@ -0,0 +1,26 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + width: '100%', + borderRadius: 16 + }, + titlePrice: { + marginLeft: Spacing['3xs'] + }, + detailTitle: { + marginRight: Spacing['3xs'] + }, + item: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: Spacing.s, + borderRadius: BorderRadius.xxs, + marginTop: Spacing['2xs'] + }, + infoIcon: { + borderRadius: BorderRadius.full + } +}); diff --git a/packages/appkit/src/partials/w3m-swap-details/utils.ts b/packages/appkit/src/partials/w3m-swap-details/utils.ts new file mode 100644 index 00000000..fc834532 --- /dev/null +++ b/packages/appkit/src/partials/w3m-swap-details/utils.ts @@ -0,0 +1,33 @@ +export interface ModalData { + detail: ModalDetail; + opts?: ModalDataOpts; +} + +export type ModalDetail = 'slippage' | 'networkCost' | 'priceImpact'; + +export interface ModalDataOpts { + networkSymbol?: string; + networkName?: string; + minimumReceive?: string; + toTokenSymbol?: string; +} + +export const getModalData = (detail: ModalDetail, opts?: ModalDataOpts) => { + switch (detail) { + case 'slippage': + return { + title: 'Max. slippage', + description: `Max slippage sets the minimum amount you must receive for the transaction to proceed. The transaction will be reversed if you receive less than ${opts?.minimumReceive} ${opts?.toTokenSymbol} due to price changes` + }; + case 'networkCost': + return { + title: 'Network cost', + description: `Network cost is paid in ${opts?.networkSymbol} on the ${opts?.networkName} network in order to execute the transaction` + }; + case 'priceImpact': + return { + title: 'Price impact', + description: 'Price impact reflects the change in market price due to your trade' + }; + } +}; diff --git a/packages/appkit/src/partials/w3m-swap-input/index.tsx b/packages/appkit/src/partials/w3m-swap-input/index.tsx new file mode 100644 index 00000000..16db2698 --- /dev/null +++ b/packages/appkit/src/partials/w3m-swap-input/index.tsx @@ -0,0 +1,152 @@ +import { useRef } from 'react'; +import type BigNumber from 'bignumber.js'; +import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; +import { + FlexView, + useTheme, + TokenButton, + Shimmer, + Text, + UiUtil, + Link +} from '@reown/appkit-ui-react-native'; +import { type SwapTokenWithBalance } from '@reown/appkit-core-react-native'; + +import styles from './styles'; +import { NumberUtil } from '@reown/appkit-common-react-native'; + +export interface SwapInputProps { + token?: SwapTokenWithBalance; + value?: string; + gasPrice?: number; + style?: StyleProp; + loading?: boolean; + onTokenPress?: () => void; + onMaxPress?: () => void; + onChange?: (value: string) => void; + balance?: BigNumber; + marketValue?: number; + editable?: boolean; + autoFocus?: boolean; +} + +const MINIMUM_USD_VALUE_TO_CONVERT = 0.00005; + +export function SwapInput({ + token, + value, + style, + loading, + onTokenPress, + onMaxPress, + onChange, + marketValue, + editable, + autoFocus +}: SwapInputProps) { + const Theme = useTheme(); + const valueInputRef = useRef(null); + const isMarketValueGreaterThanZero = + !!marketValue && NumberUtil.bigNumber(marketValue).isGreaterThan('0'); + const maxAmount = UiUtil.formatNumberToLocalString(token?.quantity.numeric, 3); + const maxError = Number(value) > Number(token?.quantity.numeric); + const showMax = + onMaxPress && + !!token?.quantity.numeric && + NumberUtil.multiply(token?.quantity.numeric, token?.price).isGreaterThan( + MINIMUM_USD_VALUE_TO_CONVERT + ); + + const handleInputChange = (_value: string) => { + const formattedValue = _value.replace(/,/g, '.'); + + if (Number(formattedValue) >= 0 || formattedValue === '') { + onChange?.(formattedValue); + } + }; + + const handleMaxPress = () => { + if (valueInputRef.current) { + valueInputRef.current.blur(); + } + + onMaxPress?.(); + }; + + return ( + + {loading ? ( + + + + + ) : ( + <> + + + + + {(showMax || isMarketValueGreaterThanZero) && ( + + + {isMarketValueGreaterThanZero + ? `~$${UiUtil.formatNumberToLocalString(marketValue, 2)}` + : ''} + + {showMax && ( + + + {showMax ? maxAmount : ''} + + Max + + )} + + )} + + )} + + ); +} diff --git a/packages/appkit/src/partials/w3m-swap-input/styles.ts b/packages/appkit/src/partials/w3m-swap-input/styles.ts new file mode 100644 index 00000000..e35dc185 --- /dev/null +++ b/packages/appkit/src/partials/w3m-swap-input/styles.ts @@ -0,0 +1,20 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: 100, + width: '100%', + borderRadius: BorderRadius.s, + borderWidth: StyleSheet.hairlineWidth + }, + input: { + fontSize: 32, + flex: 1, + marginRight: Spacing.xs + }, + sendValue: { + flex: 1, + marginRight: Spacing.xs + } +}); diff --git a/packages/appkit/src/utils/ConnectorUtil.ts b/packages/appkit/src/utils/ConnectorUtil.ts new file mode 100644 index 00000000..456e5190 --- /dev/null +++ b/packages/appkit/src/utils/ConnectorUtil.ts @@ -0,0 +1,22 @@ +import type { WalletConnector } from "../adapters/types"; +import { WalletConnectConnector } from "../connectors/WalletConnectConnector"; + +const mockMetadata = { + name: "Reown", + description: "Reown App", + url: "https://reown.xyz", + icons: ["https://reown.xyz/icon.png"] +} + +export const ConnectorUtil = { + async createConnector({ walletType, extraConnectors, projectId }: { walletType: string, extraConnectors: WalletConnector[], projectId: string }): Promise { + // Check if an extra connector was provided by the developer + const CustomConnector = extraConnectors.find(connector => connector.type === walletType); + if (CustomConnector) return CustomConnector; + + // If no extra connector is provided, default to WalletConnectConnector + if (walletType === "walletconnect") return WalletConnectConnector.create({ projectId, metadata: mockMetadata }); + + throw new Error(`Unsupported wallet type: ${walletType}`); + } +} \ No newline at end of file diff --git a/packages/appkit/src/utils/UiUtil.ts b/packages/appkit/src/utils/UiUtil.ts new file mode 100644 index 00000000..7288f410 --- /dev/null +++ b/packages/appkit/src/utils/UiUtil.ts @@ -0,0 +1,42 @@ +import { + AssetUtil, + ConnectionController, + StorageUtil, + type WcWallet +} from '@reown/appkit-core-react-native'; +import { + LayoutAnimation, + type LayoutAnimationProperty, + type LayoutAnimationType +} from 'react-native'; + +export const UiUtil = { + TOTAL_VISIBLE_WALLETS: 4, + + createViewTransition: () => { + LayoutAnimation.configureNext(LayoutAnimation.create(200, 'easeInEaseOut', 'opacity')); + }, + + animateChange: ( + type: LayoutAnimationType = 'linear', + creationProp: LayoutAnimationProperty = 'scaleX' + ) => { + LayoutAnimation.configureNext(LayoutAnimation.create(150, type, creationProp)); + }, + + storeConnectedWallet: async ( + wcLinking: { name: string; href: string }, + pressedWallet?: WcWallet + ) => { + StorageUtil.setWalletConnectDeepLink(wcLinking); + + if (pressedWallet) { + const recentWallets = await StorageUtil.addRecentWallet(pressedWallet); + if (recentWallets) { + ConnectionController.setRecentWallets(recentWallets); + } + const url = AssetUtil.getWalletImage(pressedWallet); + ConnectionController.setConnectedWalletImageUrl(url); + } + } +}; diff --git a/packages/appkit/src/views/w3m-account-default-view/components/auth-buttons.tsx b/packages/appkit/src/views/w3m-account-default-view/components/auth-buttons.tsx new file mode 100644 index 00000000..43e3cd38 --- /dev/null +++ b/packages/appkit/src/views/w3m-account-default-view/components/auth-buttons.tsx @@ -0,0 +1,68 @@ +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { UpgradeWalletButton } from './upgrade-wallet-button'; +import { ListItem, ListSocial, Spacing, Text } from '@reown/appkit-ui-react-native'; +import type { SocialProvider } from '@reown/appkit-common-react-native'; + +export interface AuthButtonsProps { + onUpgradePress: () => void; + onPress: () => void; + socialProvider?: SocialProvider; + text: string; + style?: StyleProp; +} + +export function AuthButtons({ + onUpgradePress, + onPress, + socialProvider, + text, + style +}: AuthButtonsProps) { + return ( + <> + + {socialProvider ? ( + + + {text} + + + ) : ( + + + {text} + + + )} + + ); +} + +const styles = StyleSheet.create({ + actionButton: { + marginBottom: Spacing.xs + }, + upgradeButton: { + marginBottom: Spacing.s + }, + socialContainer: { + justifyContent: 'flex-start', + width: '100%' + }, + socialText: { + flex: 1, + marginLeft: Spacing.s + } +}); diff --git a/packages/appkit/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx b/packages/appkit/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx new file mode 100644 index 00000000..e02c5a6a --- /dev/null +++ b/packages/appkit/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx @@ -0,0 +1,67 @@ +import { Animated, Pressable, StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { + BorderRadius, + FlexView, + Icon, + IconBox, + Spacing, + Text, + useTheme, + useAnimatedValue +} from '@reown/appkit-ui-react-native'; + +const AnimatedPressable = Animated.createAnimatedComponent(Pressable); + +export interface Props { + onPress: () => void; + style?: StyleProp; +} + +export function UpgradeWalletButton({ style, onPress }: Props) { + const Theme = useTheme(); + const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( + Theme['accent-glass-010'], + Theme['accent-glass-020'] + ); + + return ( + + + + + Upgrade your wallet + + + Transition to a self-custodial wallet + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + height: 75, + borderRadius: BorderRadius.s, + backgroundColor: 'red', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: Spacing.s + }, + textContainer: { + marginHorizontal: Spacing.m + }, + upgradeText: { + marginBottom: Spacing['3xs'] + }, + chevron: { + marginRight: Spacing['2xs'] + } +}); diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx new file mode 100644 index 00000000..5cd83abb --- /dev/null +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -0,0 +1,334 @@ +import { useSnapshot } from 'valtio'; +import { useState } from 'react'; +import { Linking, ScrollView } from 'react-native'; +import { + AccountController, + ApiController, + AssetUtil, + ConnectionController, + ConnectorController, + CoreHelperUtil, + ConnectionUtil, + EventsController, + ModalController, + NetworkController, + OptionsController, + RouterController, + SnackController, + type AppKitFrameProvider, + ConstantsUtil, + SwapController, + OnRampController +} from '@reown/appkit-core-react-native'; +import { + Avatar, + Button, + FlexView, + IconLink, + Text, + UiUtil, + Spacing, + ListItem +} from '@reown/appkit-ui-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; + +import styles from './styles'; +import { AuthButtons } from './components/auth-buttons'; + +export function AccountDefaultView() { + const { + address, + profileName, + profileImage, + balance, + balanceSymbol, + addressExplorerUrl, + preferredAccountType + } = useSnapshot(AccountController.state); + const { loading } = useSnapshot(ModalController.state); + const [disconnecting, setDisconnecting] = useState(false); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { connectedConnector } = useSnapshot(ConnectorController.state); + const { connectedSocialProvider } = useSnapshot(ConnectionController.state); + const { features, isOnRampEnabled } = useSnapshot(OptionsController.state); + const { history } = useSnapshot(RouterController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const showCopy = OptionsController.isClipboardAvailable(); + const isAuth = connectedConnector === 'AUTH'; + const showBalance = balance && !isAuth; + const showExplorer = addressExplorerUrl && !isAuth; + const showBack = history.length > 1; + const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); + const { padding } = useCustomDimensions(); + + async function onDisconnect() { + setDisconnecting(true); + await ConnectionUtil.disconnect(); + setDisconnecting(false); + } + + const onSwitchAccountType = async () => { + try { + if (isAuth) { + ModalController.setLoading(true); + const accountType = + AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + await provider?.setPreferredAccount(accountType); + EventsController.sendEvent({ + type: 'track', + event: 'SET_PREFERRED_ACCOUNT_TYPE', + properties: { + accountType, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + } + } catch (error) { + ModalController.setLoading(false); + SnackController.showError('Error switching account type'); + } + }; + + const getUserEmail = () => { + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + if (!provider) return ''; + + return provider.getEmail(); + }; + + const getUsername = () => { + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + if (!provider) return ''; + + return provider.getUsername(); + }; + + const onExplorerPress = () => { + if (AccountController.state.addressExplorerUrl) { + Linking.openURL(AccountController.state.addressExplorerUrl); + } + }; + + const onCopyAddress = () => { + if (AccountController.state.profileName) { + OptionsController.copyToClipboard(AccountController.state.profileName); + SnackController.showSuccess('Name copied'); + } else if (AccountController.state.address) { + OptionsController.copyToClipboard( + AccountController.state.profileName ?? AccountController.state.address + ); + SnackController.showSuccess('Address copied'); + } + }; + + const onSwapPress = () => { + if ( + NetworkController.state.caipNetwork?.id && + !ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(`${NetworkController.state.caipNetwork.id}`) + ) { + RouterController.push('UnsupportedChain'); + } else { + SwapController.resetState(); + EventsController.sendEvent({ + type: 'track', + event: 'OPEN_SWAP', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: false + } + }); + RouterController.push('Swap'); + } + }; + + const onBuyPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'SELECT_BUY_CRYPTO' + }); + + OnRampController.resetState(); + RouterController.push('OnRamp'); + }; + + const onActivityPress = () => { + RouterController.push('Transactions'); + }; + + const onNetworkPress = () => { + RouterController.push('Networks'); + + EventsController.sendEvent({ + type: 'track', + event: 'CLICK_NETWORKS' + }); + }; + + const onUpgradePress = () => { + EventsController.sendEvent({ type: 'track', event: 'EMAIL_UPGRADE_FROM_MODAL' }); + RouterController.push('UpgradeEmailWallet'); + }; + + const onEmailPress = () => { + if (ConnectionController.state.connectedSocialProvider) return; + RouterController.push('UpdateEmailWallet', { email: getUserEmail() }); + }; + + return ( + <> + {showBack && ( + + )} + + + + + + + {profileName + ? UiUtil.getTruncateString({ + string: profileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: address ?? '', + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + })} + + {showCopy && ( + + )} + + {showBalance && ( + + {CoreHelperUtil.formatBalance(balance, balanceSymbol)} + + )} + {showExplorer && ( + + )} + + {isAuth && ( + + )} + + + {caipNetwork?.name} + + + {!isAuth && isOnRampEnabled && ( + + Buy crypto + + )} + {!isAuth && features?.swaps && ( + + Swap + + )} + {!isAuth && ( + + Activity + + )} + {showSwitchAccountType && ( + + {`Switch to your ${ + preferredAccountType === 'eoa' ? 'smart account' : 'EOA' + }`} + + )} + + Disconnect + + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-account-default-view/styles.ts b/packages/appkit/src/views/w3m-account-default-view/styles.ts new file mode 100644 index 00000000..1d0389f0 --- /dev/null +++ b/packages/appkit/src/views/w3m-account-default-view/styles.ts @@ -0,0 +1,28 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + backIcon: { + alignSelf: 'flex-end', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + left: Spacing.xl + }, + closeIcon: { + alignSelf: 'flex-end', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + right: Spacing.xl + }, + copyButton: { + marginLeft: Spacing['4xs'] + }, + actionButton: { + marginBottom: Spacing.xs + }, + upgradeButton: { + marginBottom: Spacing.s + } +}); diff --git a/packages/appkit/src/views/w3m-account-view/index.tsx b/packages/appkit/src/views/w3m-account-view/index.tsx new file mode 100644 index 00000000..14e19d85 --- /dev/null +++ b/packages/appkit/src/views/w3m-account-view/index.tsx @@ -0,0 +1,105 @@ +import { useSnapshot } from 'valtio'; +import { useEffect } from 'react'; +import { ScrollView } from 'react-native'; +import { + AccountPill, + FlexView, + Icon, + IconLink, + NetworkButton, + useTheme, + Promo +} from '@reown/appkit-ui-react-native'; +import { + AccountController, + ApiController, + AssetUtil, + ModalController, + NetworkController, + RouterController, + SendController +} from '@reown/appkit-core-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; +import styles from './styles'; + +export function AccountView() { + const Theme = useTheme(); + const { padding } = useCustomDimensions(); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { address, profileName, profileImage, preferredAccountType } = useSnapshot( + AccountController.state + ); + const showActivate = + preferredAccountType === 'eoa' && NetworkController.checkIfSmartAccountEnabled(); + + const onProfilePress = () => { + RouterController.push('AccountDefault'); + }; + + const onNetworkPress = () => { + RouterController.push('Networks'); + }; + + const onActivatePress = () => { + RouterController.push('UpgradeToSmartAccount'); + }; + + useEffect(() => { + AccountController.fetchTokenBalance(); + SendController.resetSend(); + }, []); + + useEffect(() => { + AccountController.fetchTokenBalance(); + + const balanceInterval = setInterval(() => { + AccountController.fetchTokenBalance(); + }, 10000); + + return () => { + clearInterval(balanceInterval); + }; + }, []); + + return ( + + + + + + + {showActivate && ( + + )} + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-account-view/styles.ts b/packages/appkit/src/views/w3m-account-view/styles.ts new file mode 100644 index 00000000..0a256790 --- /dev/null +++ b/packages/appkit/src/views/w3m-account-view/styles.ts @@ -0,0 +1,32 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { Platform, StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + contentContainer: { + paddingBottom: Platform.select({ ios: Spacing.s }) + }, + networkIcon: { + alignSelf: 'flex-start', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + left: Spacing.l + }, + closeIcon: { + alignSelf: 'flex-end', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + right: Spacing.xl + }, + accountPill: { + alignSelf: 'center', + marginBottom: Spacing.s, + marginHorizontal: Spacing.s + }, + promoPill: { + marginTop: Spacing.xs, + marginBottom: Spacing['2xl'], + alignSelf: 'center' + } +}); diff --git a/packages/appkit/src/views/w3m-all-wallets-view/index.tsx b/packages/appkit/src/views/w3m-all-wallets-view/index.tsx new file mode 100644 index 00000000..d59d3088 --- /dev/null +++ b/packages/appkit/src/views/w3m-all-wallets-view/index.tsx @@ -0,0 +1,107 @@ +import { useState } from 'react'; +import { + ConnectionController, + ConnectorController, + EventsController, + RouterController, + type WcWallet +} from '@reown/appkit-core-react-native'; +import { FlexView, IconLink, SearchBar, Spacing, useTheme } from '@reown/appkit-ui-react-native'; + +import styles from './styles'; +import { useDebounceCallback } from '../../hooks/useDebounceCallback'; +import { AllWalletsList } from '../../partials/w3m-all-wallets-list'; +import { AllWalletsSearch } from '../../partials/w3m-all-wallets-search'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; + +export function AllWalletsView() { + const Theme = useTheme(); + const [searchQuery, setSearchQuery] = useState(''); + const { maxWidth } = useCustomDimensions(); + const numColumns = 4; + const usableWidth = maxWidth - Spacing.xs * 2; + const itemWidth = Math.abs(Math.trunc(usableWidth / numColumns)); + + const { debouncedCallback: onInputChange } = useDebounceCallback({ callback: setSearchQuery }); + + const onWalletPress = (wallet: WcWallet) => { + const connector = ConnectorController.state.connectors.find(c => c.explorerId === wallet.id); + if (connector) { + RouterController.push('ConnectingExternal', { connector, wallet }); + } else { + RouterController.push('ConnectingWalletConnect', { wallet }); + } + + EventsController.sendEvent({ + type: 'track', + event: 'SELECT_WALLET', + properties: { name: wallet.name ?? 'Unknown', platform: 'mobile', explorer_id: wallet.id } + }); + }; + + const onQrCodePress = () => { + ConnectionController.removePressedWallet(); + ConnectionController.removeWcLinking(); + RouterController.push('ConnectingWalletConnect'); + + EventsController.sendEvent({ + type: 'track', + event: 'SELECT_WALLET', + properties: { name: 'WalletConnect', platform: 'qrcode' } + }); + }; + + const headerTemplate = () => { + return ( + + + + + ); + }; + + const listTemplate = () => { + if (searchQuery) { + return ( + + ); + } + + return ( + + ); + }; + + return ( + <> + {headerTemplate()} + {listTemplate()} + + ); +} diff --git a/packages/appkit/src/views/w3m-all-wallets-view/styles.ts b/packages/appkit/src/views/w3m-all-wallets-view/styles.ts new file mode 100644 index 00000000..fe5c1701 --- /dev/null +++ b/packages/appkit/src/views/w3m-all-wallets-view/styles.ts @@ -0,0 +1,21 @@ +import { Platform, StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + header: { + zIndex: 1, + alignSelf: 'center', + ...Platform.select({ + ios: { + shadowOpacity: 1, + shadowOffset: { width: 0, height: 6 } + } + }) + }, + icon: { + marginLeft: 8, + borderWidth: StyleSheet.hairlineWidth + }, + searchBar: { + flex: 1 + } +}); diff --git a/packages/appkit/src/views/w3m-connect-socials-view/index.tsx b/packages/appkit/src/views/w3m-connect-socials-view/index.tsx new file mode 100644 index 00000000..228bc143 --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-socials-view/index.tsx @@ -0,0 +1,58 @@ +import { useEffect } from 'react'; +import { useSnapshot } from 'valtio'; +import { ScrollView } from 'react-native'; +import { StringUtil, type SocialProvider } from '@reown/appkit-common-react-native'; +import { + ConnectionController, + EventsController, + OptionsController, + RouterController, + WebviewController +} from '@reown/appkit-core-react-native'; +import { FlexView, ListSocial, Text } from '@reown/appkit-ui-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectSocialsView() { + const { features } = useSnapshot(OptionsController.state); + const { padding } = useCustomDimensions(); + const socialProviders = (features?.socials ?? []) as SocialProvider[]; + + const onItemPress = (provider: SocialProvider) => { + ConnectionController.setSelectedSocialProvider(provider); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_STARTED', + properties: { provider } + }); + if (provider === 'farcaster') { + RouterController.push('ConnectingFarcaster'); + } else { + RouterController.push('ConnectingSocial'); + } + }; + + useEffect(() => { + WebviewController.setConnecting(false); + }, []); + + return ( + + + {socialProviders.map(provider => ( + onItemPress(provider)} + style={styles.item} + > + + {StringUtil.capitalize(provider)} + + + ))} + + + ); +} diff --git a/packages/appkit/src/views/w3m-connect-socials-view/styles.ts b/packages/appkit/src/views/w3m-connect-socials-view/styles.ts new file mode 100644 index 00000000..be837b83 --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-socials-view/styles.ts @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + item: { + marginVertical: Spacing['3xs'], + justifyContent: 'flex-start' + }, + text: { + marginLeft: Spacing.s + } +}); diff --git a/packages/appkit/src/views/w3m-connect-view/components/all-wallet-list.tsx b/packages/appkit/src/views/w3m-connect-view/components/all-wallet-list.tsx new file mode 100644 index 00000000..c9d0d076 --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/all-wallet-list.tsx @@ -0,0 +1,60 @@ +import { type StyleProp, type ViewStyle } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { + ApiController, + AssetUtil, + ConnectionController, + type ConnectionControllerState, + type WcWallet +} from '@reown/appkit-core-react-native'; +import { ListItemLoader, ListWallet } from '@reown/appkit-ui-react-native'; +import { UiUtil } from '../../../utils/UiUtil'; +import { filterOutRecentWallets } from '../utils'; + +interface Props { + itemStyle: StyleProp; + onWalletPress: (wallet: WcWallet) => void; + isWalletConnectEnabled: boolean; +} + +export function AllWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { + const { installed, featured, recommended, prefetchLoading } = useSnapshot(ApiController.state); + const { recentWallets } = useSnapshot(ConnectionController.state) as ConnectionControllerState; + const imageHeaders = ApiController._getApiHeaders(); + const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; + + const combinedWallets = [...installed, ...featured, ...recommended]; + + // Deduplicate by wallet ID + const uniqueWallets = Array.from( + new Map(combinedWallets.map(wallet => [wallet.id, wallet])).values() + ); + + const list = filterOutRecentWallets(recentWallets, uniqueWallets, RECENT_COUNT).slice( + 0, + UiUtil.TOTAL_VISIBLE_WALLETS - RECENT_COUNT + ); + + if (!isWalletConnectEnabled || !list?.length) { + return null; + } + + return prefetchLoading ? ( + <> + + + + ) : ( + list.map(wallet => ( + onWalletPress(wallet)} + style={itemStyle} + installed={!!installed.find(installedWallet => installedWallet.id === wallet.id)} + /> + )) + ); +} diff --git a/packages/appkit/src/views/w3m-connect-view/components/all-wallets-button.tsx b/packages/appkit/src/views/w3m-connect-view/components/all-wallets-button.tsx new file mode 100644 index 00000000..1e4c76ed --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/all-wallets-button.tsx @@ -0,0 +1,33 @@ +import { useSnapshot } from 'valtio'; +import { ApiController } from '@reown/appkit-core-react-native'; +import { ListWallet } from '@reown/appkit-ui-react-native'; +import type { StyleProp, ViewStyle } from 'react-native'; + +interface Props { + itemStyle: StyleProp; + onPress: () => void; + isWalletConnectEnabled: boolean; +} + +export function AllWalletsButton({ itemStyle, onPress, isWalletConnectEnabled }: Props) { + const { installed, count } = useSnapshot(ApiController.state); + + if (!isWalletConnectEnabled) { + return null; + } + + const total = installed.length + count; + const label = total > 10 ? `${Math.floor(total / 10) * 10}+` : total; + + return ( + + ); +} diff --git a/packages/appkit/src/views/w3m-connect-view/components/connect-email-input.tsx b/packages/appkit/src/views/w3m-connect-view/components/connect-email-input.tsx new file mode 100644 index 00000000..4ff60e7a --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/connect-email-input.tsx @@ -0,0 +1,69 @@ +import { useSnapshot } from 'valtio'; +import { useState } from 'react'; +import { EmailInput, FlexView } from '@reown/appkit-ui-react-native'; +import { + ConnectorController, + CoreHelperUtil, + EventsController, + RouterController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; + +interface Props { + loading?: boolean; +} + +export function ConnectEmailInput({ loading }: Props) { + const { connectors } = useSnapshot(ConnectorController.state); + const [inputLoading, setInputLoading] = useState(false); + const [error, setError] = useState(''); + const [isValidEmail, setIsValidEmail] = useState(false); + const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; + + const onChangeText = (value: string) => { + setIsValidEmail(CoreHelperUtil.isValidEmail(value)); + setError(''); + }; + + const onEmailFocus = () => { + EventsController.sendEvent({ type: 'track', event: 'EMAIL_LOGIN_SELECTED' }); + }; + + const onEmailSubmit = async (email: string) => { + try { + if (email.length === 0) return; + + setInputLoading(true); + const response = await authProvider.connectEmail({ email }); + EventsController.sendEvent({ type: 'track', event: 'EMAIL_SUBMITTED' }); + if (response.action === 'VERIFY_DEVICE') { + RouterController.push('EmailVerifyDevice', { email }); + } else if (response.action === 'VERIFY_OTP') { + RouterController.push('EmailVerifyOtp', { email }); + } + } catch (e: any) { + const parsedError = CoreHelperUtil.parseError(e); + if (parsedError?.includes('valid email')) { + setError('Invalid email. Try again.'); + } else { + SnackController.showError(parsedError); + } + } finally { + setInputLoading(false); + } + }; + + return ( + + + + ); +} diff --git a/packages/appkit/src/views/w3m-connect-view/components/connectors-list.tsx b/packages/appkit/src/views/w3m-connect-view/components/connectors-list.tsx new file mode 100644 index 00000000..e1920354 --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/connectors-list.tsx @@ -0,0 +1,45 @@ +import { useSnapshot } from 'valtio'; +import type { StyleProp, ViewStyle } from 'react-native'; +import { + ConnectorController, + AssetUtil, + RouterController, + ApiController +} from '@reown/appkit-core-react-native'; + +import { ListWallet } from '@reown/appkit-ui-react-native'; +import type { ConnectorType } from '@reown/appkit-common-react-native'; + +interface Props { + itemStyle: StyleProp; + isWalletConnectEnabled: boolean; +} + +export function ConnectorList({ itemStyle, isWalletConnectEnabled }: Props) { + const { connectors } = useSnapshot(ConnectorController.state); + const excludeConnectors: ConnectorType[] = ['WALLET_CONNECT', 'AUTH']; + const imageHeaders = ApiController._getApiHeaders(); + + if (isWalletConnectEnabled) { + // use wallet from api list + excludeConnectors.push('COINBASE'); + } + + return connectors.map(connector => { + if (excludeConnectors.includes(connector.type)) { + return null; + } + + return ( + RouterController.push('ConnectingExternal', { connector })} + style={itemStyle} + installed={connector.installed} + /> + ); + }); +} diff --git a/packages/appkit/src/views/w3m-connect-view/components/custom-wallet-list.tsx b/packages/appkit/src/views/w3m-connect-view/components/custom-wallet-list.tsx new file mode 100644 index 00000000..ca9a7f9c --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/custom-wallet-list.tsx @@ -0,0 +1,41 @@ +import { useSnapshot } from 'valtio'; +import type { StyleProp, ViewStyle } from 'react-native'; +import { + OptionsController, + type CustomWallet, + type OptionsControllerState, + ApiController, + ConnectionController, + type ConnectionControllerState +} from '@reown/appkit-core-react-native'; +import { ListWallet } from '@reown/appkit-ui-react-native'; +import { filterOutRecentWallets } from '../utils'; + +interface Props { + itemStyle: StyleProp; + onWalletPress: (wallet: CustomWallet) => void; + isWalletConnectEnabled: boolean; +} + +export function CustomWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { + const { installed } = useSnapshot(ApiController.state); + const { recentWallets } = useSnapshot(ConnectionController.state) as ConnectionControllerState; + const { customWallets } = useSnapshot(OptionsController.state) as OptionsControllerState; + const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; + + if (!isWalletConnectEnabled || !customWallets?.length) { + return null; + } + + const list = filterOutRecentWallets(recentWallets, customWallets, RECENT_COUNT); + + return list.map(wallet => ( + onWalletPress(wallet)} + style={itemStyle} + /> + )); +} diff --git a/packages/appkit/src/views/w3m-connect-view/components/recent-wallet-list.tsx b/packages/appkit/src/views/w3m-connect-view/components/recent-wallet-list.tsx new file mode 100644 index 00000000..81f011f7 --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/recent-wallet-list.tsx @@ -0,0 +1,44 @@ +import { useSnapshot } from 'valtio'; +import { + ApiController, + AssetUtil, + type WcWallet, + ConnectionController +} from '@reown/appkit-core-react-native'; +import { ListWallet } from '@reown/appkit-ui-react-native'; +import type { StyleProp, ViewStyle } from 'react-native'; + +interface Props { + itemStyle: StyleProp; + onWalletPress: (wallet: WcWallet, installed: boolean) => void; + isWalletConnectEnabled: boolean; +} + +export function RecentWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { + const installed = ApiController.state.installed; + const { recentWallets } = useSnapshot(ConnectionController.state); + const imageHeaders = ApiController._getApiHeaders(); + const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; + + if (!isWalletConnectEnabled || !recentWallets?.length) { + return null; + } + + return recentWallets.slice(0, RECENT_COUNT).map(wallet => { + const isInstalled = !!installed.find(installedWallet => installedWallet.id === wallet.id); + + return ( + onWalletPress(wallet, isInstalled)} + tagLabel="Recent" + tagVariant="shade" + style={itemStyle} + installed={isInstalled} + /> + ); + }); +} diff --git a/packages/appkit/src/views/w3m-connect-view/components/social-login-list.tsx b/packages/appkit/src/views/w3m-connect-view/components/social-login-list.tsx new file mode 100644 index 00000000..ea2740db --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/social-login-list.tsx @@ -0,0 +1,95 @@ +import { StyleSheet } from 'react-native'; +import { FlexView, ListSocial, LogoSelect, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { type SocialProvider, StringUtil } from '@reown/appkit-common-react-native'; +import { + ConnectionController, + EventsController, + RouterController, + WebviewController +} from '@reown/appkit-core-react-native'; + +export interface SocialLoginListProps { + options: readonly SocialProvider[]; + disabled?: boolean; +} + +const MAX_OPTIONS = 6; + +export function SocialLoginList({ options, disabled }: SocialLoginListProps) { + const showBigSocial = options?.length > 2 || options?.length === 1; + const showMoreButton = options?.length > MAX_OPTIONS; + const topSocial = showBigSocial ? options[0] : null; + let bottomSocials = showBigSocial ? options.slice(1) : options; + bottomSocials = showMoreButton ? bottomSocials.slice(0, MAX_OPTIONS - 2) : bottomSocials; + + const onItemPress = (provider: SocialProvider) => { + ConnectionController.setSelectedSocialProvider(provider); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_STARTED', + properties: { provider } + }); + WebviewController.setConnecting(false); + + if (provider === 'farcaster') { + RouterController.push('ConnectingFarcaster'); + } else { + RouterController.push('ConnectingSocial'); + } + }; + + const onMorePress = () => { + RouterController.push('ConnectSocials'); + }; + + return ( + + {topSocial && ( + onItemPress(topSocial)}> + + {`Continue with ${StringUtil.capitalize(topSocial)}`} + + + )} + + {bottomSocials?.map((social: SocialProvider, index) => ( + onItemPress(social)} + style={[ + styles.socialItem, + index === 0 && styles.socialItemFirst, + !showMoreButton && index === bottomSocials.length - 1 && styles.socialItemLast + ]} + /> + ))} + {showMoreButton && ( + + )} + + + ); +} + +const styles = StyleSheet.create({ + topDescription: { + textAlign: 'center' + }, + socialItem: { + flex: 1, + marginHorizontal: Spacing['2xs'] + }, + socialItemFirst: { + marginLeft: 0 + }, + socialItemLast: { + marginRight: 0 + } +}); diff --git a/packages/appkit/src/views/w3m-connect-view/components/wallet-guide.tsx b/packages/appkit/src/views/w3m-connect-view/components/wallet-guide.tsx new file mode 100644 index 00000000..92fdcc2d --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/components/wallet-guide.tsx @@ -0,0 +1,50 @@ +import { RouterController } from '@reown/appkit-core-react-native'; +import { Chip, FlexView, Link, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { Linking, StyleSheet } from 'react-native'; + +export interface WalletGuideProps { + guide: 'explore' | 'get-started'; +} + +export function WalletGuide({ guide }: WalletGuideProps) { + const onExplorerPress = () => { + Linking.openURL('https://explorer.walletconnect.com'); + }; + + const onGetStartedPress = () => { + RouterController.push('Create'); + }; + + return guide === 'explore' ? ( + + + + Looking for a self-custody wallet? + + + + ) : ( + + Haven't got a wallet? + + Get started + + + ); +} + +const styles = StyleSheet.create({ + text: { + marginBottom: Spacing.xs + }, + socialSeparator: { + marginVertical: Spacing.l + } +}); diff --git a/packages/appkit/src/views/w3m-connect-view/index.tsx b/packages/appkit/src/views/w3m-connect-view/index.tsx new file mode 100644 index 00000000..f1222cc8 --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/index.tsx @@ -0,0 +1,140 @@ +import { useSnapshot } from 'valtio'; +import { Platform, ScrollView, View } from 'react-native'; +import { + ApiController, + ConnectorController, + EventUtil, + EventsController, + OptionsController, + RouterController, + type WcWallet +} from '@reown/appkit-core-react-native'; +import { FlexView, Icon, ListItem, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { ConnectEmailInput } from './components/connect-email-input'; +import { useKeyboard } from '../../hooks/useKeyboard'; +import { Placeholder } from '../../partials/w3m-placeholder'; +import { ConnectorList } from './components/connectors-list'; +import { CustomWalletList } from './components/custom-wallet-list'; +import { AllWalletsButton } from './components/all-wallets-button'; +import { AllWalletList } from './components/all-wallet-list'; +import { RecentWalletList } from './components/recent-wallet-list'; +import { SocialLoginList } from './components/social-login-list'; +import { WalletGuide } from './components/wallet-guide'; +import styles from './styles'; + +export function ConnectView() { + const connectors = ConnectorController.state.connectors; + const { authLoading } = useSnapshot(ConnectorController.state); + const { prefetchError } = useSnapshot(ApiController.state); + const { features } = useSnapshot(OptionsController.state); + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + + const isWalletConnectEnabled = connectors.some(c => c.type === 'WALLET_CONNECT'); + const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); + const isCoinbaseEnabled = connectors.some(c => c.type === 'COINBASE'); + const isEmailEnabled = isAuthEnabled && features?.email; + const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; + const showConnectWalletsButton = + isWalletConnectEnabled && isAuthEnabled && !features?.emailShowWallets; + const showSeparator = + isAuthEnabled && + (isEmailEnabled || isSocialEnabled) && + (isWalletConnectEnabled || isCoinbaseEnabled); + const showLoadingError = !showConnectWalletsButton && prefetchError; + const showList = !showConnectWalletsButton && !showLoadingError; + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], + default: Spacing['2xl'] + }); + + const onWalletPress = (wallet: WcWallet, isInstalled?: boolean) => { + const connector = connectors.find(c => c.explorerId === wallet.id); + if (connector) { + RouterController.push('ConnectingExternal', { connector, wallet }); + } else { + RouterController.push('ConnectingWalletConnect', { wallet }); + } + + const platform = EventUtil.getWalletPlatform(wallet, isInstalled); + EventsController.sendEvent({ + type: 'track', + event: 'SELECT_WALLET', + properties: { + name: wallet.name ?? connector?.name ?? 'Unknown', + platform, + explorer_id: wallet.id + } + }); + }; + + const onViewAllPress = () => { + RouterController.push('AllWallets'); + EventsController.sendEvent({ type: 'track', event: 'CLICK_ALL_WALLETS' }); + }; + + return ( + + + {isEmailEnabled && } + {isSocialEnabled && } + {showSeparator && } + + + {showConnectWalletsButton && ( + + + Continue with a wallet + + + )} + {showLoadingError && ( + + + + + )} + {showList && ( + <> + + + + + + + )} + {isAuthEnabled && } + + + + ); +} diff --git a/packages/appkit/src/views/w3m-connect-view/styles.ts b/packages/appkit/src/views/w3m-connect-view/styles.ts new file mode 100644 index 00000000..819b007d --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/styles.ts @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + item: { + marginVertical: Spacing['3xs'] + }, + socialSeparator: { + marginVertical: Spacing.xs + }, + connectWalletButton: { + justifyContent: 'space-between' + }, + connectWalletEmpty: { + height: 20, + width: 20 + } +}); diff --git a/packages/appkit/src/views/w3m-connect-view/utils.ts b/packages/appkit/src/views/w3m-connect-view/utils.ts new file mode 100644 index 00000000..94811382 --- /dev/null +++ b/packages/appkit/src/views/w3m-connect-view/utils.ts @@ -0,0 +1,14 @@ +import type { WcWallet } from '@reown/appkit-core-react-native'; + +export const filterOutRecentWallets = ( + recentWallets?: WcWallet[], + wallets?: WcWallet[], + resentCount?: number +) => { + const recentIds = recentWallets?.slice(0, resentCount ?? 1).map(wallet => wallet.id); + if (!recentIds?.length) return wallets ?? []; + + const filtered = wallets?.filter(wallet => !recentIds.includes(wallet.id)) || []; + + return filtered; +}; diff --git a/packages/appkit/src/views/w3m-connecting-external-view/index.tsx b/packages/appkit/src/views/w3m-connecting-external-view/index.tsx new file mode 100644 index 00000000..e5df102e --- /dev/null +++ b/packages/appkit/src/views/w3m-connecting-external-view/index.tsx @@ -0,0 +1,131 @@ +import { useCallback, useEffect, useState } from 'react'; +import { ScrollView } from 'react-native'; +import { + RouterController, + ApiController, + AssetUtil, + ConnectionController, + ModalController, + EventsController, + StorageUtil, + type WcWallet +} from '@reown/appkit-core-react-native'; +import { + Button, + FlexView, + IconBox, + LoadingThumbnail, + WalletImage +} from '@reown/appkit-ui-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { ConnectingBody, getMessage, type BodyErrorType } from '../../partials/w3m-connecting-body'; +import styles from './styles'; + +export function ConnectingExternalView() { + const { data } = RouterController.state; + const connector = data?.connector; + const { maxWidth: width } = useCustomDimensions(); + const [errorType, setErrorType] = useState(); + const bodyMessage = getMessage({ walletName: data?.wallet?.name, errorType }); + + const onRetryPress = () => { + setErrorType(undefined); + onConnect(); + }; + + const storeConnectedWallet = useCallback( + async (wallet?: WcWallet) => { + if (wallet) { + const recentWallets = await StorageUtil.addRecentWallet(wallet); + if (recentWallets) { + ConnectionController.setRecentWallets(recentWallets); + } + } + if (connector) { + const url = AssetUtil.getConnectorImage(connector); + ConnectionController.setConnectedWalletImageUrl(url); + } + }, + [connector] + ); + + const onConnect = useCallback(async () => { + try { + if (connector) { + await ConnectionController.connectExternal(connector); + storeConnectedWallet(data?.wallet); + ModalController.close(); + EventsController.sendEvent({ + type: 'track', + event: 'CONNECT_SUCCESS', + properties: { + name: data.wallet?.name ?? 'Unknown', + method: 'mobile', + explorer_id: data.wallet?.id + } + }); + } + } catch (error) { + if (/(Wallet not found)/i.test((error as Error).message)) { + setErrorType('not_installed'); + } else if (/(rejected)/i.test((error as Error).message)) { + setErrorType('declined'); + } else { + setErrorType('default'); + } + EventsController.sendEvent({ + type: 'track', + event: 'CONNECT_ERROR', + properties: { message: (error as Error)?.message ?? 'Unknown' } + }); + } + }, [connector, storeConnectedWallet, data?.wallet]); + + useEffect(() => { + onConnect(); + }, [onConnect]); + + return ( + + + + + {errorType && ( + + )} + + + {errorType !== 'not_installed' && ( + + )} + + + ); +} diff --git a/packages/appkit/src/views/w3m-connecting-external-view/styles.ts b/packages/appkit/src/views/w3m-connecting-external-view/styles.ts new file mode 100644 index 00000000..ccc94c03 --- /dev/null +++ b/packages/appkit/src/views/w3m-connecting-external-view/styles.ts @@ -0,0 +1,20 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + paddingBottom: Spacing['3xl'] + }, + retryButton: { + marginTop: Spacing.m + }, + retryIcon: { + transform: [{ rotateY: '180deg' }] + }, + errorIcon: { + position: 'absolute', + bottom: 5, + right: 5, + zIndex: 2 + } +}); diff --git a/packages/appkit/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/appkit/src/views/w3m-connecting-farcaster-view/index.tsx new file mode 100644 index 00000000..fd7cb308 --- /dev/null +++ b/packages/appkit/src/views/w3m-connecting-farcaster-view/index.tsx @@ -0,0 +1,140 @@ +import { Linking } from 'react-native'; +import { useCallback, useEffect, useState } from 'react'; +import { + ConnectionController, + ConnectorController, + EventsController, + ModalController, + OptionsController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { + FlexView, + LoadingThumbnail, + IconBox, + Logo, + Text, + Link +} from '@reown/appkit-ui-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectingFarcasterView() { + const { maxWidth: width } = useCustomDimensions(); + const authConnector = ConnectorController.getAuthConnector(); + const [error, setError] = useState(false); + const [processing, setProcessing] = useState(false); + const [url, setUrl] = useState(); + const showCopy = OptionsController.isClipboardAvailable(); + const provider = authConnector?.provider as AppKitFrameProvider; + + const onConnect = useCallback(async () => { + try { + if (provider && authConnector) { + setError(false); + const { url: farcasterUrl } = await provider.getFarcasterUri(); + setUrl(farcasterUrl); + Linking.openURL(farcasterUrl); + + await provider.connectFarcaster(); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', + properties: { provider: 'farcaster' } + }); + setProcessing(true); + await ConnectionController.connectExternal(authConnector); + ConnectionController.setConnectedSocialProvider('farcaster'); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_SUCCESS', + properties: { provider: 'farcaster' } + }); + + setProcessing(false); + ModalController.close(); + } + } catch (e) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_ERROR', + properties: { provider: 'farcaster' } + }); + // TODO: remove this once Farcaster session refresh is implemented + // @ts-expect-error + provider?.webviewRef?.current?.reload(); + SnackController.showError('Something went wrong'); + setError(true); + setProcessing(false); + } + }, [provider, authConnector]); + + const onCopyUrl = () => { + if (url) { + OptionsController.copyToClipboard(url); + SnackController.showSuccess('Link copied'); + } + }; + + useEffect(() => { + return () => { + // TODO: remove this once Farcaster session refresh is implemented + if (!ModalController.state.open) { + // @ts-expect-error + provider.webviewRef?.current?.reload(); + } + }; + // @ts-expect-error + }, [provider.webviewRef]); + + useEffect(() => { + onConnect(); + }, [onConnect]); + + return ( + + <> + + + {error && ( + + )} + + + {processing ? 'Loading user data' : 'Continue in Farcaster'} + + + {processing + ? 'Please wait a moment while we load your data' + : 'Connect in the Farcaster app'} + + {showCopy && ( + + Copy link + + )} + + + ); +} diff --git a/packages/appkit/src/views/w3m-connecting-farcaster-view/styles.ts b/packages/appkit/src/views/w3m-connecting-farcaster-view/styles.ts new file mode 100644 index 00000000..542a8ad2 --- /dev/null +++ b/packages/appkit/src/views/w3m-connecting-farcaster-view/styles.ts @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + errorIcon: { + position: 'absolute', + bottom: 8, + right: 8, + zIndex: 2 + }, + continueText: { + marginTop: Spacing.m, + marginBottom: Spacing.xs + }, + copyButton: { + marginTop: Spacing.m + } +}); diff --git a/packages/appkit/src/views/w3m-connecting-social-view/index.tsx b/packages/appkit/src/views/w3m-connecting-social-view/index.tsx new file mode 100644 index 00000000..dc7b0aaa --- /dev/null +++ b/packages/appkit/src/views/w3m-connecting-social-view/index.tsx @@ -0,0 +1,153 @@ +import { useSnapshot } from 'valtio'; +import { useCallback, useEffect, useState } from 'react'; +import { Platform } from 'react-native'; +import { + ConnectionController, + ConnectorController, + EventsController, + ModalController, + RouterController, + SnackController, + WebviewController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { FlexView, LoadingThumbnail, IconBox, Logo, Text } from '@reown/appkit-ui-react-native'; +import { StringUtil } from '@reown/appkit-common-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectingSocialView() { + const { maxWidth: width } = useCustomDimensions(); + const { processingAuth } = useSnapshot(WebviewController.state); + const { selectedSocialProvider } = useSnapshot(ConnectionController.state); + const authConnector = ConnectorController.getAuthConnector(); + const [error, setError] = useState(false); + const provider = authConnector?.provider as AppKitFrameProvider; + + const onConnect = useCallback(async () => { + try { + if ( + !WebviewController.state.connecting && + provider && + ConnectionController.state.selectedSocialProvider + ) { + const { uri } = await provider.getSocialRedirectUri({ + provider: ConnectionController.state.selectedSocialProvider + }); + WebviewController.setWebviewUrl(uri); + + const isNativeApple = + ConnectionController.state.selectedSocialProvider === 'apple' && Platform.OS === 'ios'; + + WebviewController.setWebviewVisible(!isNativeApple); + WebviewController.setConnecting(true); + WebviewController.setConnectingProvider(ConnectionController.state.selectedSocialProvider); + } + } catch (e) { + WebviewController.setWebviewVisible(false); + WebviewController.setWebviewUrl(undefined); + WebviewController.setConnecting(false); + WebviewController.setConnectingProvider(undefined); + SnackController.showError('Something went wrong'); + setError(true); + } + }, [provider]); + + const socialMessageHandler = useCallback( + async (url: string) => { + try { + if ( + url.includes('/sdk/oauth') && + ConnectionController.state.selectedSocialProvider && + authConnector && + !WebviewController.state.processingAuth + ) { + WebviewController.setProcessingAuth(true); + WebviewController.setWebviewVisible(false); + const parsedUrl = new URL(url); + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', + properties: { provider: ConnectionController.state.selectedSocialProvider } + }); + + await provider?.connectSocial(parsedUrl.search); + await ConnectionController.connectExternal(authConnector); + ConnectionController.setConnectedSocialProvider( + ConnectionController.state.selectedSocialProvider + ); + WebviewController.setConnecting(false); + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_SUCCESS', + properties: { provider: ConnectionController.state.selectedSocialProvider } + }); + + ModalController.close(); + WebviewController.reset(); + } + } catch (e) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_ERROR', + properties: { provider: ConnectionController.state.selectedSocialProvider! } + }); + WebviewController.reset(); + RouterController.goBack(); + SnackController.showError('Something went wrong'); + } + }, + [authConnector, provider] + ); + + useEffect(() => { + onConnect(); + }, [onConnect]); + + useEffect(() => { + if (!provider) return; + + const unsubscribe = provider?.getEventEmitter().addListener('social', socialMessageHandler); + + return () => { + unsubscribe.removeListener('social', socialMessageHandler); + }; + }, [socialMessageHandler, provider]); + + return ( + + + + {error && ( + + )} + + + {processingAuth + ? 'Loading user data' + : `Continue with ${StringUtil.capitalize(selectedSocialProvider)}`} + + + {processingAuth + ? 'Please wait a moment while we load your data' + : 'Connect in the provider window'} + + + ); +} diff --git a/packages/appkit/src/views/w3m-connecting-social-view/styles.ts b/packages/appkit/src/views/w3m-connecting-social-view/styles.ts new file mode 100644 index 00000000..dcb555e8 --- /dev/null +++ b/packages/appkit/src/views/w3m-connecting-social-view/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + errorIcon: { + position: 'absolute', + bottom: 8, + right: 8, + zIndex: 2 + }, + continueText: { + marginTop: Spacing.m, + marginBottom: Spacing.xs + } +}); diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx new file mode 100644 index 00000000..85337016 --- /dev/null +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -0,0 +1,158 @@ +import { useSnapshot } from 'valtio'; +import { useEffect, useState } from 'react'; +import { + AccountController, + ConnectionController, + ConstantsUtil, + CoreHelperUtil, + ModalController, + RouterController, + SnackController, + type Platform, + OptionsController, + ApiController, + EventsController, + ConnectorController +} from '@reown/appkit-core-react-native'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; +import { useAppKit } from '@reown/appkit-react-native'; + +import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; +import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; +import { ConnectingWeb } from '../../partials/w3m-connecting-web'; +import { ConnectingHeader } from '../../partials/w3m-connecting-header'; +import { UiUtil } from '../../utils/UiUtil'; + +export function ConnectingView() { + const { appKit } = useAppKit(); + const { installed } = useSnapshot(ApiController.state); + const { data } = RouterController.state; + const [lastRetry, setLastRetry] = useState(Date.now()); + const isQr = !data?.wallet; + const isInstalled = !!installed?.find(wallet => wallet.id === data?.wallet?.id); + + const [platform, setPlatform] = useState(); + const [platforms, setPlatforms] = useState([]); + + const onRetry = () => { + if (CoreHelperUtil.isAllowedRetry(lastRetry)) { + setLastRetry(Date.now()); + ConnectionController.clearUri(); + initializeConnection(true); + } else { + SnackController.showError('Please wait a second before retrying'); + } + }; + + const initializeConnection = async (retry = false) => { + try { + const { wcPairingExpiry } = ConnectionController.state; + const { data: routeData } = RouterController.state; + if (retry || CoreHelperUtil.isPairingExpired(wcPairingExpiry)) { + ConnectionController.setWcError(false); + // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); + appKit?.connect('walletconnect', ['eip155']); + await ConnectionController.state.wcPromise; + ConnectorController.setConnectedConnector('WALLET_CONNECT'); + AccountController.setIsConnected(true); + + if (OptionsController.state.isSiweEnabled) { + if (SIWEController.state.status === 'success') { + ModalController.close(); + } else { + RouterController.push('ConnectingSiwe'); + } + } else { + ModalController.close(); + } + } + } catch (error) { + ConnectionController.setWcError(true); + ConnectionController.clearUri(); + SnackController.showError('Declined'); + if (isQr && CoreHelperUtil.isAllowedRetry(lastRetry)) { + setLastRetry(Date.now()); + initializeConnection(true); + } + EventsController.sendEvent({ + type: 'track', + event: 'CONNECT_ERROR', + properties: { + message: (error as Error)?.message ?? 'Unknown' + } + }); + } + }; + + const onCopyUri = (uri?: string) => { + if (OptionsController.isClipboardAvailable() && uri) { + OptionsController.copyToClipboard(uri); + SnackController.showSuccess('Link copied'); + } + }; + + const onSelectPlatform = (tab: Platform) => { + UiUtil.createViewTransition(); + setPlatform(tab); + }; + + const headerTemplate = () => { + if (isQr) return null; + + if (platforms.length > 1) { + return ; + } + + return null; + }; + + const platformTemplate = () => { + if (isQr) { + return ; + } + + switch (platform) { + case 'mobile': + return ( + + ); + case 'web': + return ; + default: + return undefined; + } + }; + + useEffect(() => { + const _platforms: Platform[] = []; + if (data?.wallet?.mobile_link) { + _platforms.push('mobile'); + } + if (data?.wallet?.webapp_link && !isInstalled) { + _platforms.push('web'); + } + + setPlatforms(_platforms); + setPlatform(_platforms[0]); + }, [data, isInstalled]); + + useEffect(() => { + initializeConnection(); + let _interval: NodeJS.Timeout; + + // Check if the pairing expired every 10 seconds. If expired, it will create a new uri. + if (isQr) { + _interval = setInterval(initializeConnection, ConstantsUtil.TEN_SEC_MS); + } + + return () => clearInterval(_interval); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isQr]); + + return ( + <> + {headerTemplate()} + {platformTemplate()} + + ); +} diff --git a/packages/appkit/src/views/w3m-create-view/index.tsx b/packages/appkit/src/views/w3m-create-view/index.tsx new file mode 100644 index 00000000..dcfa0965 --- /dev/null +++ b/packages/appkit/src/views/w3m-create-view/index.tsx @@ -0,0 +1,35 @@ +import { Platform, ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { FlexView, Spacing } from '@reown/appkit-ui-react-native'; +import { ConnectEmailInput } from '../w3m-connect-view/components/connect-email-input'; +import { SocialLoginList } from '../w3m-connect-view/components/social-login-list'; +import { WalletGuide } from '../w3m-connect-view/components/wallet-guide'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { ConnectorController, OptionsController } from '@reown/appkit-core-react-native'; +import { useKeyboard } from '../../hooks/useKeyboard'; + +export function CreateView() { + const connectors = ConnectorController.state.connectors; + const { authLoading } = useSnapshot(ConnectorController.state); + const { features } = useSnapshot(OptionsController.state); + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); + const isEmailEnabled = isAuthEnabled && features?.email; + const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing.xl : Spacing.xl, + default: Spacing.xl + }); + + return ( + + + {isEmailEnabled && } + {isSocialEnabled && } + {isAuthEnabled && } + + + ); +} diff --git a/packages/appkit/src/views/w3m-email-verify-device-view/index.tsx b/packages/appkit/src/views/w3m-email-verify-device-view/index.tsx new file mode 100644 index 00000000..510e75ea --- /dev/null +++ b/packages/appkit/src/views/w3m-email-verify-device-view/index.tsx @@ -0,0 +1,80 @@ +import { useSnapshot } from 'valtio'; +import { View } from 'react-native'; +import { useEffect, useState } from 'react'; +import { FlexView, Icon, Link, Text, useTheme } from '@reown/appkit-ui-react-native'; +import { + ConnectorController, + CoreHelperUtil, + EventsController, + RouterController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import useTimeout from '../../hooks/useTimeout'; +import styles from './styles'; + +export function EmailVerifyDeviceView() { + const Theme = useTheme(); + const { connectors } = useSnapshot(ConnectorController.state); + const { data } = RouterController.state; + const { timeLeft, startTimer } = useTimeout(0); + const [loading, setLoading] = useState(false); + const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; + + const listenForDeviceApproval = async () => { + if (authProvider && data?.email) { + try { + await authProvider.connectDevice(); + EventsController.sendEvent({ type: 'track', event: 'DEVICE_REGISTERED_FOR_EMAIL' }); + EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_SENT' }); + RouterController.replace('EmailVerifyOtp', { email: data.email }); + } catch (error: any) { + RouterController.goBack(); + } + } + }; + + const onResendEmail = async () => { + try { + if (!data?.email || !authProvider) return; + setLoading(true); + authProvider?.connectEmail({ email: data.email }); + listenForDeviceApproval(); + SnackController.showSuccess('Link resent'); + startTimer(30); + setLoading(false); + } catch (e) { + const parsedError = CoreHelperUtil.parseError(e); + SnackController.showError(parsedError); + } + }; + + useEffect(() => { + listenForDeviceApproval(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + + Register this device to continue + + + Check the instructions sent to{' '} + + {data?.email ?? 'your email'} + + The link expires in 20 minutes + + + Didn't receive it? + 0 || loading}> + {timeLeft > 0 ? `Resend in ${timeLeft}s` : 'Resend link'} + + + + ); +} diff --git a/packages/appkit/src/views/w3m-email-verify-device-view/styles.ts b/packages/appkit/src/views/w3m-email-verify-device-view/styles.ts new file mode 100644 index 00000000..9e5db141 --- /dev/null +++ b/packages/appkit/src/views/w3m-email-verify-device-view/styles.ts @@ -0,0 +1,19 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + iconContainer: { + height: 64, + width: 64, + justifyContent: 'center', + alignItems: 'center', + borderRadius: Spacing.xl, + marginBottom: Spacing['2xl'] + }, + headingText: { + marginBottom: Spacing.s + }, + expiryText: { + marginVertical: Spacing.l + } +}); diff --git a/packages/appkit/src/views/w3m-email-verify-otp-view/index.tsx b/packages/appkit/src/views/w3m-email-verify-otp-view/index.tsx new file mode 100644 index 00000000..4a7fbdf3 --- /dev/null +++ b/packages/appkit/src/views/w3m-email-verify-otp-view/index.tsx @@ -0,0 +1,75 @@ +import { useState } from 'react'; +import { + ConnectionController, + ConnectorController, + CoreHelperUtil, + EventsController, + ModalController, + RouterController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import useTimeout from '../../hooks/useTimeout'; +import { OtpCodeView } from '../../partials/w3m-otp-code'; + +export function EmailVerifyOtpView() { + const { timeLeft, startTimer } = useTimeout(0); + const { data } = RouterController.state; + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const authConnector = ConnectorController.getAuthConnector(); + + const onOtpResend = async () => { + try { + if (!data?.email || !authConnector) return; + setLoading(true); + const provider = authConnector?.provider as AppKitFrameProvider; + await provider.connectEmail({ email: data.email }); + SnackController.showSuccess('Code resent'); + startTimer(30); + setLoading(false); + } catch (e) { + const parsedError = CoreHelperUtil.parseError(e); + SnackController.showError(parsedError); + setLoading(false); + } + }; + + const onOtpSubmit = async (otp: string) => { + if (!authConnector) return; + setLoading(true); + setError(''); + try { + const provider = authConnector?.provider as AppKitFrameProvider; + await provider.connectOtp({ otp }); + EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_PASS' }); + await ConnectionController.connectExternal(authConnector); + ModalController.close(); + EventsController.sendEvent({ + type: 'track', + event: 'CONNECT_SUCCESS', + properties: { method: 'email', name: authConnector.name || 'Unknown' } + }); + } catch (e) { + EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_FAIL' }); + const parsedError = CoreHelperUtil.parseError(e); + if (parsedError?.includes('Invalid code')) { + setError('Invalid code. Try again.'); + } else { + SnackController.showError(parsedError); + } + } + setLoading(false); + }; + + return ( + + ); +} diff --git a/packages/appkit/src/views/w3m-get-wallet-view/index.tsx b/packages/appkit/src/views/w3m-get-wallet-view/index.tsx new file mode 100644 index 00000000..3cc7478a --- /dev/null +++ b/packages/appkit/src/views/w3m-get-wallet-view/index.tsx @@ -0,0 +1,50 @@ +import { Linking, Platform, ScrollView } from 'react-native'; +import { FlexView, ListWallet } from '@reown/appkit-ui-react-native'; +import { ApiController, AssetUtil, type WcWallet } from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function GetWalletView() { + const { padding } = useCustomDimensions(); + const imageHeaders = ApiController._getApiHeaders(); + + const onWalletPress = (wallet: WcWallet) => { + const storeUrl = + Platform.select({ ios: wallet.app_store, android: wallet.play_store }) || wallet.homepage; + if (storeUrl) { + Linking.openURL(storeUrl); + } + }; + + const listTemplate = () => { + return ApiController.state.recommended.map(wallet => ( + onWalletPress(wallet)} + style={styles.item} + /> + )); + }; + + return ( + + + {listTemplate()} + Linking.openURL('https://walletconnect.com/explorer?type=wallet')} + /> + + + ); +} diff --git a/packages/appkit/src/views/w3m-get-wallet-view/styles.ts b/packages/appkit/src/views/w3m-get-wallet-view/styles.ts new file mode 100644 index 00000000..9ce25737 --- /dev/null +++ b/packages/appkit/src/views/w3m-get-wallet-view/styles.ts @@ -0,0 +1,8 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + item: { + marginBottom: Spacing.xs + } +}); diff --git a/packages/appkit/src/views/w3m-network-switch-view/index.tsx b/packages/appkit/src/views/w3m-network-switch-view/index.tsx new file mode 100644 index 00000000..8adf0455 --- /dev/null +++ b/packages/appkit/src/views/w3m-network-switch-view/index.tsx @@ -0,0 +1,138 @@ +/* eslint-disable valtio/state-snapshot-rule */ +import { useSnapshot } from 'valtio'; +import { useEffect, useState } from 'react'; +import { + ApiController, + AssetUtil, + ConnectionController, + ConnectorController, + EventsController, + NetworkController, + RouterController, + RouterUtil +} from '@reown/appkit-core-react-native'; +import { + Button, + FlexView, + IconBox, + LoadingHexagon, + NetworkImage, + Text +} from '@reown/appkit-ui-react-native'; +import styles from './styles'; + +export function NetworkSwitchView() { + const { data } = useSnapshot(RouterController.state); + const { recentWallets } = useSnapshot(ConnectionController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH'; + const [error, setError] = useState(false); + const [showRetry, setShowRetry] = useState(false); + const network = data?.network!; + const wallet = recentWallets?.[0]; + + const onSwitchNetwork = async () => { + try { + setError(false); + await NetworkController.switchActiveNetwork(network); + EventsController.sendEvent({ + type: 'track', + event: 'SWITCH_NETWORK', + properties: { + network: network.id + } + }); + } catch { + setError(true); + setShowRetry(true); + } + }; + + useEffect(() => { + onSwitchNetwork(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // Go back if network is already switched + if (caipNetwork?.id === network?.id) { + RouterUtil.navigateAfterNetworkSwitch(); + } + }, [caipNetwork?.id, network?.id]); + + const retryTemplate = () => { + if (!showRetry) return null; + + return ( + + ); + }; + + const textTemplate = () => { + const walletName = wallet?.name ?? 'wallet'; + if (error) { + return ( + <> + + Switch declined + + + Switch can be declined if chain is not supported by a wallet or previous request is + still active + + + ); + } + + if (isAuthConnected) { + return ( + + Switching to {network.name} network + + ); + } + + return ( + <> + {`Approve in ${walletName}`} + + Accept switch request in your wallet + + + ); + }; + + return ( + + + + {error && ( + + )} + + {textTemplate()} + {retryTemplate()} + + ); +} diff --git a/packages/appkit/src/views/w3m-network-switch-view/styles.ts b/packages/appkit/src/views/w3m-network-switch-view/styles.ts new file mode 100644 index 00000000..924d6704 --- /dev/null +++ b/packages/appkit/src/views/w3m-network-switch-view/styles.ts @@ -0,0 +1,23 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + descriptionText: { + marginHorizontal: Spacing['3xl'] + }, + errorIcon: { + position: 'absolute', + bottom: 12, + right: 20, + zIndex: 2 + }, + retryButton: { + marginTop: Spacing.xl + }, + retryIcon: { + transform: [{ rotateY: '180deg' }] + }, + text: { + marginVertical: Spacing.xs + } +}); diff --git a/packages/appkit/src/views/w3m-networks-view/index.tsx b/packages/appkit/src/views/w3m-networks-view/index.tsx new file mode 100644 index 00000000..30bdd029 --- /dev/null +++ b/packages/appkit/src/views/w3m-networks-view/index.tsx @@ -0,0 +1,111 @@ +import { ScrollView, View } from 'react-native'; +import { + CardSelect, + CardSelectWidth, + FlexView, + Link, + Separator, + Spacing, + Text +} from '@reown/appkit-ui-react-native'; +import { + ApiController, + AssetUtil, + NetworkController, + RouterController, + type CaipNetwork, + EventsController, + CoreHelperUtil, + NetworkUtil +} from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function NetworksView() { + const { caipNetwork, requestedCaipNetworks, approvedCaipNetworkIds, supportsAllNetworks } = + NetworkController.state; + const imageHeaders = ApiController._getApiHeaders(); + const { maxWidth: width, padding } = useCustomDimensions(); + const numColumns = 4; + const usableWidth = width - Spacing.xs * 2 - Spacing['4xs']; + const itemWidth = Math.abs(Math.trunc(usableWidth / numColumns)); + const itemGap = Math.abs( + Math.trunc((usableWidth - numColumns * CardSelectWidth) / numColumns) / 2 + ); + + const onHelpPress = () => { + RouterController.push('WhatIsANetwork'); + EventsController.sendEvent({ type: 'track', event: 'CLICK_NETWORK_HELP' }); + }; + + const networksTemplate = () => { + const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); + + const onNetworkPress = async (network: CaipNetwork) => { + const result = await NetworkUtil.handleNetworkSwitch(network); + if (result?.type === 'SWITCH_NETWORK') { + EventsController.sendEvent({ + type: 'track', + event: 'SWITCH_NETWORK', + properties: { + network: network.id + } + }); + } + }; + + return networks.map(network => ( + + onNetworkPress(network)} + /> + + )); + }; + + return ( + <> + + + {networksTemplate()} + + + + + + Your connected wallet may not support some of the networks available for this dApp + + + What is a network? + + + + ); +} diff --git a/packages/appkit/src/views/w3m-networks-view/styles.ts b/packages/appkit/src/views/w3m-networks-view/styles.ts new file mode 100644 index 00000000..aa4204c1 --- /dev/null +++ b/packages/appkit/src/views/w3m-networks-view/styles.ts @@ -0,0 +1,12 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + itemContainer: { + alignItems: 'center', + justifyContent: 'center' + }, + helpButton: { + marginTop: Spacing.s + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx b/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx new file mode 100644 index 00000000..a3085027 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx @@ -0,0 +1,266 @@ +import { + AssetUtil, + NetworkController, + OnRampController, + RouterController, + ThemeController +} from '@reown/appkit-core-react-native'; +import { + BorderRadius, + Button, + FlexView, + Image, + Separator, + Spacing, + Text, + Toggle, + useTheme +} from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { NumberUtil, StringUtil } from '@reown/appkit-common-react-native'; + +export function OnRampCheckoutView() { + const Theme = useTheme(); + const { themeMode } = useSnapshot(ThemeController.state); + const { selectedQuote, selectedPaymentMethod, purchaseCurrency } = useSnapshot( + OnRampController.state + ); + + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const value = NumberUtil.roundNumber(selectedQuote?.destinationAmount ?? 0, 6, 5); + const symbol = selectedQuote?.destinationCurrencyCode; + const paymentLogo = selectedPaymentMethod?.logos[themeMode ?? 'light']; + const providerImage = OnRampController.getServiceProviderImage( + selectedQuote?.serviceProvider ?? '' + ); + + const showNetworkFee = selectedQuote?.networkFee != null; + const showTransactionFee = selectedQuote?.transactionFee != null; + const showTotalFee = selectedQuote?.totalFee != null; + const showFees = showNetworkFee || showTransactionFee || showTotalFee; + + const onConfirm = () => { + RouterController.push('OnRampLoading'); + }; + + return ( + + + You Buy + + {value} + + {symbol?.split('_')[0] ?? symbol ?? ''} + + + + via + {providerImage && } + {StringUtil.capitalize(selectedQuote?.serviceProvider)} + + + + + You Pay + + {selectedQuote?.sourceAmount} {selectedQuote?.sourceCurrencyCode} + + + + You Receive + + + {value} {symbol?.split('_')[0] ?? ''} + + {purchaseCurrency?.symbolImageUrl && ( + + )} + + + + Network + + {purchaseCurrency?.chainName} + + + + Pay with + + {paymentLogo && ( + + )} + + {selectedPaymentMethod?.name} + + + + + {showFees && ( + + + Fees{' '} + {showTotalFee && ( + + {selectedQuote?.totalFee} {selectedQuote?.sourceCurrencyCode} + + )} + + + } + style={[styles.feesToggle, { backgroundColor: Theme['gray-glass-002'] }]} + contentContainerStyle={styles.feesToggleContent} + > + {showNetworkFee && ( + + + Network Fees + + + {networkImage && ( + + )} + + {selectedQuote?.networkFee} {selectedQuote?.sourceCurrencyCode} + + + + )} + {showTransactionFee && ( + + + Transaction Fees + + + {selectedQuote.transactionFee} {selectedQuote?.sourceCurrencyCode} + + + )} + + )} + + + + + + ); +} + +const styles = StyleSheet.create({ + amount: { + fontSize: 38, + marginRight: Spacing['3xs'] + }, + separator: { + marginVertical: Spacing.m + }, + feesToggle: { + borderRadius: BorderRadius.xs + }, + feesToggleContent: { + paddingHorizontal: Spacing.xs, + paddingBottom: Spacing.xs + }, + toggleItem: { + padding: Spacing.s, + borderRadius: BorderRadius.xxs + }, + paymentMethodImage: { + width: 14, + height: 14, + marginRight: Spacing['3xs'] + }, + confirmButton: { + marginLeft: Spacing.s, + flex: 3 + }, + cancelButton: { + flex: 1 + }, + providerImage: { + height: 16, + width: 16, + marginRight: 2 + }, + tokenImage: { + height: 20, + width: 20, + marginLeft: 4, + borderRadius: BorderRadius.full, + borderWidth: 1 + }, + networkImage: { + height: 16, + width: 16, + marginRight: 4, + borderRadius: BorderRadius.full, + borderWidth: 1 + }, + paymentMethodContainer: { + borderWidth: StyleSheet.hairlineWidth, + borderRadius: BorderRadius.full, + padding: Spacing.xs + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-loading-view/index.tsx b/packages/appkit/src/views/w3m-onramp-loading-view/index.tsx new file mode 100644 index 00000000..f2351aef --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-loading-view/index.tsx @@ -0,0 +1,157 @@ +import { useCallback, useEffect } from 'react'; +import { useSnapshot } from 'valtio'; +import { Linking, ScrollView } from 'react-native'; +import { + RouterController, + OnRampController, + OptionsController, + EventsController +} from '@reown/appkit-core-react-native'; +import { FlexView, DoubleImageLoader, IconLink, Button, Text } from '@reown/appkit-ui-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { ConnectingBody } from '../../partials/w3m-connecting-body'; +import styles from './styles'; +import { StringUtil } from '@reown/appkit-common-react-native'; + +export function OnRampLoadingView() { + const { maxWidth: width } = useCustomDimensions(); + const { error } = useSnapshot(OnRampController.state); + const providerName = StringUtil.capitalize( + OnRampController.state.selectedQuote?.serviceProvider.toLowerCase() + ); + + const serviceProvideLogo = OnRampController.getServiceProviderImage( + OnRampController.state.selectedQuote?.serviceProvider ?? '' + ); + + const handleGoBack = () => { + if (EventsController.state.data.event === 'BUY_SUBMITTED') { + // Send event only if the onramp url was already created + EventsController.sendEvent({ + type: 'track', + event: 'BUY_CANCEL' + }); + } + + RouterController.goBack(); + }; + + const onConnect = useCallback(async () => { + if (OnRampController.state.selectedQuote) { + OnRampController.clearError(); + const response = await OnRampController.generateWidget({ + quote: OnRampController.state.selectedQuote + }); + if (response?.widgetUrl) { + Linking.openURL(response?.widgetUrl); + } + } + }, []); + + useEffect(() => { + const unsubscribe = Linking.addEventListener('url', ({ url }) => { + const metadata = OptionsController.state.metadata; + + if ( + (metadata?.redirect?.universal && url.startsWith(metadata?.redirect?.universal)) || + (metadata?.redirect?.native && url.startsWith(metadata?.redirect?.native)) + ) { + const parsedUrl = new URL(url); + const searchParams = new URLSearchParams(parsedUrl.search); + const asset = searchParams.get('cryptoCurrency'); + const network = searchParams.get('network'); + const purchaseAmount = searchParams.get('cryptoAmount'); + const amount = searchParams.get('fiatAmount'); + const currency = searchParams.get('fiatCurrency'); + const orderId = searchParams.get('orderId'); + const status = searchParams.get('status'); + + EventsController.sendEvent({ + type: 'track', + event: 'BUY_SUCCESS', + properties: { + asset, + network, + amount, + currency, + orderId + } + }); + + RouterController.reset('OnRampTransaction', { + onrampResult: { + purchaseCurrency: asset, + purchaseAmount, + purchaseImageUrl: OnRampController.state.purchaseCurrency?.symbolImageUrl ?? '', + paymentCurrency: currency, + paymentAmount: amount, + network: network, + status: status + } + }); + } + }); + + return () => unsubscribe.remove(); + }, []); + + useEffect(() => { + onConnect(); + }, [onConnect]); + + return ( + + + + + {error ? ( + + + There was an error while connecting with {providerName} + + + + ) : ( + + )} + + + ); +} diff --git a/packages/appkit/src/views/w3m-onramp-loading-view/styles.ts b/packages/appkit/src/views/w3m-onramp-loading-view/styles.ts new file mode 100644 index 00000000..b4f0bab9 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-loading-view/styles.ts @@ -0,0 +1,23 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + paddingBottom: Spacing['3xl'] + }, + backButton: { + alignSelf: 'flex-start' + }, + imageContainer: { + marginBottom: Spacing.s + }, + retryButton: { + marginTop: Spacing.m + }, + retryIcon: { + transform: [{ rotateY: '180deg' }] + }, + errorText: { + marginHorizontal: Spacing['4xl'] + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-settings-view/components/Country.tsx b/packages/appkit/src/views/w3m-onramp-settings-view/components/Country.tsx new file mode 100644 index 00000000..c5a3c3e6 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-settings-view/components/Country.tsx @@ -0,0 +1,65 @@ +import type { OnRampCountry } from '@reown/appkit-core-react-native'; +import { + Pressable, + FlexView, + Spacing, + Text, + Icon, + BorderRadius +} from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; +import { SvgUri } from 'react-native-svg'; + +interface Props { + onPress: (item: OnRampCountry) => void; + item: OnRampCountry; + selected: boolean; +} + +export const ITEM_HEIGHT = 60; + +export function Country({ onPress, item, selected }: Props) { + const handlePress = () => { + onPress(item); + }; + + return ( + + + + {item.flagImageUrl && SvgUri && } + + + + {item.name} + + + {item.countryCode} + + + {selected && ( + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + borderRadius: BorderRadius.s, + height: ITEM_HEIGHT, + justifyContent: 'center' + }, + imageContainer: { + borderRadius: BorderRadius.full, + overflow: 'hidden', + marginRight: Spacing.xs + }, + textContainer: { + flex: 1 + }, + checkmark: { + marginRight: Spacing['2xs'] + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-settings-view/index.tsx b/packages/appkit/src/views/w3m-onramp-settings-view/index.tsx new file mode 100644 index 00000000..1f2063bd --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-settings-view/index.tsx @@ -0,0 +1,145 @@ +import { useSnapshot } from 'valtio'; +import { memo, useState } from 'react'; +import { SvgUri } from 'react-native-svg'; +import { FlexView, ListItem, Text, useTheme, Icon, Image } from '@reown/appkit-ui-react-native'; +import { + OnRampController, + type OnRampCountry, + type OnRampFiatCurrency +} from '@reown/appkit-core-react-native'; + +import { SelectorModal } from '../../partials/w3m-selector-modal'; +import { Country } from './components/Country'; +import { Currency } from '../w3m-onramp-view/components/Currency'; +import { + getModalTitle, + getItemHeight, + getModalItems, + getModalItemKey, + getModalSearchPlaceholder +} from './utils'; +import { styles } from './styles'; + +type ModalType = 'country' | 'paymentCurrency'; + +const MemoizedCountry = memo(Country); +const MemoizedCurrency = memo(Currency); + +export function OnRampSettingsView() { + const { paymentCurrency, selectedCountry } = useSnapshot(OnRampController.state); + const Theme = useTheme(); + const [modalType, setModalType] = useState(); + const [searchValue, setSearchValue] = useState(''); + + const onCountryPress = () => { + setModalType('country'); + }; + + const onPaymentCurrencyPress = () => { + setModalType('paymentCurrency'); + }; + + const onPressModalItem = async (item: any) => { + setModalType(undefined); + setSearchValue(''); + if (modalType === 'country') { + await OnRampController.setSelectedCountry(item as OnRampCountry); + } else if (modalType === 'paymentCurrency') { + OnRampController.setPaymentCurrency(item as OnRampFiatCurrency); + } + }; + + const renderModalItem = ({ item }: { item: any }) => { + if (modalType === 'country') { + const parsedItem = item as OnRampCountry; + + return ( + + ); + } + + const parsedItem = item as OnRampFiatCurrency; + + return ( + + ); + }; + + return ( + <> + + + + + {selectedCountry?.flagImageUrl && SvgUri ? ( + + ) : undefined} + + + + Select Country + {selectedCountry?.name && ( + + {selectedCountry?.name} + + )} + + + + + + {paymentCurrency?.symbolImageUrl ? ( + + ) : ( + + )} + + + + Select Currency + {paymentCurrency?.name && ( + + {paymentCurrency?.name} + + )} + + + + setModalType(undefined)} + items={getModalItems(modalType, searchValue, true)} + selectedItem={modalType === 'country' ? selectedCountry : paymentCurrency} + onSearch={setSearchValue} + renderItem={renderModalItem} + keyExtractor={(item: any, index: number) => getModalItemKey(modalType, index, item)} + title={getModalTitle(modalType)} + itemHeight={getItemHeight(modalType)} + searchPlaceholder={getModalSearchPlaceholder(modalType)} + /> + + ); +} diff --git a/packages/appkit/src/views/w3m-onramp-settings-view/styles.ts b/packages/appkit/src/views/w3m-onramp-settings-view/styles.ts new file mode 100644 index 00000000..8d0a6d4a --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-settings-view/styles.ts @@ -0,0 +1,25 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + itemContent: { + paddingLeft: 0 + }, + firstItem: { + marginBottom: Spacing.xs + }, + image: { + height: 20, + width: 20 + }, + imageContainer: { + borderRadius: BorderRadius.full, + height: 36, + width: 36, + marginRight: Spacing.s + }, + imageBorder: { + borderRadius: BorderRadius.full, + overflow: 'hidden' + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-settings-view/utils.ts b/packages/appkit/src/views/w3m-onramp-settings-view/utils.ts new file mode 100644 index 00000000..4106dd28 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-settings-view/utils.ts @@ -0,0 +1,90 @@ +import { ITEM_HEIGHT as COUNTRY_ITEM_HEIGHT } from './components/Country'; +import { ITEM_HEIGHT as CURRENCY_ITEM_HEIGHT } from '../w3m-onramp-view/components/Currency'; +import { + OnRampController, + type OnRampCountry, + type OnRampFiatCurrency +} from '@reown/appkit-core-react-native'; + +// -------------------------- Types -------------------------- +type ModalType = 'country' | 'paymentCurrency'; + +// -------------------------- Constants -------------------------- +const MODAL_TITLES: Record = { + country: 'Select Country', + paymentCurrency: 'Select Currency' +}; + +const MODAL_SEARCH_PLACEHOLDERS: Record = { + country: 'Search country', + paymentCurrency: 'Search currency' +}; + +const ITEM_HEIGHTS: Record = { + country: COUNTRY_ITEM_HEIGHT, + paymentCurrency: CURRENCY_ITEM_HEIGHT +}; + +const KEY_EXTRACTORS: Record string> = { + country: (item: OnRampCountry) => item.countryCode, + paymentCurrency: (item: OnRampFiatCurrency) => item.currencyCode +}; + +// -------------------------- Utils -------------------------- +export const getItemHeight = (type?: ModalType) => { + return type ? ITEM_HEIGHTS[type] : 0; +}; + +export const getModalTitle = (type?: ModalType) => { + return type ? MODAL_TITLES[type] : undefined; +}; + +export const getModalSearchPlaceholder = (type?: ModalType) => { + return type ? MODAL_SEARCH_PLACEHOLDERS[type] : undefined; +}; + +const searchFilter = ( + item: { name: string; currencyCode?: string; countryCode?: string }, + searchValue: string +) => { + const search = searchValue.toLowerCase(); + + return ( + item.name.toLowerCase().includes(search) || + (item.currencyCode?.toLowerCase().includes(search) ?? false) || + (item.countryCode?.toLowerCase().includes(search) ?? false) + ); +}; + +export const getModalItemKey = (type: ModalType | undefined, index: number, item: any) => { + return type ? KEY_EXTRACTORS[type](item) : index.toString(); +}; + +export const getModalItems = ( + type?: Exclude, + searchValue?: string, + filterSelected?: boolean +) => { + const items = { + country: () => + filterSelected + ? OnRampController.state.countries.filter( + c => c.countryCode !== OnRampController.state.selectedCountry?.countryCode + ) + : OnRampController.state.countries, + paymentCurrency: () => + filterSelected + ? OnRampController.state.paymentCurrencies?.filter( + pc => pc.currencyCode !== OnRampController.state.paymentCurrency?.currencyCode + ) + : OnRampController.state.paymentCurrencies + }; + + const result = items[type!]?.() || []; + + return searchValue + ? result.filter((item: { name: string; currencyCode?: string }) => + searchFilter(item, searchValue) + ) + : result; +}; diff --git a/packages/appkit/src/views/w3m-onramp-transaction-view/index.tsx b/packages/appkit/src/views/w3m-onramp-transaction-view/index.tsx new file mode 100644 index 00000000..45e6d4f8 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-transaction-view/index.tsx @@ -0,0 +1,120 @@ +import { useSnapshot } from 'valtio'; +import { useEffect } from 'react'; +import { + AccountController, + ConnectorController, + OnRampController, + RouterController +} from '@reown/appkit-core-react-native'; +import { StringUtil } from '@reown/appkit-common-react-native'; +import { Button, FlexView, IconBox, Image, Text, useTheme } from '@reown/appkit-ui-react-native'; +import styles from './styles'; + +export function OnRampTransactionView() { + const Theme = useTheme(); + const { data } = useSnapshot(RouterController.state); + + const onClose = () => { + const isAuth = ConnectorController.state.connectedConnector === 'AUTH'; + RouterController.replace(isAuth ? 'Account' : 'AccountDefault'); + }; + + const showNetwork = !!data?.onrampResult?.network; + const showStatus = !!data?.onrampResult?.status; + + useEffect(() => { + return () => { + OnRampController.resetState(); + AccountController.fetchTokenBalance(); + }; + }, []); + + return ( + + + + + + You successfully bought {data?.onrampResult?.purchaseCurrency} + + + + + + You Paid + + + {data?.onrampResult?.paymentAmount} {data?.onrampResult?.paymentCurrency} + + + + + You Bought + + + + {data?.onrampResult?.purchaseAmount}{' '} + {data?.onrampResult?.purchaseCurrency?.split('_')[0] ?? ''} + + {data?.onrampResult?.purchaseImageUrl && ( + + )} + + + {showNetwork && ( + + + Network + + + {StringUtil.capitalize(data?.onrampResult?.network)} + + + )} + {showStatus && ( + + + Status + + + {StringUtil.capitalize(data?.onrampResult?.status)} + + + )} + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-onramp-transaction-view/styles.ts b/packages/appkit/src/views/w3m-onramp-transaction-view/styles.ts new file mode 100644 index 00000000..2e73f68a --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-transaction-view/styles.ts @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + icon: { + marginBottom: Spacing.m + }, + card: { + borderRadius: BorderRadius.s + }, + tokenImage: { + height: 16, + width: 16, + marginLeft: 4, + borderRadius: BorderRadius.full, + borderWidth: 1 + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/components/Currency.tsx b/packages/appkit/src/views/w3m-onramp-view/components/Currency.tsx new file mode 100644 index 00000000..9492dfa3 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/components/Currency.tsx @@ -0,0 +1,86 @@ +import { + type OnRampFiatCurrency, + type OnRampCryptoCurrency +} from '@reown/appkit-core-react-native'; +import { + Pressable, + FlexView, + Spacing, + Text, + useTheme, + Icon, + Image, + BorderRadius +} from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export const ITEM_HEIGHT = 60; + +interface Props { + onPress: (item: OnRampFiatCurrency | OnRampCryptoCurrency) => void; + item: OnRampFiatCurrency | OnRampCryptoCurrency; + selected: boolean; + title: string; + subtitle: string; + testID?: string; +} + +export function Currency({ onPress, item, selected, title, subtitle, testID }: Props) { + const Theme = useTheme(); + + const handlePress = () => { + onPress(item); + }; + + return ( + + + + + + + {title} + + + {subtitle} + + + + {selected && ( + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + height: ITEM_HEIGHT, + borderRadius: BorderRadius.s + }, + logo: { + width: 36, + height: 36, + borderRadius: BorderRadius.full, + marginRight: Spacing.xs + }, + checkmark: { + marginRight: Spacing['2xs'] + }, + selected: { + borderWidth: 1 + }, + text: { + flex: 1 + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/components/CurrencyInput.tsx b/packages/appkit/src/views/w3m-onramp-view/components/CurrencyInput.tsx new file mode 100644 index 00000000..7fe03cf3 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/components/CurrencyInput.tsx @@ -0,0 +1,169 @@ +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { + Button, + FlexView, + useTheme, + Text, + LoadingSpinner, + NumericKeyboard, + Separator, + Spacing, + BorderRadius +} from '@reown/appkit-ui-react-native'; +import { useEffect, useState } from 'react'; +import { useRef } from 'react'; + +export interface InputTokenProps { + style?: StyleProp; + value?: string; + symbol?: string; + loading?: boolean; + error?: string; + isAmountError?: boolean; + purchaseValue?: string; + onValueChange?: (value: number) => void; + onSuggestedValuePress?: (value: number) => void; + suggestedValues?: number[]; +} + +export function CurrencyInput({ + value, + loading, + error, + isAmountError, + purchaseValue, + onValueChange, + onSuggestedValuePress, + symbol, + style, + suggestedValues +}: InputTokenProps) { + const Theme = useTheme(); + const [displayValue, setDisplayValue] = useState(value?.toString() || '0'); + const isInternalChange = useRef(false); + const amountColor = isAmountError ? 'error-100' : value ? 'fg-100' : 'fg-200'; + + const handleKeyPress = (key: string) => { + isInternalChange.current = true; + + if (key === 'erase') { + setDisplayValue(prev => { + const newDisplay = prev.slice(0, -1) || '0'; + + // If the previous value does not end with a comma, convert to numeric value + if (!prev?.endsWith(',')) { + const numericValue = Number(newDisplay.replace(',', '.')); + onValueChange?.(numericValue); + } + + return newDisplay; + }); + } else if (key === ',') { + setDisplayValue(prev => { + if (prev.includes(',')) return prev; // Don't add multiple commas + const newDisplay = prev + ','; + + return newDisplay; + }); + } else { + setDisplayValue(prev => { + const newDisplay = prev === '0' ? key : prev + key; + + // Convert to numeric value + const numericValue = Number(newDisplay.replace(',', '.')); + onValueChange?.(numericValue); + + return newDisplay; + }); + } + }; + + useEffect(() => { + // Handle external value changes + if (!isInternalChange.current && value !== undefined) { + setDisplayValue(value.toString()); + } + isInternalChange.current = false; + }, [value]); + + return ( + + + + {displayValue} + + {symbol || ''} + + + + {loading ? ( + + ) : error ? ( + + {error} + + ) : ( + + {purchaseValue} + + )} + + + + {suggestedValues?.map((suggestion: number) => { + const isSelected = suggestion.toString() === value; + + return ( + + ); + })} + + + + + ); +} + +const styles = StyleSheet.create({ + input: { + fontSize: 38, + marginRight: Spacing['3xs'] + }, + bottomContainer: { + height: 20 + }, + separator: { + marginTop: 16 + }, + suggestedValue: { + flex: 1, + borderRadius: BorderRadius.xxs, + marginRight: Spacing.xs, + height: 40 + }, + selectedValue: { + borderWidth: StyleSheet.hairlineWidth + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/components/Header.tsx b/packages/appkit/src/views/w3m-onramp-view/components/Header.tsx new file mode 100644 index 00000000..064c91a6 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/components/Header.tsx @@ -0,0 +1,47 @@ +import { StyleSheet } from 'react-native'; +import { ModalController, RouterController } from '@reown/appkit-core-react-native'; +import { IconLink, Text } from '@reown/appkit-ui-react-native'; +import { FlexView } from '@reown/appkit-ui-react-native'; + +interface HeaderProps { + onSettingsPress: () => void; +} + +export function Header({ onSettingsPress }: HeaderProps) { + const handleGoBack = () => { + if (RouterController.state.history.length > 1) { + RouterController.goBack(); + } else { + ModalController.close(); + } + }; + + return ( + + + + Buy crypto + + + + ); +} + +const styles = StyleSheet.create({ + icon: { + height: 40, + width: 40 + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/components/LoadingView.tsx b/packages/appkit/src/views/w3m-onramp-view/components/LoadingView.tsx new file mode 100644 index 00000000..3260f959 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/components/LoadingView.tsx @@ -0,0 +1,43 @@ +import { FlexView, Text, Shimmer } from '@reown/appkit-ui-react-native'; +import { Dimensions, ScrollView } from 'react-native'; +import { Header } from './Header'; +import styles from '../styles'; + +export function LoadingView() { + const windowWidth = Dimensions.get('window').width; + + return ( + <> +
{}} /> + + + + + You Buy + + + + + {/* Currency Input Area */} + + + + + {/* Payment Method Button */} + + + {/* Action Buttons */} + + + + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-onramp-view/components/PaymentMethod.tsx b/packages/appkit/src/views/w3m-onramp-view/components/PaymentMethod.tsx new file mode 100644 index 00000000..1996246e --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/components/PaymentMethod.tsx @@ -0,0 +1,97 @@ +import { useSnapshot } from 'valtio'; +import { ThemeController, type OnRampPaymentMethod } from '@reown/appkit-core-react-native'; +import { + Pressable, + FlexView, + Spacing, + Text, + useTheme, + Image, + BorderRadius, + IconBox +} from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export const ITEM_SIZE = 85; + +interface Props { + onPress: (item: OnRampPaymentMethod) => void; + item: OnRampPaymentMethod; + selected: boolean; + testID?: string; +} + +export function PaymentMethod({ onPress, item, selected, testID }: Props) { + const Theme = useTheme(); + const { themeMode } = useSnapshot(ThemeController.state); + + const handlePress = () => { + onPress(item); + }; + + return ( + + + + {selected && ( + + )} + + + {item.name} + + + ); +} + +const styles = StyleSheet.create({ + container: { + height: ITEM_SIZE, + width: ITEM_SIZE, + justifyContent: 'center', + alignItems: 'center' + }, + logoContainer: { + width: 60, + height: 60, + borderRadius: BorderRadius.full, + marginBottom: Spacing['4xs'] + }, + logo: { + width: 22, + height: 22 + }, + checkmark: { + borderRadius: BorderRadius.full, + position: 'absolute', + bottom: 0, + right: -10 + }, + text: { + marginTop: Spacing.xs + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/components/Quote.tsx b/packages/appkit/src/views/w3m-onramp-view/components/Quote.tsx new file mode 100644 index 00000000..97372fd0 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/components/Quote.tsx @@ -0,0 +1,94 @@ +import { NumberUtil } from '@reown/appkit-common-react-native'; +import { type OnRampQuote } from '@reown/appkit-core-react-native'; +import { + FlexView, + Image, + Spacing, + Text, + Tag, + useTheme, + BorderRadius, + Icon, + Pressable +} from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +interface Props { + item: OnRampQuote; + isBestDeal?: boolean; + tagText?: string; + logoURL?: string; + onQuotePress: (item: OnRampQuote) => void; + selected?: boolean; +} + +export const ITEM_HEIGHT = 64; + +export function Quote({ item, logoURL, onQuotePress, selected, tagText }: Props) { + const Theme = useTheme(); + + return ( + onQuotePress(item)} + > + + + {logoURL ? ( + + ) : ( + + )} + + + + {item.serviceProvider?.toLowerCase()} + + {tagText && ( + + {tagText} + + )} + + + {NumberUtil.roundNumber(item.destinationAmount, 6, 5)} {item.destinationCurrencyCode} + + + + {selected && } + + + ); +} + +const styles = StyleSheet.create({ + container: { + borderRadius: BorderRadius.xs, + borderWidth: 1, + borderColor: 'transparent', + height: ITEM_HEIGHT, + justifyContent: 'center' + }, + logo: { + height: 40, + width: 40, + borderRadius: BorderRadius['3xs'], + marginRight: Spacing.xs + }, + providerText: { + textTransform: 'capitalize' + }, + tag: { + padding: Spacing['3xs'], + marginLeft: Spacing['2xs'] + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx b/packages/appkit/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx new file mode 100644 index 00000000..eac3c426 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx @@ -0,0 +1,255 @@ +import { useSnapshot } from 'valtio'; +import { useRef, useState, useMemo } from 'react'; +import Modal from 'react-native-modal'; +import { Dimensions, FlatList, StyleSheet, View } from 'react-native'; +import { + FlexView, + IconLink, + Spacing, + Text, + useTheme, + Separator, + LoadingSpinner, + BorderRadius +} from '@reown/appkit-ui-react-native'; +import { + OnRampController, + type OnRampPaymentMethod, + type OnRampQuote +} from '@reown/appkit-core-react-native'; +import { Placeholder } from '../../../partials/w3m-placeholder'; +import { Quote, ITEM_HEIGHT as QUOTE_ITEM_HEIGHT } from './Quote'; +import { PaymentMethod, ITEM_SIZE } from './PaymentMethod'; + +interface SelectPaymentModalProps { + title?: string; + visible: boolean; + onClose: () => void; +} + +const SEPARATOR_HEIGHT = Spacing.s; + +export function SelectPaymentModal({ title, visible, onClose }: SelectPaymentModalProps) { + const Theme = useTheme(); + const { selectedQuote, quotes } = useSnapshot(OnRampController.state); + const paymentMethodsRef = useRef(null); + const [paymentMethods, setPaymentMethods] = useState( + OnRampController.state.paymentMethods + ); + + const sortedQuotes = useMemo(() => { + if (!selectedQuote) { + return quotes; + } + + return [ + selectedQuote, + // eslint-disable-next-line valtio/state-snapshot-rule + ...(quotes?.filter(quote => quote.serviceProvider !== selectedQuote.serviceProvider) ?? []) + ]; + }, [quotes, selectedQuote]); + + const renderSeparator = () => { + return ; + }; + + const handleQuotePress = (quote: OnRampQuote) => { + if (quote.serviceProvider !== OnRampController.state.selectedQuote?.serviceProvider) { + OnRampController.setSelectedQuote(quote); + } + onClose(); + }; + + const handlePaymentMethodPress = (paymentMethod: OnRampPaymentMethod) => { + if ( + paymentMethod.paymentMethod !== OnRampController.state.selectedPaymentMethod?.paymentMethod + ) { + OnRampController.setSelectedPaymentMethod(paymentMethod); + } + + const visibleItemsCount = Math.round(Dimensions.get('window').width / ITEM_SIZE); + + // Switch payment method to the top if there are more than visibleItemsCount payment methods + if (OnRampController.state.paymentMethods.length > visibleItemsCount) { + const paymentIndex = paymentMethods.findIndex( + method => method.paymentMethod === paymentMethod.paymentMethod + ); + + // Switch payment if its not visible + if (paymentIndex + 1 > visibleItemsCount - 1) { + const realIndex = OnRampController.state.paymentMethods.findIndex( + method => method.paymentMethod === paymentMethod.paymentMethod + ); + + const newPaymentMethods = [ + paymentMethod, + ...OnRampController.state.paymentMethods.slice(0, realIndex), + ...OnRampController.state.paymentMethods.slice(realIndex + 1) + ]; + setPaymentMethods(newPaymentMethods); + } + } + paymentMethodsRef.current?.scrollToIndex({ + index: 0, + animated: true + }); + }; + + const renderQuote = ({ item }: { item: OnRampQuote }) => { + const logoURL = OnRampController.getServiceProviderImage(item.serviceProvider); + const selected = item.serviceProvider === OnRampController.state.selectedQuote?.serviceProvider; + const isBestDeal = + OnRampController.state.quotes?.findIndex( + quote => quote.serviceProvider === item.serviceProvider + ) === 0; + const tagText = isBestDeal ? 'Best Deal' : item.lowKyc ? 'Low KYC' : undefined; + + return ( + handleQuotePress(item)} + tagText={tagText} + /> + ); + }; + + const renderEmpty = () => { + return OnRampController.state.quotesLoading ? ( + + + + ) : ( + + ); + }; + + const renderPaymentMethod = ({ item }: { item: OnRampPaymentMethod }) => { + const parsedItem = item as OnRampPaymentMethod; + const selected = + parsedItem.paymentMethod === OnRampController.state.selectedPaymentMethod?.paymentMethod; + + return ( + handlePaymentMethodPress(parsedItem)} + selected={selected} + testID={`payment-method-item-${parsedItem.paymentMethod}`} + /> + ); + }; + + return ( + + + + + {!!title && {title}} + + + + Pay with + + + item.paymentMethod} + horizontal + showsHorizontalScrollIndicator={false} + /> + + + + Providers + + `${item.serviceProvider}-${item.paymentMethodType}`} + getItemLayout={(_, index) => ({ + length: QUOTE_ITEM_HEIGHT + SEPARATOR_HEIGHT, + offset: (QUOTE_ITEM_HEIGHT + SEPARATOR_HEIGHT) * index, + index + })} + /> + + + ); +} +const styles = StyleSheet.create({ + modal: { + margin: 0, + justifyContent: 'flex-end' + }, + header: { + marginBottom: Spacing.l, + paddingHorizontal: Spacing.m, + paddingTop: Spacing.m + }, + container: { + height: '80%', + borderTopLeftRadius: BorderRadius.l, + borderTopRightRadius: BorderRadius.l + }, + separator: { + width: undefined, + marginVertical: Spacing.m, + marginHorizontal: Spacing.m + }, + listContent: { + paddingTop: Spacing['3xs'], + paddingBottom: Spacing['4xl'], + paddingHorizontal: Spacing.m + }, + iconPlaceholder: { + height: 32, + width: 32 + }, + subtitle: { + marginBottom: Spacing.xs, + marginHorizontal: Spacing.m + }, + emptyContainer: { + height: 150 + }, + paymentMethodsContainer: { + paddingHorizontal: Spacing['3xs'] + }, + paymentMethodsContent: { + paddingLeft: Spacing.xs + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/index.tsx b/packages/appkit/src/views/w3m-onramp-view/index.tsx new file mode 100644 index 00000000..74a76291 --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/index.tsx @@ -0,0 +1,293 @@ +import { useSnapshot } from 'valtio'; +import { memo, useCallback, useEffect, useState } from 'react'; +import { ScrollView } from 'react-native'; +import { + OnRampController, + type OnRampCryptoCurrency, + ThemeController, + RouterController, + type OnRampControllerState, + NetworkController, + AssetUtil, + SnackController, + ConstantsUtil +} from '@reown/appkit-core-react-native'; +import { + Button, + FlexView, + Image, + ListItem, + Text, + TokenButton, + useTheme +} from '@reown/appkit-ui-react-native'; +import { NumberUtil, StringUtil } from '@reown/appkit-common-react-native'; +import { SelectorModal } from '../../partials/w3m-selector-modal'; +import { Currency } from './components/Currency'; +import { getPurchaseCurrencies, getCurrencySuggestedValues } from './utils'; +import { CurrencyInput } from './components/CurrencyInput'; +import { SelectPaymentModal } from './components/SelectPaymentModal'; +import { ITEM_HEIGHT as CURRENCY_ITEM_HEIGHT } from './components/Currency'; +import { Header } from './components/Header'; +import { UiUtil } from '../../utils/UiUtil'; +import { LoadingView } from './components/LoadingView'; +import styles from './styles'; + +const MemoizedCurrency = memo(Currency); + +export function OnRampView() { + const { themeMode } = useSnapshot(ThemeController.state); + const Theme = useTheme(); + + const { + purchaseCurrency, + paymentCurrency, + paymentMethods, + selectedPaymentMethod, + paymentAmount, + quotesLoading, + selectedQuote, + error, + loading, + initialLoading + } = useSnapshot(OnRampController.state) as OnRampControllerState; + const { caipNetwork } = useSnapshot(NetworkController.state); + const [searchValue, setSearchValue] = useState(''); + const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); + const [isPaymentMethodModalVisible, setIsPaymentMethodModalVisible] = useState(false); + const providerImage = OnRampController.getServiceProviderImage(selectedQuote?.serviceProvider); + const suggestedValues = getCurrencySuggestedValues(paymentCurrency); + const purchaseCurrencyCode = + purchaseCurrency?.currencyCode?.split('_')[0] ?? purchaseCurrency?.currencyCode; + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const getQuotes = useCallback(() => { + if (OnRampController.canGenerateQuote()) { + OnRampController.getQuotes(); + } + }, []); + + const getProviderButtonText = () => { + if (selectedQuote) { + return 'via '; + } + + if (!paymentAmount) { + return 'Enter an amount'; + } + + if (!paymentMethods?.length) { + return 'No payment methods available'; + } + + return 'Select a provider'; + }; + + const onValueChange = (value: number) => { + UiUtil.animateChange(); + if (!value) { + OnRampController.abortGetQuotes(); + OnRampController.setPaymentAmount(0); + OnRampController.setSelectedQuote(undefined); + OnRampController.clearError(); + + return; + } + + OnRampController.setPaymentAmount(value); + OnRampController.getQuotesDebounced(); + }; + + const onSuggestedValuePress = (value: number) => { + UiUtil.animateChange(); + OnRampController.setPaymentAmount(value); + getQuotes(); + }; + + const handleSearch = (value: string) => { + setSearchValue(value); + }; + + const handleContinue = async () => { + if (OnRampController.state.selectedQuote) { + RouterController.push('OnRampCheckout'); + } + }; + + const renderCurrencyItem = ({ item }: { item: OnRampCryptoCurrency }) => { + return ( + + ); + }; + + const onPressPurchaseCurrency = (item: any) => { + setIsCurrencyModalVisible(false); + setIsPaymentMethodModalVisible(false); + setSearchValue(''); + OnRampController.setPurchaseCurrency(item as OnRampCryptoCurrency); + getQuotes(); + }; + + const onModalClose = () => { + setSearchValue(''); + setIsCurrencyModalVisible(false); + setIsPaymentMethodModalVisible(false); + }; + + useEffect(() => { + getQuotes(); + }, [selectedPaymentMethod, getQuotes]); + + useEffect(() => { + if (error?.type === ConstantsUtil.ONRAMP_ERROR_TYPES.FAILED_TO_LOAD) { + SnackController.showInternalError({ + shortMessage: 'Failed to load data. Please try again later.', + longMessage: error?.message + }); + RouterController.goBack(); + } + }, [error]); + + useEffect(() => { + if (OnRampController.state.countries.length === 0) { + OnRampController.loadOnRampData(); + } + }, []); + + if (initialLoading || OnRampController.state.countries.length === 0) { + return ; + } + + return ( + <> +
RouterController.push('OnRampSettings')} /> + + + + + You Buy + + setIsCurrencyModalVisible(true)} + testID="currency-selector" + chevron + renderClip={ + networkImage ? ( + + ) : null + } + /> + + + setIsPaymentMethodModalVisible(true)} + style={styles.paymentMethodButton} + imageSrc={selectedPaymentMethod?.logos[themeMode ?? 'light']} + imageStyle={styles.paymentMethodImage} + imageContainerStyle={[ + styles.paymentMethodImageContainer, + { backgroundColor: Theme['gray-glass-010'] } + ]} + disabled={!selectedPaymentMethod || !paymentAmount} + testID="payment-method-button" + > + + {selectedPaymentMethod?.name && ( + + {selectedPaymentMethod.name} + + )} + {getProviderButtonText() && ( + + + {getProviderButtonText()} + + {selectedQuote && ( + <> + {providerImage && ( + + )} + + {StringUtil.capitalize(selectedQuote?.serviceProvider)} + + + )} + + )} + + + + + + + + item.currencyCode} + title="Select token" + itemHeight={CURRENCY_ITEM_HEIGHT} + showNetwork + /> + + + + ); +} diff --git a/packages/appkit/src/views/w3m-onramp-view/styles.ts b/packages/appkit/src/views/w3m-onramp-view/styles.ts new file mode 100644 index 00000000..cd77e1ec --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/styles.ts @@ -0,0 +1,41 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + continueButton: { + marginLeft: Spacing.m, + flex: 3 + }, + cancelButton: { + flex: 1 + }, + paymentMethodButton: { + borderRadius: BorderRadius.s, + height: 64 + }, + paymentMethodImage: { + width: 20, + height: 20, + borderRadius: 0 + }, + paymentMethodImageContainer: { + width: 40, + height: 40, + borderWidth: 0, + borderRadius: BorderRadius['3xs'] + }, + currencyInput: { + marginBottom: Spacing.m + }, + providerImage: { + height: 16, + width: 16, + marginRight: 2 + }, + networkImage: { + height: 14, + width: 14, + borderRadius: BorderRadius.full, + borderWidth: 1 + } +}); diff --git a/packages/appkit/src/views/w3m-onramp-view/utils.ts b/packages/appkit/src/views/w3m-onramp-view/utils.ts new file mode 100644 index 00000000..520b11fb --- /dev/null +++ b/packages/appkit/src/views/w3m-onramp-view/utils.ts @@ -0,0 +1,124 @@ +import { + OnRampController, + NetworkController, + type OnRampFiatCurrency, + ConstantsUtil +} from '@reown/appkit-core-react-native'; + +// -------------------------- Utils -------------------------- +export const getPurchaseCurrencies = (searchValue?: string, filterSelected?: boolean) => { + const networkId = NetworkController.state.caipNetwork?.id?.split(':')[1]; + let networkTokens = + OnRampController.state.purchaseCurrencies?.filter(c => c.chainId === networkId) ?? []; + + if (filterSelected) { + networkTokens = networkTokens?.filter( + c => c.currencyCode !== OnRampController.state.purchaseCurrency?.currencyCode + ); + } + + return searchValue + ? networkTokens.filter( + item => + item.name.toLowerCase().includes(searchValue) || + item.currencyCode.toLowerCase().includes(searchValue) + ) + : networkTokens; +}; + +// Helper function to generate values based on limits and default value +function generateValuesFromLimits( + minAmount: number, + maxAmount: number, + defaultAmount?: number | null +): number[] { + // Use default amount if provided, otherwise calculate a reasonable default + const baseAmount = defaultAmount || Math.min(maxAmount, Math.max(minAmount * 5, 50)); + + // Generate two values less than the default and the default itself + const value1 = Math.max(minAmount, baseAmount * 0.5); + const value2 = Math.max(minAmount, baseAmount * 0.75); + const value3 = baseAmount; + + // Ensure all values are within the maximum limit + const safeValue1 = Math.min(value1, maxAmount); + const safeValue2 = Math.min(value2, maxAmount); + const safeValue3 = Math.min(value3, maxAmount); + + // Round all values to nice numbers + return [safeValue1, safeValue2, safeValue3].map(v => roundToNiceNumber(v)); +} + +// Helper function to round to nice numbers +function roundToNiceNumber(value: number): number { + if (value < 10) return Math.ceil(value); + + if (value < 100) { + // Round to nearest 10 + return Math.ceil(value / 10) * 10; + } else if (value < 1000) { + // Round to nearest 50 + return Math.ceil(value / 50) * 50; + } else if (value < 10000) { + // Round to nearest 100 + return Math.ceil(value / 100) * 100; + } else if (value < 100000) { + // Round to nearest 1000 + return Math.ceil(value / 1000) * 1000; + } else if (value < 1000000) { + // Round to nearest 10000 + return Math.ceil(value / 10000) * 10000; + } else { + // Round to nearest 100000 + return Math.ceil(value / 100000) * 100000; + } +} + +export const getCurrencySuggestedValues = (currency?: OnRampFiatCurrency) => { + if (!currency) return []; + + const limit = OnRampController.getCurrencyLimit(currency); + + // If we have predefined values for this currency, use them + if ( + ConstantsUtil.CURRENCY_SUGGESTED_VALUES[ + currency.currencyCode as keyof typeof ConstantsUtil.CURRENCY_SUGGESTED_VALUES + ] + ) { + const suggestedValues = + ConstantsUtil.CURRENCY_SUGGESTED_VALUES[ + currency.currencyCode as keyof typeof ConstantsUtil.CURRENCY_SUGGESTED_VALUES + ]; + + // Ensure values are within limits + if (limit) { + const minAmount = limit.minimumAmount ?? 0; + const maxAmount = limit.maximumAmount ?? Infinity; + + // Filter values that are within limits + const validValues = suggestedValues?.filter( + (value: number) => value >= minAmount && value <= maxAmount + ); + + // If we have valid values, return them + if (validValues?.length) { + return validValues; + } + + // If no valid values, generate new ones based on limits and default + return generateValuesFromLimits(minAmount, maxAmount, limit?.defaultAmount); + } + + return suggestedValues; + } + + // Fallback to generating values from limits + if (limit) { + const minAmount = limit.minimumAmount ?? 0; + const maxAmount = limit.maximumAmount ?? Infinity; + + return generateValuesFromLimits(minAmount, maxAmount, limit?.defaultAmount); + } + + return []; +}; diff --git a/packages/appkit/src/views/w3m-swap-preview-view/index.tsx b/packages/appkit/src/views/w3m-swap-preview-view/index.tsx new file mode 100644 index 00000000..f3c8f77f --- /dev/null +++ b/packages/appkit/src/views/w3m-swap-preview-view/index.tsx @@ -0,0 +1,145 @@ +import { useSnapshot } from 'valtio'; +import { useEffect } from 'react'; +import { Platform, ScrollView } from 'react-native'; +import { NumberUtil } from '@reown/appkit-common-react-native'; +import { RouterController, SwapController } from '@reown/appkit-core-react-native'; +import { + Button, + FlexView, + Icon, + Spacing, + Text, + TokenButton, + UiUtil +} from '@reown/appkit-ui-react-native'; +import { SwapDetails } from '../../partials/w3m-swap-details'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { useKeyboard } from '../../hooks/useKeyboard'; +import styles from './styles'; + +export function SwapPreviewView() { + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const { + sourceToken, + sourceTokenAmount, + sourceTokenPriceInUSD, + toToken, + toTokenAmount, + toTokenPriceInUSD, + loadingQuote, + loadingBuildTransaction, + loadingTransaction, + loadingApprovalTransaction + } = useSnapshot(SwapController.state); + + const sourceTokenMarketValue = + NumberUtil.parseLocalStringToNumber(sourceTokenAmount) * sourceTokenPriceInUSD; + const toTokenMarketValue = NumberUtil.parseLocalStringToNumber(toTokenAmount) * toTokenPriceInUSD; + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], + default: Spacing['2xl'] + }); + + const loading = + loadingQuote || loadingBuildTransaction || loadingTransaction || loadingApprovalTransaction; + + const onCancel = () => { + RouterController.goBack(); + }; + + const onSwap = () => { + if (SwapController.state.approvalTransaction) { + SwapController.sendTransactionForApproval(SwapController.state.approvalTransaction); + } else { + SwapController.sendTransactionForSwap(SwapController.state.swapTransaction); + } + }; + + useEffect(() => { + function refreshTransaction() { + if (!SwapController.state.loadingApprovalTransaction) { + SwapController.getTransaction(); + } + } + + SwapController.getTransaction(); + + const interval = setInterval(refreshTransaction, 10000); + + return () => { + clearInterval(interval); + }; + }, []); + + return ( + + + + + + Send + + + ${UiUtil.formatNumberToLocalString(sourceTokenMarketValue, 2)} + + + + + + + + + Receive + + + ${UiUtil.formatNumberToLocalString(toTokenMarketValue, 2)} + + + + + + + + + Review transaction carefully + + + + + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-swap-preview-view/styles.ts b/packages/appkit/src/views/w3m-swap-preview-view/styles.ts new file mode 100644 index 00000000..7af1b350 --- /dev/null +++ b/packages/appkit/src/views/w3m-swap-preview-view/styles.ts @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + swapIcon: { + marginVertical: Spacing.xs + }, + reviewIcon: { + marginRight: Spacing['3xs'] + }, + cancelButton: { + flex: 1 + }, + sendButton: { + marginLeft: Spacing.s, + flex: 3 + } +}); diff --git a/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx b/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx new file mode 100644 index 00000000..0a716800 --- /dev/null +++ b/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx @@ -0,0 +1,137 @@ +import { useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { ScrollView, SectionList, type SectionListData } from 'react-native'; +import { + FlexView, + InputText, + ListToken, + ListTokenTotalHeight, + Separator, + Text, + TokenButton, + useTheme +} from '@reown/appkit-ui-react-native'; + +import { + AssetUtil, + NetworkController, + RouterController, + SwapController, + type SwapTokenWithBalance +} from '@reown/appkit-core-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { Placeholder } from '../../partials/w3m-placeholder'; +import styles from './styles'; +import { createSections } from './utils'; + +export function SwapSelectTokenView() { + const { padding } = useCustomDimensions(); + const Theme = useTheme(); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { sourceToken, suggestedTokens } = useSnapshot(SwapController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const [tokenSearch, setTokenSearch] = useState(''); + const isSourceToken = RouterController.state.data?.swapTarget === 'sourceToken'; + + const [filteredTokens, setFilteredTokens] = useState(createSections(isSourceToken, tokenSearch)); + const suggestedList = suggestedTokens + ?.filter(token => token.address !== SwapController.state.sourceToken?.address) + .slice(0, 8); + + const onSearchChange = (value: string) => { + setTokenSearch(value); + setFilteredTokens(createSections(isSourceToken, value)); + }; + + const onTokenPress = (token: SwapTokenWithBalance) => { + if (isSourceToken) { + SwapController.setSourceToken(token); + } else { + SwapController.setToToken(token); + if (SwapController.state.sourceToken && SwapController.state.sourceTokenAmount) { + SwapController.swapTokens(); + } + } + RouterController.goBack(); + }; + + return ( + + + + {!isSourceToken && ( + + {suggestedList?.map((token, index) => ( + onTokenPress(token)} + style={index !== suggestedList.length - 1 ? styles.suggestedToken : undefined} + /> + ))} + + )} + + + []} + bounces={false} + fadingEdgeLength={20} + contentContainerStyle={styles.tokenList} + renderSectionHeader={({ section: { title } }) => ( + + {title} + + )} + ListEmptyComponent={ + + } + getItemLayout={(_, index) => ({ + length: ListTokenTotalHeight, + offset: ListTokenTotalHeight * index, + index + })} + renderItem={({ item }) => ( + onTokenPress(item)} + disabled={item.address === sourceToken?.address} + /> + )} + /> + + ); +} diff --git a/packages/appkit/src/views/w3m-swap-select-token-view/styles.ts b/packages/appkit/src/views/w3m-swap-select-token-view/styles.ts new file mode 100644 index 00000000..ffc103fa --- /dev/null +++ b/packages/appkit/src/views/w3m-swap-select-token-view/styles.ts @@ -0,0 +1,30 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + minHeight: 250, + maxHeight: 600 + }, + title: { + paddingTop: Spacing['2xs'] + }, + tokenList: { + paddingHorizontal: Spacing.m + }, + input: { + marginHorizontal: Spacing.xs + }, + suggestedList: { + marginTop: Spacing.xs + }, + suggestedListContent: { + paddingHorizontal: Spacing.s + }, + suggestedToken: { + marginRight: Spacing.s + }, + suggestedSeparator: { + marginVertical: Spacing.s + } +}); diff --git a/packages/appkit/src/views/w3m-swap-select-token-view/utils.ts b/packages/appkit/src/views/w3m-swap-select-token-view/utils.ts new file mode 100644 index 00000000..978d2bb6 --- /dev/null +++ b/packages/appkit/src/views/w3m-swap-select-token-view/utils.ts @@ -0,0 +1,33 @@ +import { SwapController, type SwapTokenWithBalance } from '@reown/appkit-core-react-native'; + +export function filterTokens(tokens: SwapTokenWithBalance[], searchValue?: string) { + if (!searchValue) { + return tokens; + } + + return tokens.filter( + token => + token.name.toLowerCase().includes(searchValue.toLowerCase()) || + token.symbol.toLowerCase().includes(searchValue.toLowerCase()) + ); +} + +export function createSections(isSourceToken: boolean, searchValue: string) { + const myTokensFiltered = filterTokens( + SwapController.state.myTokensWithBalance ?? [], + searchValue + ); + const popularFiltered = isSourceToken + ? [] + : filterTokens(SwapController.getFilteredPopularTokens() ?? [], searchValue); + + const sections = []; + if (myTokensFiltered.length > 0) { + sections.push({ title: 'Your tokens', data: myTokensFiltered }); + } + if (popularFiltered.length > 0) { + sections.push({ title: 'Popular tokens', data: popularFiltered }); + } + + return sections; +} diff --git a/packages/appkit/src/views/w3m-swap-view/index.tsx b/packages/appkit/src/views/w3m-swap-view/index.tsx new file mode 100644 index 00000000..a8778841 --- /dev/null +++ b/packages/appkit/src/views/w3m-swap-view/index.tsx @@ -0,0 +1,209 @@ +import { useSnapshot } from 'valtio'; +import { useCallback, useEffect } from 'react'; +import { Platform, ScrollView } from 'react-native'; +import { + AccountController, + EventsController, + NetworkController, + RouterController, + SwapController +} from '@reown/appkit-core-react-native'; +import { Button, FlexView, IconLink, Spacing, useTheme } from '@reown/appkit-ui-react-native'; +import { NumberUtil } from '@reown/appkit-common-react-native'; + +import { useKeyboard } from '../../hooks/useKeyboard'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { SwapInput } from '../../partials/w3m-swap-input'; +import { useDebounceCallback } from '../../hooks/useDebounceCallback'; +import { SwapDetails } from '../../partials/w3m-swap-details'; +import styles from './styles'; + +export function SwapView() { + const { + initializing, + sourceToken, + toToken, + sourceTokenAmount, + toTokenAmount, + loadingPrices, + loadingQuote, + sourceTokenPriceInUSD, + toTokenPriceInUSD, + myTokensWithBalance, + inputError + } = useSnapshot(SwapController.state); + const Theme = useTheme(); + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const showDetails = !!sourceToken && !!toToken && !inputError; + + const showSwitch = + myTokensWithBalance && + myTokensWithBalance.findIndex( + token => token.address === SwapController.state.toToken?.address + ) >= 0; + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], + default: Spacing['2xl'] + }); + + const getActionButtonState = () => { + if (!SwapController.state.sourceToken || !SwapController.state.toToken) { + return { text: 'Select token', disabled: true }; + } + + if (!SwapController.state.sourceTokenAmount || !SwapController.state.toTokenAmount) { + return { text: 'Enter amount', disabled: true }; + } + + if (SwapController.state.inputError) { + return { text: SwapController.state.inputError, disabled: true }; + } + + return { text: 'Review swap', disabled: false }; + }; + + const actionState = getActionButtonState(); + const actionLoading = initializing || loadingPrices || loadingQuote; + + const { debouncedCallback: onDebouncedSwap } = useDebounceCallback({ + callback: SwapController.swapTokens.bind(SwapController), + delay: 400 + }); + + const onSourceTokenChange = (value: string) => { + SwapController.setSourceTokenAmount(value); + onDebouncedSwap(); + }; + + const onToTokenChange = (value: string) => { + SwapController.setToTokenAmount(value); + onDebouncedSwap(); + }; + + const onSourceTokenPress = () => { + RouterController.push('SwapSelectToken', { swapTarget: 'sourceToken' }); + }; + + const onReviewPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'INITIATE_SWAP', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + swapFromToken: SwapController.state.sourceToken?.symbol || '', + swapToToken: SwapController.state.toToken?.symbol || '', + swapFromAmount: SwapController.state.sourceTokenAmount || '', + swapToAmount: SwapController.state.toTokenAmount || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + RouterController.push('SwapPreview'); + }; + + const onSourceMaxPress = () => { + const isNetworkToken = + SwapController.state.sourceToken?.address === + NetworkController.getActiveNetworkTokenAddress(); + + const _gasPriceInUSD = SwapController.state.gasPriceInUSD; + const _sourceTokenPriceInUSD = SwapController.state.sourceTokenPriceInUSD; + const _balance = SwapController.state.sourceToken?.quantity.numeric; + + if (_balance) { + if (!_gasPriceInUSD) { + return SwapController.setSourceTokenAmount(_balance); + } + + const amountOfTokenGasRequires = NumberUtil.bigNumber(_gasPriceInUSD.toFixed(5)).dividedBy( + _sourceTokenPriceInUSD + ); + + const maxValue = isNetworkToken + ? NumberUtil.bigNumber(_balance).minus(amountOfTokenGasRequires) + : NumberUtil.bigNumber(_balance); + + SwapController.setSourceTokenAmount(maxValue.isGreaterThan(0) ? maxValue.toFixed(20) : '0'); + SwapController.swapTokens(); + } + }; + + const onToTokenPress = () => { + RouterController.push('SwapSelectToken', { swapTarget: 'toToken' }); + }; + + const onSwitchPress = () => { + SwapController.switchTokens(); + }; + + const watchTokens = useCallback(() => { + SwapController.getNetworkTokenPrice(); + SwapController.getMyTokensWithBalance(); + SwapController.swapTokens(); + }, []); + + useEffect(() => { + SwapController.initializeState(); + + const interval = setInterval(watchTokens, 10000); + + return () => { + clearInterval(interval); + }; + }, [watchTokens]); + + return ( + + + + + + {showSwitch && ( + + )} + + {showDetails && } + + + + ); +} diff --git a/packages/appkit/src/views/w3m-swap-view/styles.ts b/packages/appkit/src/views/w3m-swap-view/styles.ts new file mode 100644 index 00000000..99c07ce4 --- /dev/null +++ b/packages/appkit/src/views/w3m-swap-view/styles.ts @@ -0,0 +1,23 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + bottomInputContainer: { + width: '100%' + }, + arrowIcon: { + position: 'absolute', + top: -30, + borderRadius: BorderRadius.xs, + borderWidth: 4, + height: 50, + width: 50 + }, + tokenInput: { + marginBottom: Spacing.xs + }, + actionButton: { + marginTop: Spacing.xs, + width: '100%' + } +}); diff --git a/packages/appkit/src/views/w3m-transactions-view/index.tsx b/packages/appkit/src/views/w3m-transactions-view/index.tsx new file mode 100644 index 00000000..b2f66295 --- /dev/null +++ b/packages/appkit/src/views/w3m-transactions-view/index.tsx @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; +import { AccountActivity } from '../../partials/w3m-account-activity'; + +export function TransactionsView() { + return ; +} + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: Spacing.l, + marginTop: Spacing.s + } +}); diff --git a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx new file mode 100644 index 00000000..f2074e50 --- /dev/null +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx @@ -0,0 +1,92 @@ +import { useSnapshot } from 'valtio'; +import { useState } from 'react'; +import { FlatList } from 'react-native'; +import { Icon, ListItem, Separator, Text } from '@reown/appkit-ui-react-native'; +import { + ApiController, + AssetUtil, + CoreHelperUtil, + ConnectionUtil, + EventsController, + NetworkController, + NetworkUtil, + type CaipNetwork, + type NetworkControllerState +} from '@reown/appkit-core-react-native'; +import styles from './styles'; + +export function UnsupportedChainView() { + const { caipNetwork, supportsAllNetworks, approvedCaipNetworkIds, requestedCaipNetworks } = + useSnapshot(NetworkController.state) as NetworkControllerState; + + const [disconnecting, setDisconnecting] = useState(false); + const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); + const imageHeaders = ApiController._getApiHeaders(); + + const onNetworkPress = async (network: CaipNetwork) => { + const result = await NetworkUtil.handleNetworkSwitch(network); + if (result?.type === 'SWITCH_NETWORK') { + EventsController.sendEvent({ + type: 'track', + event: 'SWITCH_NETWORK', + properties: { + network: network.id + } + }); + } + }; + + const onDisconnect = async () => { + setDisconnecting(true); + await ConnectionUtil.disconnect(); + setDisconnecting(false); + }; + + return ( + + The swap feature doesn't support your current network. Switch to an available option to + continue. + + } + contentContainerStyle={styles.contentContainer} + renderItem={({ item }) => ( + onNetworkPress(item)} + testID="button-network" + style={styles.networkItem} + contentStyle={styles.networkItemContent} + disabled={!supportsAllNetworks && !approvedCaipNetworkIds?.includes(item.id)} + > + + {item.name ?? 'Unknown'} + + {item.id === caipNetwork?.id && } + + )} + ListFooterComponent={ + <> + + + Disconnect + + + } + /> + ); +} diff --git a/packages/appkit/src/views/w3m-unsupported-chain-view/styles.ts b/packages/appkit/src/views/w3m-unsupported-chain-view/styles.ts new file mode 100644 index 00000000..0c07dc9c --- /dev/null +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/styles.ts @@ -0,0 +1,21 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + contentContainer: { + padding: Spacing.s, + paddingBottom: Spacing.xl + }, + header: { + marginBottom: Spacing.s + }, + networkItem: { + marginVertical: Spacing['3xs'] + }, + networkItemContent: { + justifyContent: 'space-between' + }, + separator: { + marginBottom: Spacing['2xs'] + } +}); diff --git a/packages/appkit/src/views/w3m-update-email-primary-otp-view/index.tsx b/packages/appkit/src/views/w3m-update-email-primary-otp-view/index.tsx new file mode 100644 index 00000000..4a6297f0 --- /dev/null +++ b/packages/appkit/src/views/w3m-update-email-primary-otp-view/index.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react'; + +import { + ConnectorController, + CoreHelperUtil, + EventsController, + RouterController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; + +import { OtpCodeView } from '../../partials/w3m-otp-code'; + +export function UpdateEmailPrimaryOtpView() { + const { data } = RouterController.state; + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const authProvider = ConnectorController.getAuthConnector()?.provider as + | AppKitFrameProvider + | undefined; + + const onOtpSubmit = async (value: string) => { + if (!authProvider || loading) return; + setLoading(true); + setError(''); + try { + await authProvider.updateEmailPrimaryOtp({ otp: value }); + EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_PASS' }); + RouterController.replace('UpdateEmailSecondaryOtp', data); + } catch (e) { + EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_FAIL' }); + const parsedError = CoreHelperUtil.parseError(e); + if (parsedError?.includes('Invalid Otp')) { + setError('Invalid code. Try again.'); + } else { + SnackController.showError(parsedError); + } + } + setLoading(false); + }; + + return ( + + ); +} diff --git a/packages/appkit/src/views/w3m-update-email-secondary-otp-view/index.tsx b/packages/appkit/src/views/w3m-update-email-secondary-otp-view/index.tsx new file mode 100644 index 00000000..a0102f33 --- /dev/null +++ b/packages/appkit/src/views/w3m-update-email-secondary-otp-view/index.tsx @@ -0,0 +1,56 @@ +import { useSnapshot } from 'valtio'; +import { useState } from 'react'; + +import { + ConnectorController, + CoreHelperUtil, + RouterController, + SnackController, + EventsController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; + +import { OtpCodeView } from '../../partials/w3m-otp-code'; + +export function UpdateEmailSecondaryOtpView() { + const { data } = useSnapshot(RouterController.state); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const authConnector = ConnectorController.getAuthConnector(); + + const onOtpSubmit = async (value: string) => { + if (!authConnector) return; + setLoading(true); + setError(''); + try { + const provider = authConnector?.provider as AppKitFrameProvider; + await provider.updateEmailSecondaryOtp({ otp: value }); + EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_PASS' }); + EventsController.sendEvent({ type: 'track', event: 'EMAIL_EDIT_COMPLETE' }); + RouterController.reset('Account'); + } catch (e) { + EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_FAIL' }); + const parsedError = CoreHelperUtil.parseError(e); + if (parsedError?.includes('Invalid Otp')) { + setError('Invalid code. Try again.'); + } else { + SnackController.showError(parsedError); + } + } + setLoading(false); + }; + + return ( + + ); +} diff --git a/packages/appkit/src/views/w3m-update-email-wallet-view/index.tsx b/packages/appkit/src/views/w3m-update-email-wallet-view/index.tsx new file mode 100644 index 00000000..3d8dbeb4 --- /dev/null +++ b/packages/appkit/src/views/w3m-update-email-wallet-view/index.tsx @@ -0,0 +1,96 @@ +import { useState } from 'react'; +import { Platform } from 'react-native'; +import { + ConnectorController, + CoreHelperUtil, + RouterController, + SnackController, + EventsController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { Button, EmailInput, FlexView, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { useKeyboard } from '../../hooks/useKeyboard'; + +import styles from './styles'; + +export function UpdateEmailWalletView() { + const { data } = RouterController.state; + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [email, setEmail] = useState(data?.email || ''); + const [isValidNewEmail, setIsValidNewEmail] = useState(false); + const authConnector = ConnectorController.getAuthConnector(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing.l : Spacing.l, + default: Spacing.l + }); + + const onChangeText = (value: string) => { + setIsValidNewEmail(data?.email !== value && CoreHelperUtil.isValidEmail(value)); + setEmail(value); + setError(''); + }; + + const onEmailSubmit = async (value: string) => { + if (!authConnector) return; + + const provider = authConnector.provider as AppKitFrameProvider; + setLoading(true); + setError(''); + + try { + const response = await provider.updateEmail({ email: value }); + EventsController.sendEvent({ type: 'track', event: 'EMAIL_EDIT' }); + if (response.action === 'VERIFY_SECONDARY_OTP') { + RouterController.push('UpdateEmailSecondaryOtp', { email: data?.email, newEmail: value }); + } else { + RouterController.push('UpdateEmailPrimaryOtp', { email: data?.email, newEmail: value }); + } + } catch (e) { + const parsedError = CoreHelperUtil.parseError(e); + if (parsedError?.includes('Invalid email')) { + setError('Invalid email. Try again.'); + } else { + SnackController.showError(parsedError); + } + } finally { + setLoading(false); + } + }; + + return ( + + + + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-update-email-wallet-view/styles.ts b/packages/appkit/src/views/w3m-update-email-wallet-view/styles.ts new file mode 100644 index 00000000..d456fc24 --- /dev/null +++ b/packages/appkit/src/views/w3m-update-email-wallet-view/styles.ts @@ -0,0 +1,24 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + }, + emailInput: { + marginBottom: Spacing.s + }, + cancelButton: { + flex: 1, + height: 48, + marginRight: Spacing['2xs'], + borderRadius: BorderRadius.xs + }, + saveButton: { + flex: 1, + height: 48, + marginLeft: Spacing['2xs'], + borderRadius: BorderRadius.xs + } +}); diff --git a/packages/appkit/src/views/w3m-upgrade-email-wallet-view/index.tsx b/packages/appkit/src/views/w3m-upgrade-email-wallet-view/index.tsx new file mode 100644 index 00000000..6b66cf5f --- /dev/null +++ b/packages/appkit/src/views/w3m-upgrade-email-wallet-view/index.tsx @@ -0,0 +1,38 @@ +import { useSnapshot } from 'valtio'; +import { Linking, StyleSheet } from 'react-native'; +import { Chip, FlexView, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { ConnectorController, type AppKitFrameProvider } from '@reown/appkit-core-react-native'; + +export function UpgradeEmailWalletView() { + const { connectors } = useSnapshot(ConnectorController.state); + const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; + + const onLinkPress = () => { + const link = authProvider.getSecureSiteDashboardURL(); + Linking.canOpenURL(link).then(supported => { + if (supported) Linking.openURL(link); + }); + }; + + return ( + + Follow the instructions on + + + You will have to reconnect for security reasons + + + ); +} + +const styles = StyleSheet.create({ + chip: { + marginVertical: Spacing.m + } +}); diff --git a/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/index.tsx b/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/index.tsx new file mode 100644 index 00000000..ce042b10 --- /dev/null +++ b/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/index.tsx @@ -0,0 +1,106 @@ +import { Linking } from 'react-native'; +import { useEffect, useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { Button, FlexView, IconLink, Link, Text, Visual } from '@reown/appkit-ui-react-native'; +import { + AccountController, + ConnectorController, + EventsController, + ModalController, + NetworkController, + RouterController, + SnackController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import styles from './styles'; + +export function UpgradeToSmartAccountView() { + const { address } = useSnapshot(AccountController.state); + const { loading } = useSnapshot(ModalController.state); + const [initialAddress] = useState(address); + + const onSwitchAccountType = async () => { + try { + ModalController.setLoading(true); + const accountType = + AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + await provider?.setPreferredAccount(accountType); + EventsController.sendEvent({ + type: 'track', + event: 'SET_PREFERRED_ACCOUNT_TYPE', + properties: { + accountType, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + } catch (error) { + ModalController.setLoading(false); + SnackController.showError('Error switching account type'); + } + }; + + const onClose = () => { + ModalController.close(); + ModalController.setLoading(false); + }; + + const onGoBack = () => { + RouterController.goBack(); + ModalController.setLoading(false); + }; + + const onLearnMorePress = () => { + Linking.openURL('https://reown.com/faq'); + }; + + useEffect(() => { + // Go back if the address has changed + if (address && initialAddress !== address) { + RouterController.goBack(); + } + }, [initialAddress, address]); + + return ( + <> + + + + + + + + + + Discover Smart Accounts + + + Access advanced brand new features as username, improved security and a smoother user + experience! + + + + + + + Learn more + + + + ); +} diff --git a/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/styles.ts b/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/styles.ts new file mode 100644 index 00000000..f2d23ef0 --- /dev/null +++ b/packages/appkit/src/views/w3m-upgrade-to-smart-account-view/styles.ts @@ -0,0 +1,29 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + }, + title: { + marginTop: Spacing.xl, + marginVertical: Spacing.s + }, + button: { + width: 110 + }, + cancelButton: { + marginRight: Spacing.m + }, + middleIcon: { + marginHorizontal: Spacing.s + }, + closeButton: { + alignSelf: 'flex-end', + right: Spacing.xl, + top: Spacing.l, + position: 'absolute', + zIndex: 2 + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx new file mode 100644 index 00000000..3695bc47 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -0,0 +1,48 @@ +import { ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { FlexView, Text, Banner, NetworkImage } from '@reown/appkit-ui-react-native'; +import { + AccountController, + ApiController, + AssetUtil, + NetworkController +} from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function WalletCompatibleNetworks() { + const { padding } = useCustomDimensions(); + const { preferredAccountType } = useSnapshot(AccountController.state); + const isSmartAccount = + preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); + const approvedNetworks = isSmartAccount + ? NetworkController.getSmartAccountEnabledNetworks() + : NetworkController.getApprovedCaipNetworks(); + const imageHeaders = ApiController._getApiHeaders(); + + return ( + + + + {approvedNetworks.map((network, index) => ( + + + + {network?.name ?? 'Unknown Network'} + + + ))} + + + ); +} diff --git a/packages/appkit/src/views/w3m-wallet-compatible-networks-view/styles.ts b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/styles.ts new file mode 100644 index 00000000..de669ca6 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/styles.ts @@ -0,0 +1,8 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + image: { + marginRight: Spacing.s + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-receive-view/index.tsx b/packages/appkit/src/views/w3m-wallet-receive-view/index.tsx new file mode 100644 index 00000000..ae41a517 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-receive-view/index.tsx @@ -0,0 +1,94 @@ +import { useSnapshot } from 'valtio'; +import { ScrollView, StyleSheet } from 'react-native'; +import { + Chip, + CompatibleNetwork, + FlexView, + QrCode, + Spacing, + Text, + UiUtil +} from '@reown/appkit-ui-react-native'; +import { + AccountController, + ApiController, + AssetUtil, + NetworkController, + OptionsController, + RouterController, + SnackController +} from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; + +export function WalletReceiveView() { + const { address, profileName, preferredAccountType } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { padding } = useCustomDimensions(); + const canCopy = OptionsController.isClipboardAvailable(); + const isSmartAccount = + preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); + const networks = isSmartAccount + ? NetworkController.getSmartAccountEnabledNetworks() + : NetworkController.getApprovedCaipNetworks(); + + const imagesArray = networks + .filter(network => network?.imageId) + .slice(0, 5) + .map(AssetUtil.getNetworkImage) + .filter(Boolean) as string[]; + + const label = UiUtil.getTruncateString({ + string: profileName ?? address ?? '', + charsStart: profileName ? 30 : 4, + charsEnd: profileName ? 0 : 4, + truncate: profileName ? 'end' : 'middle' + }); + + const onNetworkPress = () => { + RouterController.push('WalletCompatibleNetworks'); + }; + + const onCopyAddress = () => { + if (canCopy && AccountController.state.address) { + OptionsController.copyToClipboard(AccountController.state.address); + SnackController.showSuccess('Address copied'); + } + }; + + if (!address) return; + + return ( + + + + + + {canCopy ? 'Copy your address or scan this QR code' : 'Scan this QR code'} + + + + + ); +} + +const styles = StyleSheet.create({ + qrContainer: { + marginVertical: Spacing.xl + }, + networksButton: { + marginTop: Spacing.l + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-receive-view/styles.ts b/packages/appkit/src/views/w3m-wallet-receive-view/styles.ts new file mode 100644 index 00000000..c866ab3d --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-receive-view/styles.ts @@ -0,0 +1,8 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx new file mode 100644 index 00000000..d71df174 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx @@ -0,0 +1,101 @@ +import { AssetUtil, type CaipNetwork } from '@reown/appkit-core-react-native'; +import { + BorderRadius, + FlexView, + NetworkImage, + Spacing, + Text, + UiUtil, + useTheme +} from '@reown/appkit-ui-react-native'; +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; + +export interface PreviewSendDetailsProps { + address?: string; + name?: string; + caipNetwork?: CaipNetwork; + networkFee?: number; + style?: StyleProp; +} + +export function PreviewSendDetails({ + address, + name, + caipNetwork, + networkFee, + style +}: PreviewSendDetailsProps) { + const Theme = useTheme(); + + const formattedName = UiUtil.getTruncateString({ + string: name ?? '', + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }); + + const formattedAddress = UiUtil.getTruncateString({ + string: address || '', + charsStart: 6, + charsEnd: 8, + truncate: 'middle' + }); + + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + return ( + + + Details + + + + Network cost + + + ${UiUtil.formatNumberToLocalString(networkFee, 2)} + + + + + {formattedName || 'Address'} + + + {formattedAddress} + + + + + Network + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + justifyContent: 'center', + borderRadius: BorderRadius.xxs + }, + title: { + marginBottom: Spacing.xs + }, + item: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: Spacing.s, + borderRadius: BorderRadius.xxs, + marginTop: Spacing['2xs'] + }, + networkImage: { + height: 24, + width: 24, + borderRadius: BorderRadius.full + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx new file mode 100644 index 00000000..ea085ecd --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx @@ -0,0 +1,36 @@ +import { BorderRadius, FlexView, Text, useTheme } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export interface PreviewSendPillProps { + text: string; + children: React.ReactNode; +} + +export function PreviewSendPill({ text, children }: PreviewSendPillProps) { + const Theme = useTheme(); + + return ( + + + {text} + + {children} + + ); +} + +const styles = StyleSheet.create({ + pill: { + borderRadius: BorderRadius.full, + borderWidth: StyleSheet.hairlineWidth + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-send-preview-view/index.tsx b/packages/appkit/src/views/w3m-wallet-send-preview-view/index.tsx new file mode 100644 index 00000000..8b9e7f41 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-preview-view/index.tsx @@ -0,0 +1,134 @@ +import { useSnapshot } from 'valtio'; +import { ScrollView } from 'react-native'; +import { Avatar, Button, FlexView, Icon, Image, Text, UiUtil } from '@reown/appkit-ui-react-native'; +import { NumberUtil } from '@reown/appkit-common-react-native'; +import { + NetworkController, + RouterController, + SendController +} from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { PreviewSendPill } from './components/preview-send-pill'; +import styles from './styles'; +import { PreviewSendDetails } from './components/preview-send-details'; + +export function WalletSendPreviewView() { + const { padding } = useCustomDimensions(); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { + token, + receiverAddress, + receiverProfileName, + receiverProfileImageUrl, + gasPriceInUSD, + loading + } = useSnapshot(SendController.state); + + const getSendValue = () => { + if (SendController.state.token && SendController.state.sendTokenAmount) { + const price = SendController.state.token.price; + const totalValue = price * SendController.state.sendTokenAmount; + + return totalValue.toFixed(2); + } + + return null; + }; + + const getTokenAmount = () => { + const value = SendController.state.sendTokenAmount + ? NumberUtil.roundNumber(SendController.state.sendTokenAmount, 6, 5) + : 'unknown'; + + return `${value} ${SendController.state.token?.symbol}`; + }; + + const formattedAddress = receiverProfileName + ? UiUtil.getTruncateString({ + string: receiverProfileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: receiverAddress || '', + charsStart: 4, + charsEnd: 4, + truncate: 'middle' + }); + + const onSend = () => { + SendController.sendToken(); + }; + + const onCancel = () => { + RouterController.goBack(); + SendController.setLoading(false); + }; + + return ( + + + + + + Send + + + ${getSendValue()} + + + + {token?.iconUrl ? ( + + ) : ( + + )} + + + + + + To + + + + + + + + + + Review transaction carefully + + + + + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-wallet-send-preview-view/styles.ts b/packages/appkit/src/views/w3m-wallet-send-preview-view/styles.ts new file mode 100644 index 00000000..432a72c3 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-preview-view/styles.ts @@ -0,0 +1,35 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + }, + tokenLogo: { + height: 32, + width: 32, + borderRadius: BorderRadius.full, + marginLeft: Spacing.xs + }, + arrow: { + marginVertical: Spacing.xs + }, + avatar: { + marginLeft: Spacing.xs + }, + details: { + marginTop: Spacing['2xl'], + marginBottom: Spacing.s + }, + reviewIcon: { + marginRight: Spacing['3xs'] + }, + cancelButton: { + flex: 1 + }, + sendButton: { + marginLeft: Spacing.s, + flex: 3 + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-send-select-token-view/index.tsx b/packages/appkit/src/views/w3m-wallet-send-select-token-view/index.tsx new file mode 100644 index 00000000..73a065e3 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-select-token-view/index.tsx @@ -0,0 +1,83 @@ +import { useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { ScrollView } from 'react-native'; +import { FlexView, InputText, ListToken, Text } from '@reown/appkit-ui-react-native'; +import { + AccountController, + AssetUtil, + NetworkController, + RouterController, + SendController +} from '@reown/appkit-core-react-native'; +import type { Balance } from '@reown/appkit-common-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { Placeholder } from '../../partials/w3m-placeholder'; +import styles from './styles'; + +export function WalletSendSelectTokenView() { + const { padding } = useCustomDimensions(); + const { tokenBalance } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { token } = useSnapshot(SendController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const [tokenSearch, setTokenSearch] = useState(''); + const [filteredTokens, setFilteredTokens] = useState(tokenBalance ?? []); + + const onSearchChange = (value: string) => { + setTokenSearch(value); + const filtered = AccountController.state.tokenBalance?.filter(_token => + _token.name.toLowerCase().includes(value.toLowerCase()) + ); + setFilteredTokens(filtered ?? []); + }; + + const onTokenPress = (_token: Balance) => { + SendController.setToken(_token); + SendController.setTokenAmount(undefined); + RouterController.goBack(); + }; + + return ( + + + + + + + Your tokens + + {filteredTokens.length ? ( + filteredTokens.map((_token, index) => ( + onTokenPress(_token)} + disabled={_token.address === token?.address} + /> + )) + ) : ( + + )} + + + ); +} diff --git a/packages/appkit/src/views/w3m-wallet-send-select-token-view/styles.ts b/packages/appkit/src/views/w3m-wallet-send-select-token-view/styles.ts new file mode 100644 index 00000000..23c2e7c5 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-select-token-view/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + minHeight: 250, + maxHeight: 600 + }, + title: { + marginBottom: Spacing.xs + }, + tokenList: { + paddingHorizontal: Spacing.m + } +}); diff --git a/packages/appkit/src/views/w3m-wallet-send-view/index.tsx b/packages/appkit/src/views/w3m-wallet-send-view/index.tsx new file mode 100644 index 00000000..aecdeb05 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-view/index.tsx @@ -0,0 +1,129 @@ +import { useCallback, useEffect } from 'react'; +import { Platform, ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { + AccountController, + CoreHelperUtil, + RouterController, + SendController, + SwapController +} from '@reown/appkit-core-react-native'; +import { Button, FlexView, IconBox, Spacing } from '@reown/appkit-ui-react-native'; +import { SendInputToken } from '../../partials/w3m-send-input-token'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { useKeyboard } from '../../hooks/useKeyboard'; +import { SendInputAddress } from '../../partials/w3m-send-input-address'; +import styles from './styles'; + +export function WalletSendView() { + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const { token, sendTokenAmount, receiverAddress, receiverProfileName, loading, gasPrice } = + useSnapshot(SendController.state); + const { tokenBalance } = useSnapshot(AccountController.state); + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], + default: Spacing['2xl'] + }); + + const fetchNetworkPrice = useCallback(async () => { + await SwapController.getNetworkTokenPrice(); + const gas = await SwapController.getInitialGasPrice(); + if (gas?.gasPrice && gas?.gasPriceInUSD) { + SendController.setGasPrice(gas.gasPrice); + SendController.setGasPriceInUsd(gas.gasPriceInUSD); + } + }, []); + + const onSendPress = () => { + RouterController.push('WalletSendPreview'); + }; + + const getActionText = () => { + if (!SendController.state.token) { + return 'Select token'; + } + + if ( + SendController.state.sendTokenAmount && + SendController.state.token && + SendController.state.sendTokenAmount > Number(SendController.state.token.quantity.numeric) + ) { + return 'Insufficient funds'; + } + + if (!SendController.state.sendTokenAmount) { + return 'Add amount'; + } + + if (SendController.state.sendTokenAmount && SendController.state.token?.price) { + const value = SendController.state.sendTokenAmount * SendController.state.token.price; + if (!value) { + return 'Incorrect value'; + } + } + + if ( + SendController.state.receiverAddress && + !CoreHelperUtil.isAddress(SendController.state.receiverAddress) + ) { + return 'Invalid address'; + } + + if (!SendController.state.receiverAddress) { + return 'Add address'; + } + + return 'Preview send'; + }; + + useEffect(() => { + if (!token) { + SendController.setToken(tokenBalance?.[0]); + } + fetchNetworkPrice(); + }, [token, tokenBalance, fetchNetworkPrice]); + + const actionText = getActionText(); + + return ( + + + RouterController.push('WalletSendSelectToken')} + /> + + + + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-wallet-send-view/styles.ts b/packages/appkit/src/views/w3m-wallet-send-view/styles.ts new file mode 100644 index 00000000..a3cdd0f4 --- /dev/null +++ b/packages/appkit/src/views/w3m-wallet-send-view/styles.ts @@ -0,0 +1,21 @@ +import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + sendButton: { + width: '100%', + marginTop: Spacing.xl, + borderRadius: BorderRadius.xs + }, + tokenInput: { + marginBottom: Spacing.xs + }, + arrowIcon: { + position: 'absolute', + top: -30, + borderRadius: BorderRadius.s + }, + addressContainer: { + width: '100%' + } +}); diff --git a/packages/appkit/src/views/w3m-what-is-a-network-view/index.tsx b/packages/appkit/src/views/w3m-what-is-a-network-view/index.tsx new file mode 100644 index 00000000..cb8cca52 --- /dev/null +++ b/packages/appkit/src/views/w3m-what-is-a-network-view/index.tsx @@ -0,0 +1,49 @@ +import { Linking, ScrollView } from 'react-native'; +import { Button, FlexView, Text, Visual } from '@reown/appkit-ui-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function WhatIsANetworkView() { + const { padding } = useCustomDimensions(); + const onLearnMorePress = () => { + Linking.openURL('https://ethereum.org/en/developers/docs/networks/'); + }; + + return ( + + + + + + + + + The system’s nuts and bolts + + + A network is what brings the blockchain to life, as this technical infrastructure allows + apps to access the ledger and smart contract services. + + + + + + + + Designed for different uses + + + Each network is designed differently, and may therefore suit certain apps and experiences. + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-what-is-a-network-view/styles.ts b/packages/appkit/src/views/w3m-what-is-a-network-view/styles.ts new file mode 100644 index 00000000..593afd3b --- /dev/null +++ b/packages/appkit/src/views/w3m-what-is-a-network-view/styles.ts @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + learnButton: { + marginTop: Spacing.xl + }, + visual: { + marginHorizontal: Spacing.s + }, + text: { + marginVertical: Spacing.xs + } +}); diff --git a/packages/appkit/src/views/w3m-what-is-a-wallet-view/index.tsx b/packages/appkit/src/views/w3m-what-is-a-wallet-view/index.tsx new file mode 100644 index 00000000..17cdcc55 --- /dev/null +++ b/packages/appkit/src/views/w3m-what-is-a-wallet-view/index.tsx @@ -0,0 +1,68 @@ +import { ScrollView } from 'react-native'; +import { Button, FlexView, Text, Visual } from '@reown/appkit-ui-react-native'; +import { EventsController, RouterController } from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function WhatIsAWalletView() { + const { padding } = useCustomDimensions(); + + const onGetWalletPress = () => { + RouterController.push('GetWallet'); + EventsController.sendEvent({ type: 'track', event: 'CLICK_GET_WALLET' }); + }; + + return ( + + + + + + + + + Your web3 account + + + Create a wallet with your email or by choosing a wallet provider. + + + + + + + + The home for your digital assets + + + Store, send, and receive digital assets like crypto and NFTs. + + + + + + + + Your gateway to web3 apps + + + Connect your wallet to start exploring DeFi, DAOs, and much more. + + + + + ); +} diff --git a/packages/appkit/src/views/w3m-what-is-a-wallet-view/styles.ts b/packages/appkit/src/views/w3m-what-is-a-wallet-view/styles.ts new file mode 100644 index 00000000..40f3f31b --- /dev/null +++ b/packages/appkit/src/views/w3m-what-is-a-wallet-view/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + getWalletButton: { + marginTop: Spacing.xl, + marginBottom: Spacing.m + }, + visual: { + marginHorizontal: Spacing.s + }, + text: { + marginVertical: Spacing.xs + } +}); diff --git a/packages/appkit/tsconfig.json b/packages/appkit/tsconfig.json new file mode 100644 index 00000000..b8a49a4b --- /dev/null +++ b/packages/appkit/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "src/index.ts"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index c998ec99..652e59ff 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-react-native": "1.2.3", "@reown/appkit-scaffold-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3" From aebda1c8b3c147647bc15e2d8840e61479786933 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:08:41 -0300 Subject: [PATCH 02/91] chore: connect with basic ethers adapter --- apps/native/App.tsx | 91 ++++--- apps/native/package.json | 3 + package.json | 1 + packages/appkit/package.json | 71 ++++++ packages/appkit/src/AppKit.ts | 176 ++++++++++++++ packages/appkit/src/AppKitContext.tsx | 30 +++ packages/appkit/src/adapters/types.ts | 100 ++++++++ .../src/connectors/WalletConnectConnector.ts | 76 ++++++ .../src/controllers/ConnectionController.ts | 110 +++++++++ packages/appkit/src/index.ts | 6 + .../views/w3m-account-default-view/index.tsx | 12 +- .../src/views/w3m-connect-view/index.tsx | 3 +- .../src/views/w3m-connecting-view/index.tsx | 10 +- .../w3m-unsupported-chain-view/index.tsx | 8 +- .../src/controllers/ConnectionController.ts | 9 + packages/core/src/index.ts | 1 - packages/core/src/utils/ConnectionUtil.ts | 27 --- packages/ethers/package.json | 1 + packages/ethers/src/adapter.ts | 99 ++++++++ packages/ethers/src/index.tsx | 4 + .../views/w3m-account-default-view/index.tsx | 3 +- .../w3m-unsupported-chain-view/index.tsx | 3 +- packages/wagmi/src/adapter.ts | 93 +++++++ .../src/connectors/WalletConnectConnector.ts | 19 +- packages/wagmi/src/index.tsx | 4 +- yarn.lock | 226 +++++++++++++++++- 26 files changed, 1098 insertions(+), 88 deletions(-) create mode 100644 packages/appkit/package.json create mode 100644 packages/appkit/src/AppKit.ts create mode 100644 packages/appkit/src/AppKitContext.tsx create mode 100644 packages/appkit/src/adapters/types.ts create mode 100644 packages/appkit/src/connectors/WalletConnectConnector.ts create mode 100644 packages/appkit/src/controllers/ConnectionController.ts delete mode 100644 packages/core/src/utils/ConnectionUtil.ts create mode 100644 packages/ethers/src/adapter.ts create mode 100644 packages/wagmi/src/adapter.ts diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 12815a5f..fa057de7 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -6,13 +6,16 @@ import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Toast from 'react-native-toast-message'; -import { - AppKit, - AppKitButton, - NetworkButton, - createAppKit, - defaultWagmiConfig -} from '@reown/appkit-wagmi-react-native'; +// import { +// // AppKit, +// // AppKitButton, +// // NetworkButton, +// // createAppKit, +// WagmiAdapter, +// defaultWagmiConfig +// } from '@reown/appkit-wagmi-react-native'; + +import { AppKitProvider, createAppKit, AppKit, AppKitButton } from '@reown/appkit-react-native'; import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; import { Text } from '@reown/appkit-ui-react-native'; @@ -25,6 +28,7 @@ import { getCustomWallets } from './src/utils/misc'; import { chains } from './src/utils/WagmiUtils'; import { OpenButton } from './src/components/OpenButton'; import { DisconnectButton } from './src/components/DisconnectButton'; +import { EthersAdapter } from '@reown/appkit-ethers-react-native'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -53,40 +57,58 @@ const extraConnectors = Platform.select({ default: [] }); -const wagmiConfig = defaultWagmiConfig({ - chains, - projectId, - metadata, - extraConnectors -}); +// const wagmiConfig = defaultWagmiConfig({ +// chains, +// projectId, +// metadata, +// extraConnectors +// }); const queryClient = new QueryClient(); const customWallets = getCustomWallets(); -createAppKit({ +// const wagmiAdapter = new WagmiAdapter({ +// wagmiConfig, +// projectId, +// networks: chains +// }); + +const ethersAdapter = new EthersAdapter({ + projectId +}); + +// createAppKit({ +// projectId, +// wagmiConfig, +// siweConfig, +// clipboardClient, +// customWallets, +// enableAnalytics: true, +// metadata, +// debug: true, +// features: { +// email: true, +// socials: ['x', 'discord', 'apple'], +// emailShowWallets: true, +// swaps: true +// // onramp: true +// } +// }); + +const appKit = createAppKit({ projectId, - wagmiConfig, - siweConfig, - clipboardClient, - customWallets, - enableAnalytics: true, + adapters: [ethersAdapter], metadata, - debug: true, - features: { - email: true, - socials: ['x', 'discord', 'apple'], - emailShowWallets: true, - swaps: true - // onramp: true - } + networks: chains, }); export default function Native() { const isDarkMode = useColorScheme() === 'dark'; return ( - + // + @@ -100,16 +122,17 @@ export default function Native() { loadingLabel="Connecting..." balance="show" /> - - - - - + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} - + + // ); } diff --git a/apps/native/package.json b/apps/native/package.json index 50627158..6d4da975 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -23,11 +23,14 @@ "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", "@reown/appkit-auth-wagmi-react-native": "1.2.3", + "@reown/appkit-ethers-react-native": "workspace:*", + "@reown/appkit-react-native": "workspace:*", "@reown/appkit-wagmi-react-native": "1.2.3", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-query-persist-client": "5.56.2", "@walletconnect/react-native-compat": "2.19.1", + "ethers": "6.13.5", "expo": "^52.0.38", "expo-application": "~6.0.2", "expo-clipboard": "~7.0.1", diff --git a/package.json b/package.json index 1b5efac7..93265fcd 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "workspaces": [ "packages/core", + "packages/appkit", "packages/ui", "packages/common", "packages/wallet", diff --git a/packages/appkit/package.json b/packages/appkit/package.json new file mode 100644 index 00000000..9dd8f22a --- /dev/null +++ b/packages/appkit/package.json @@ -0,0 +1,71 @@ +{ + "name": "@reown/appkit-react-native", + "version": "1.2.3", + "main": "lib/commonjs/index.js", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index.js", + "source": "src/index.ts", + "scripts": { + "build": "bob build", + "clean": "rm -rf lib", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" + }, + "files": [ + "src", + "lib", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": [ + "web3", + "crypto", + "ethereum", + "appkit", + "walletconnect", + "react-native" + ], + "repository": "https://github.com/reown-com/appkit-react-native", + "author": "Reown (https://reown.com)", + "homepage": "https://reown.com/appkit", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reown-com/appkit-react-native/issues" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "dependencies": { + "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-core-react-native": "1.2.3", + "@reown/appkit-siwe-react-native": "1.2.3", + "@reown/appkit-ui-react-native": "1.2.3", + "@walletconnect/universal-provider": "2.19.2", + "valtio": "^1.13.2" + }, + "peerDependencies": { + "react": ">=17", + "react-native": ">=0.68.5", + "react-native-modal": ">=13" + }, + "react-native": "src/index.ts", + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "tsc": "../../node_modules/.bin/tsc" + } + ] + ] + }, + "eslintIgnore": [ + "node_modules/", + "lib/" + ] +} diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts new file mode 100644 index 00000000..4f816642 --- /dev/null +++ b/packages/appkit/src/AppKit.ts @@ -0,0 +1,176 @@ +import { + AccountController, + EventsController, + ModalController, + OptionsController, + RouterController, + TransactionsController, + type Metadata +} from '@reown/appkit-core-react-native'; + +import type { ConnectorType, WalletConnector, BlockchainAdapter } from './adapters/types'; +import { ConnectionController } from './controllers/ConnectionController'; +import { WalletConnectConnector } from './connectors/WalletConnectConnector'; +interface AppKitConfig { + projectId: string; + metadata: Metadata; + adapters: BlockchainAdapter[]; + networks: string[]; + extraConnectors?: WalletConnector[]; +} + +export class AppKit { + private projectId: string; + private metadata: Metadata; + private adapters: BlockchainAdapter[]; + private networks: string[]; + private namespaces: string[]; + private extraConnectors: WalletConnector[]; + + constructor(config: AppKitConfig) { + this.projectId = config.projectId; + this.metadata = config.metadata; + this.adapters = config.adapters; + this.networks = config.networks; + this.namespaces = this.getNamespaces(config.networks); + this.extraConnectors = config.extraConnectors || []; + console.log(this.networks?.length); + + this.initControllers(config); + } + + //TODO: define type for networks + private getNamespaces(networks: any[]): string[] { + // Extract unique namespaces from network identifiers + // Default to 'eip155' if no namespace is found + + return [...new Set(networks.map(network => network.id.split?.(':')[0] || 'eip155'))]; + } + + private async createConnector(type: ConnectorType): Promise { + // Check if an extra connector was provided by the developer + const CustomConnector = this.extraConnectors.find( + connector => connector.constructor.name.toLowerCase() === type.toLowerCase() + ); + + if (CustomConnector) { + return CustomConnector; + } + + return WalletConnectConnector.create({ projectId: this.projectId, metadata: this.metadata }); + } + + async connect(type: ConnectorType, requestedNamespaces?: string[]): Promise { + try { + const connector = await this.createConnector(type); + + //Set connector in available adapters + const adapters = this.adapters.filter( + adapter => requestedNamespaces?.includes(adapter.getSupportedNamespace()) + ); + + if (adapters.length === 0) { + throw new Error('No compatible adapters found for the requested namespaces'); + } + + console.log(adapters); + + adapters.forEach(adapter => { + adapter.setConnector(connector); + this.subscribeToAdapterEvents(adapter); + }); + + // Connect using the connector and get approved namespaces + const approvedNamespaces = await connector.connect(requestedNamespaces ?? this.namespaces); + + // Find adapters that support the approved namespaces + const adapterInstances = adapters.filter( + adapter => approvedNamespaces?.includes(adapter.getSupportedNamespace()) + ); + + // if (adapterInstances.length === 0) { + // throw new Error('No compatible adapters found for the approved namespaces'); + // } + + // Store the connection in supported adapters + adapterInstances.forEach(adapter => { + // adapter.setConnector(connector); + // this.subscribeToAdapterEvents(adapter); + ConnectionController.storeConnection(adapter.getSupportedNamespace(), adapter); + }); + + // Unsubscribe evets from the not connected connectors + const notConnectedAdapters = this.adapters.filter( + adapter => !adapterInstances.includes(adapter) + ); + + notConnectedAdapters.forEach(adapter => { + adapter.removeAllListeners(); + adapter.removeConnector(); + }); + } catch (error) { + console.error('Connection failed:', error); + throw error; + } + } + + private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { + adapter.on('accountsChanged', ({ accounts, namespace }) => { + console.log(`Updating accounts for namespace: ${namespace}`); + ConnectionController.updateAccounts(namespace, accounts); + }); + + adapter.on('chainChanged', ({ chainId, namespace }) => { + console.log(`Chain changed for namespace: ${namespace}`); + ConnectionController.updateChainId(namespace, chainId); + }); + + adapter.on('disconnect', ({ namespace }) => { + console.log(`Disconnect event received for ${namespace}`); + ConnectionController.disconnect(namespace); + }); + } + + private async initControllers(options: AppKitConfig) { + OptionsController.setProjectId(options.projectId); + + if (options.metadata) { + OptionsController.setMetadata(options.metadata); + } + } + + async disconnect(namespace: string): Promise { + console.log('AppKit disconnecting', namespace); + try { + await ConnectionController.disconnect(namespace); + ModalController.close(); + AccountController.setIsConnected(false); + RouterController.reset('Connect'); + TransactionsController.resetTransactions(); + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }); + } catch (error) { + console.error('AppKit:disconnect - error', error); + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_ERROR' + }); + } + } + + getProvider(namespace?: string): T | null { + const connection = + ConnectionController.state.connections[ + namespace ?? ConnectionController.state.activeNamespace + ]; + if (!connection) return null; + + return (connection.adapter as any).currentConnector?.getProvider() as T; + } +} + +export function createAppKit(config: AppKitConfig): AppKit { + return new AppKit(config); +} diff --git a/packages/appkit/src/AppKitContext.tsx b/packages/appkit/src/AppKitContext.tsx new file mode 100644 index 00000000..5974007e --- /dev/null +++ b/packages/appkit/src/AppKitContext.tsx @@ -0,0 +1,30 @@ +import React, { createContext, useContext, type ReactNode } from 'react'; +import { AppKit } from './AppKit'; + +interface AppKitContextType { + appKit: AppKit | null; +} + +const AppKitContext = createContext({ appKit: null }); + +interface AppKitProviderProps { + children: ReactNode; + instance: AppKit; +} + +export const AppKitProvider: React.FC = ({ children, instance }) => { + return {children}; +}; + +export const useAppKit = (): { appKit: AppKit } => { + const context = useContext(AppKitContext); + if (context === undefined) { + throw new Error('useAppKit must be used within an AppKitProvider'); + } + if (!context.appKit) { + // This might happen if the provider is rendered before AppKit is initialized + throw new Error('AppKit instance is not yet available in context.'); + } + + return { appKit: context.appKit }; +}; diff --git a/packages/appkit/src/adapters/types.ts b/packages/appkit/src/adapters/types.ts new file mode 100644 index 00000000..ae7236b8 --- /dev/null +++ b/packages/appkit/src/adapters/types.ts @@ -0,0 +1,100 @@ +import { EventEmitter } from "events"; + +//********** Adapter Types **********// + +export abstract class BlockchainAdapter extends EventEmitter { + public projectId: string; + public connector?: WalletConnector; + public supportedNamespace: string; + + constructor({ projectId, supportedNamespace }: { projectId: string, supportedNamespace: string }) { + super(); + this.projectId = projectId; + this.supportedNamespace = supportedNamespace; + } + + setConnector(connector: WalletConnector) { + this.connector = connector; + } + + removeConnector() { + this.connector = undefined; + } + + abstract disconnect(): Promise; + abstract request(method: string, params?: any[]): Promise; + abstract getSupportedNamespace(): string; +} + + + +export abstract class EVMAdapter extends BlockchainAdapter { + abstract signTransaction(tx: TransactionData): Promise; + abstract getBalance(address: string): Promise; + abstract sendTransaction(tx: TransactionData): Promise; +} + +//********** Connector Types **********// + +export abstract class WalletConnector extends EventEmitter { + public type: ConnectorType; + protected provider: Provider; + protected namespaces?: string[]; + + constructor({ type, provider }: { type: ConnectorType, provider: Provider }) { + super(); + this.type = type; + this.provider = provider; + } + + abstract connect(namespaces?: string[]): Promise; + abstract disconnect(): Promise; + abstract getProvider(): Provider; + abstract getNamespaces(): string[]; +} + +//********** Provider Types **********// + +export interface Provider { + connect(params?: any): Promise; + disconnect(): Promise; + request(args: RequestArguments, chain?: string | undefined, expiry?: number | undefined): Promise; + on(event: string, listener: (args?: any) => void): any; + off(event: string, listener: (args?: any) => void): any; +} + +export interface RequestArguments { + method: string; + params?: unknown[] | Record | object | undefined; +} + +export type ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; + +//********** Others **********// + +export interface TransactionData { + to: string; + value?: string; + data?: string; + [key: string]: any; +} + +export interface SignedTransaction { + raw: string; + [key: string]: any; +} + +export interface TransactionReceipt { + transactionHash: string; + [key: string]: any; +} + + +export interface ConnectionResponse { + accounts: string[]; + chainId: string; + [key: string]: any; +} + + + diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts new file mode 100644 index 00000000..fe7f47e1 --- /dev/null +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -0,0 +1,76 @@ +import { type Metadata, ConnectionController } from '@reown/appkit-core-react-native'; +import { UniversalProvider, type IUniversalProvider } from '@walletconnect/universal-provider'; +import { WalletConnector, type Provider } from '../adapters/types'; + +export class WalletConnectConnector extends WalletConnector { + private constructor(provider: Provider) { + super({ type: 'walletconnect', provider }); + } + + public static async create({ + projectId, + metadata + }: { + projectId: string; + metadata: Metadata; + }): Promise { + const provider = await UniversalProvider.init({ + projectId, + metadata + }); + + //TODO: Check this + return new WalletConnectConnector(provider as Provider); + } + + override disconnect(): Promise { + return this.provider.disconnect(); + } + + async connect(namespaces?: string[]): Promise { + //TODO: use namespaces + this.namespaces = namespaces; + + function onUri(uri: string) { + ConnectionController.setWcUri(uri); + } + + this.provider.on('display_uri', onUri); + + const session = await this.provider.connect({ + optionalNamespaces: { + eip155: { + methods: [ + 'eth_sendTransaction', + 'eth_signTransaction', + 'eth_sign', + 'personal_sign', + 'eth_signTypedData' + ], + chains: ['eip155:1'], + events: ['chainChanged', 'accountsChanged'] + }, + solana: { + methods: ['solana_signMessage'], + chains: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + events: ['chainChanged', 'accountsChanged'] + } + } + }); + + const approvedNamespaces = Object.keys(session?.namespaces ?? {}); + console.log('session', approvedNamespaces); + + this.provider.off('display_uri', onUri); + + return approvedNamespaces; + } + + override getProvider(): Provider { + return this.provider; + } + + override getNamespaces(): string[] { + return this.namespaces ?? []; + } +} diff --git a/packages/appkit/src/controllers/ConnectionController.ts b/packages/appkit/src/controllers/ConnectionController.ts new file mode 100644 index 00000000..043eb7eb --- /dev/null +++ b/packages/appkit/src/controllers/ConnectionController.ts @@ -0,0 +1,110 @@ +import { proxy } from 'valtio'; +import type { BlockchainAdapter } from '../adapters/types'; + +interface ConnectionState { + accounts: string[]; + balances: Record; + activeChainId: string; + adapter: BlockchainAdapter; +} + +interface State { + activeNamespace: string; + connections: Record; +} + +const state = proxy({ + activeNamespace: 'eip155', + connections: {} +}); + +function setActiveNamespace(namespace: string) { + state.activeNamespace = namespace; +} + +function storeConnection( + namespace: string, + adapter: BlockchainAdapter, + accounts: string[] = [], + chainId: string = '' +) { + state.connections[namespace] = { + accounts, + balances: {}, + activeChainId: chainId, + adapter + }; +} + +function updateAccounts(namespace: string, accounts: string[]) { + const connection = state.connections[namespace]; + if (!connection) { + return; + } + connection.accounts = accounts; +} + +function updateBalances(namespace: string, balances: Record) { + const connection = state.connections[namespace]; + if (!connection) { + return; + } + connection.balances = balances; +} + +function updateChainId(namespace: string, chainId: string) { + const connection = state.connections[namespace]; + if (!connection) { + return; + } + connection.activeChainId = chainId; +} + +async function disconnect(namespace: string) { + const connection = state.connections[namespace]; + if (!connection) return; + + console.log('ConnectionController:disconnect - connection', connection); + + // Get the current connector from the adapter + const connector = connection.adapter.connector; + if (!connector) return; + + console.log('ConnectionController:disconnect - connector', connector); + + // Find all namespaces that use the same connector + const namespacesUsingConnector = Object.keys(state.connections).filter( + ns => state.connections[ns]?.adapter.connector === connector + ); + + console.log( + 'ConnectionController:disconnect - namespacesUsingConnector', + namespacesUsingConnector + ); + + // Unsubscribe all event listeners from the adapter + namespacesUsingConnector.forEach(ns => { + const _connection = state.connections[ns]; + if (_connection?.adapter) { + _connection.adapter.removeAllListeners(); + } + }); + + // Disconnect the adapter + await connection.adapter.disconnect(); + + // Remove all namespaces that used this connector + namespacesUsingConnector.forEach(ns => { + delete state.connections[ns]; + }); +} + +export const ConnectionController = { + state, + setActiveNamespace, + storeConnection, + updateAccounts, + updateBalances, + updateChainId, + disconnect +}; diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 444c35d5..766fa0a2 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -20,3 +20,9 @@ export type { LibraryOptions, ScaffoldOptions } from './client'; export type * from '@reown/appkit-core-react-native'; export { CoreHelperUtil } from '@reown/appkit-core-react-native'; +export * from './AppKit'; +export * from './adapters/types'; +export { AppKitProvider, useAppKit } from './AppKitContext'; +export { ConnectionController } from './controllers/ConnectionController'; +export { useProvider } from './hooks/useProvider'; +export { WalletConnectConnector } from './connectors/WalletConnectConnector'; \ No newline at end of file diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 5cd83abb..0f49b7a2 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -8,7 +8,6 @@ import { ConnectionController, ConnectorController, CoreHelperUtil, - ConnectionUtil, EventsController, ModalController, NetworkController, @@ -30,10 +29,12 @@ import { Spacing, ListItem } from '@reown/appkit-ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; +import { useAppKit } from '../../AppKitContext'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { AuthButtons } from './components/auth-buttons'; +import styles from './styles'; export function AccountDefaultView() { const { @@ -60,10 +61,11 @@ export function AccountDefaultView() { const showBack = history.length > 1; const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); const { padding } = useCustomDimensions(); - + const { appKit } = useAppKit(); async function onDisconnect() { setDisconnecting(true); - await ConnectionUtil.disconnect(); + //TODO: USE ACTIVE NAMESPACE + await appKit?.disconnect('eip155'); setDisconnecting(false); } diff --git a/packages/appkit/src/views/w3m-connect-view/index.tsx b/packages/appkit/src/views/w3m-connect-view/index.tsx index f1222cc8..5ef2fb49 100644 --- a/packages/appkit/src/views/w3m-connect-view/index.tsx +++ b/packages/appkit/src/views/w3m-connect-view/index.tsx @@ -31,7 +31,8 @@ export function ConnectView() { const { padding } = useCustomDimensions(); const { keyboardShown, keyboardHeight } = useKeyboard(); - const isWalletConnectEnabled = connectors.some(c => c.type === 'WALLET_CONNECT'); + // const isWalletConnectEnabled = connectors.some(c => c.type === 'WALLET_CONNECT'); + const isWalletConnectEnabled = true; const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); const isCoinbaseEnabled = connectors.some(c => c.type === 'COINBASE'); const isEmailEnabled = isAuthEnabled && features?.email; diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 85337016..5c386161 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -15,7 +15,7 @@ import { ConnectorController } from '@reown/appkit-core-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; -import { useAppKit } from '@reown/appkit-react-native'; +import { useAppKit } from '../../AppKitContext'; import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; @@ -47,12 +47,14 @@ export function ConnectingView() { const initializeConnection = async (retry = false) => { try { const { wcPairingExpiry } = ConnectionController.state; - const { data: routeData } = RouterController.state; + // const { data: routeData } = RouterController.state; if (retry || CoreHelperUtil.isPairingExpired(wcPairingExpiry)) { ConnectionController.setWcError(false); // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); - appKit?.connect('walletconnect', ['eip155']); - await ConnectionController.state.wcPromise; + const wcPromise = appKit?.connect('walletconnect', ['eip155']); + ConnectionController.setWcPromise(wcPromise); + await wcPromise; + // await ConnectionController.state.wcPromise; ConnectorController.setConnectedConnector('WALLET_CONNECT'); AccountController.setIsConnected(true); diff --git a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx index f2074e50..9e17494a 100644 --- a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx @@ -6,13 +6,15 @@ import { ApiController, AssetUtil, CoreHelperUtil, - ConnectionUtil, EventsController, NetworkController, NetworkUtil, type CaipNetwork, type NetworkControllerState } from '@reown/appkit-core-react-native'; + +// import { useAppKit } from '@reown/appkit-react-native'; +import { useAppKit } from '../../AppKitContext'; import styles from './styles'; export function UnsupportedChainView() { @@ -22,6 +24,7 @@ export function UnsupportedChainView() { const [disconnecting, setDisconnecting] = useState(false); const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); const imageHeaders = ApiController._getApiHeaders(); + const { appKit } = useAppKit(); const onNetworkPress = async (network: CaipNetwork) => { const result = await NetworkUtil.handleNetworkSwitch(network); @@ -38,7 +41,8 @@ export function UnsupportedChainView() { const onDisconnect = async () => { setDisconnecting(true); - await ConnectionUtil.disconnect(); + //TODO: USE ACTIVE NAMESPACE + await appKit?.disconnect('eip155'); setDisconnecting(false); }; diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index d36182b7..5f1000cc 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -120,6 +120,15 @@ export const ConnectionController = { state.pressedWallet = undefined; }, + setWcPromise(wcPromise: ConnectionControllerState['wcPromise']) { + state.wcPromise = wcPromise; + }, + + setWcUri(wcUri: ConnectionControllerState['wcUri']) { + state.wcUri = wcUri; + state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry(); + }, + setRecentWallets(wallets: ConnectionControllerState['recentWallets']) { state.recentWallets = wallets; }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8c196a7a..b3fc085b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -62,7 +62,6 @@ export { WebviewController, type WebviewControllerState } from './controllers/We // -- Utils ------------------------------------------------------------------- export { ApiUtil } from './utils/ApiUtil'; export { AssetUtil } from './utils/AssetUtil'; -export { ConnectionUtil } from './utils/ConnectionUtil'; export { ConstantsUtil } from './utils/ConstantsUtil'; export { CoreHelperUtil } from './utils/CoreHelperUtil'; export { StorageUtil } from './utils/StorageUtil'; diff --git a/packages/core/src/utils/ConnectionUtil.ts b/packages/core/src/utils/ConnectionUtil.ts deleted file mode 100644 index 0803b699..00000000 --- a/packages/core/src/utils/ConnectionUtil.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AccountController } from '../controllers/AccountController'; -import { ConnectionController } from '../controllers/ConnectionController'; -import { EventsController } from '../controllers/EventsController'; -import { ModalController } from '../controllers/ModalController'; -import { RouterController } from '../controllers/RouterController'; -import { TransactionsController } from '../controllers/TransactionsController'; - -export const ConnectionUtil = { - async disconnect() { - try { - await ConnectionController.disconnect(); - ModalController.close(); - AccountController.setIsConnected(false); - RouterController.reset('Connect'); - TransactionsController.resetTransactions(); - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_SUCCESS' - }); - } catch (error) { - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_ERROR' - }); - } - } -}; diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 9618b79d..0212d5f6 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-react-native": "workspace:*", "@reown/appkit-scaffold-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts new file mode 100644 index 00000000..66a232a1 --- /dev/null +++ b/packages/ethers/src/adapter.ts @@ -0,0 +1,99 @@ +import { + EVMAdapter, + WalletConnector, + type SignedTransaction, + type TransactionData, + type TransactionReceipt +} from '@reown/appkit-react-native'; + +export class EthersAdapter extends EVMAdapter { + private static supportedNamespace: string = 'eip155'; + + constructor(configParams: { projectId: string }) { + super({ + projectId: configParams.projectId, + supportedNamespace: EthersAdapter.supportedNamespace + }); + } + + async signTransaction(tx: TransactionData): Promise { + if (!this.connector) throw new Error('No active connector'); + + return this.request('eth_signTransaction', [tx]) as Promise; + } + + async getBalance(address: string): Promise { + if (!this.connector) throw new Error('No active connector'); + + return this.request('eth_getBalance', [address, 'latest']) as Promise; + } + + sendTransaction(/*tx: TransactionData*/): Promise { + throw new Error('Method not implemented.'); + } + + disconnect(): Promise { + if (!this.connector) throw new Error('EthersAdapter:disconnect - No active connector'); + + return this.connector.disconnect(); + } + + async request(method: string, params?: any[]) { + if (!this.connector) throw new Error('No active connector'); + const provider = this.connector.getProvider(); + + return provider.request({ method, params }); + } + + getSupportedNamespace(): string { + return EthersAdapter.supportedNamespace; + } + + onChainChanged(chainId: string): void { + console.log('adapter chainChanged', chainId); + this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + } + + onAccountsChanged(accounts: string[]): void { + console.log('adapter accountsChanged', accounts); + // Emit this change to AppKit with the corresponding namespace. + this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + } + + onDisconnect(): void { + console.log('adapter onDisconnect'); + this.emit('disconnect', { namespace: this.getSupportedNamespace() }); + + //the connector might be shared between adapters. Validate this + const provider = this.connector?.getProvider(); + if (provider) { + provider.off('chainChanged', this.onChainChanged.bind(this)); + provider.off('accountsChanged', this.onAccountsChanged.bind(this)); + provider.off('disconnect', this.onDisconnect.bind(this)); + console.log('Removed listeners from provider on disconnect'); + } + + this.connector = undefined; + } + + override setConnector(connector: WalletConnector): void { + super.setConnector(connector); + this.subscribeToEvents(); + } + + subscribeToEvents(): void { + const provider = this.connector?.getProvider(); + if (!provider) return; + + console.log('subscribing to events'); + provider.on('chainChanged', this.onChainChanged.bind(this)); + provider.on('accountsChanged', this.onAccountsChanged.bind(this)); + provider.on('disconnect', this.onDisconnect.bind(this)); + //@ts-ignore + console.log('subscribed to events', provider?.events); + + provider.on('accountsChanged', () => { + console.log('accountsChanged'); + }); + } +} diff --git a/packages/ethers/src/index.tsx b/packages/ethers/src/index.tsx index 2bd2a569..3781f889 100644 --- a/packages/ethers/src/index.tsx +++ b/packages/ethers/src/index.tsx @@ -16,11 +16,15 @@ export { defaultConfig } from './utils/defaultConfig'; import type { AppKitOptions } from './client'; import { AppKit } from './client'; +import { EthersAdapter } from './adapter'; +export { EthersAdapter }; + // -- Types ------------------------------------------------------------------- export type { AppKitOptions } from './client'; type OpenOptions = Parameters[0]; + // -- Setup ------------------------------------------------------------------- let modal: AppKit | undefined; diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx index 5cd83abb..c352bf02 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -8,7 +8,6 @@ import { ConnectionController, ConnectorController, CoreHelperUtil, - ConnectionUtil, EventsController, ModalController, NetworkController, @@ -63,7 +62,7 @@ export function AccountDefaultView() { async function onDisconnect() { setDisconnecting(true); - await ConnectionUtil.disconnect(); + // await ConnectionUtil.disconnect(); setDisconnecting(false); } diff --git a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx b/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx index f2074e50..3020ab02 100644 --- a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx @@ -6,7 +6,6 @@ import { ApiController, AssetUtil, CoreHelperUtil, - ConnectionUtil, EventsController, NetworkController, NetworkUtil, @@ -38,7 +37,7 @@ export function UnsupportedChainView() { const onDisconnect = async () => { setDisconnecting(true); - await ConnectionUtil.disconnect(); + // await ConnectionUtil.disconnect(); setDisconnecting(false); }; diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts new file mode 100644 index 00000000..4ae17e16 --- /dev/null +++ b/packages/wagmi/src/adapter.ts @@ -0,0 +1,93 @@ +import { + EVMAdapter, + WalletConnector, + type SignedTransaction, + type TransactionData, + type TransactionReceipt +} from '@reown/appkit-react-native'; +import { + type Config, + type CreateConfigParameters, + type CreateConnectorFn, + createConfig +} from '@wagmi/core'; +import type { Chain } from 'wagmi/chains'; +import { getTransport } from './utils/helpers'; + +type ConfigParams = Partial & { + networks: [Chain, ...Chain[]]; + projectId: string; +}; + +export class WagmiAdapter extends EVMAdapter { + private static supportedNamespace: string = 'eip155'; + public wagmiChains: readonly [Chain, ...Chain[]] | undefined; + public wagmiConfig!: Config; + + constructor(configParams: ConfigParams) { + super({ + projectId: configParams.projectId, + supportedNamespace: WagmiAdapter.supportedNamespace + }); + this.wagmiChains = configParams.networks; + this.wagmiConfig = this.createConfig(configParams); + } + + private createConfig(configParams: ConfigParams) { + const connectors: CreateConnectorFn[] = []; + + const transportsArr = configParams.networks.map(chain => [ + chain.id, + getTransport({ chainId: chain.id, projectId: configParams.projectId }) + ]); + + const transports = Object.fromEntries(transportsArr); + + // const storage = createStorage({ storage: StorageUtil }); + + return createConfig({ + chains: configParams.networks, + connectors, + transports, + // storage, + multiInjectedProviderDiscovery: false + // ...wagmiConfig + }); + } + + async signTransaction(tx: TransactionData): Promise { + if (!this.connector) throw new Error('No active connector'); + + return this.request('eth_signTransaction', [tx]) as Promise; + } + + async getBalance(address: string): Promise { + if (!this.connector) throw new Error('No active connector'); + + return this.request('eth_getBalance', [address, 'latest']) as Promise; + } + + sendTransaction(/*tx: TransactionData*/): Promise { + throw new Error('Method not implemented.'); + } + + disconnect(): Promise { + throw new Error('Method not implemented.'); + } + + async request(method: string, params?: any[]) { + if (!this.connector) throw new Error('No active connector'); + const provider = this.connector.getProvider(); + + return provider.request({ method, params }); + } + + getSupportedNamespace(): string { + return WagmiAdapter.supportedNamespace; + } + + override setConnector(connector: WalletConnector): void { + super.setConnector(connector); + // this.wagmiConfig.connectors = [connector]; + } +} diff --git a/packages/wagmi/src/connectors/WalletConnectConnector.ts b/packages/wagmi/src/connectors/WalletConnectConnector.ts index 4c82bbb4..4b732c1d 100644 --- a/packages/wagmi/src/connectors/WalletConnectConnector.ts +++ b/packages/wagmi/src/connectors/WalletConnectConnector.ts @@ -22,7 +22,7 @@ import { EthereumProvider } from '@walletconnect/ethereum-provider'; /**** Types ****/ -type WalletConnectConnector = Connector & { +type IWalletConnectConnector = Connector & { onDisplayUri(uri: string): void; onSessionDelete(data: { topic: string }): void; }; @@ -114,18 +114,20 @@ export function walletConnect(parameters: WalletConnectParameters) { let providerPromise: Promise; const NAMESPACE = 'eip155'; - let accountsChanged: WalletConnectConnector['onAccountsChanged'] | undefined; - let chainChanged: WalletConnectConnector['onChainChanged'] | undefined; - let connect: WalletConnectConnector['onConnect'] | undefined; - let displayUri: WalletConnectConnector['onDisplayUri'] | undefined; - let sessionDelete: WalletConnectConnector['onSessionDelete'] | undefined; - let disconnect: WalletConnectConnector['onDisconnect'] | undefined; + let accountsChanged: IWalletConnectConnector['onAccountsChanged'] | undefined; + let chainChanged: IWalletConnectConnector['onChainChanged'] | undefined; + let connect: IWalletConnectConnector['onConnect'] | undefined; + let displayUri: IWalletConnectConnector['onDisplayUri'] | undefined; + let sessionDelete: IWalletConnectConnector['onSessionDelete'] | undefined; + let disconnect: IWalletConnectConnector['onDisconnect'] | undefined; + // let genericConnector: WalletConnectConnector | undefined; return createConnector(config => ({ id: 'walletConnect', name: 'WalletConnect', type: walletConnect.type, async setup() { + // genericConnector = WalletConnectConnector.create({ projectId: parameters.projectId, metadata: parameters.metadata }); const provider = await this.getProvider().catch(() => null); if (!provider) return; if (!connect) { @@ -139,6 +141,7 @@ export function walletConnect(parameters: WalletConnectParameters) { }, async connect({ chainId, ...rest } = {}) { try { + const provider = await this.getProvider(); if (!provider) throw new ProviderNotFoundError(); if (!displayUri) { @@ -291,7 +294,7 @@ export function walletConnect(parameters: WalletConnectParameters) { // If the chains are stale on the session, then the connector is unauthorized. const isChainsStale = await this.isChainsStale(); if (isChainsStale && provider.session) { - await provider.disconnect().catch(() => {}); + await provider.disconnect().catch(() => { }); return false; } diff --git a/packages/wagmi/src/index.tsx b/packages/wagmi/src/index.tsx index 51872665..78583101 100644 --- a/packages/wagmi/src/index.tsx +++ b/packages/wagmi/src/index.tsx @@ -13,7 +13,7 @@ import { ConstantsUtil } from '@reown/appkit-common-react-native'; export { defaultWagmiConfig } from './utils/defaultWagmiConfig'; import type { AppKitOptions } from './client'; import { AppKit } from './client'; - +import { WagmiAdapter } from './adapter'; // -- Types ------------------------------------------------------------------- export type { AppKitOptions } from './client'; @@ -22,6 +22,8 @@ type OpenOptions = Parameters[0]; // -- Setup ------------------------------------------------------------------- let modal: AppKit | undefined; +export { WagmiAdapter }; + export function createAppKit(options: AppKitOptions) { if (!modal) { modal = new AppKit({ diff --git a/yarn.lock b/yarn.lock index 3c48d4a9..623f2735 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: fdd647604e8fac6204921888aaf5a6bc65eabf0d2921bc5f93b64d01f4bc33ead167c1445f7de05468d05cd92ac31b74c68d2be840c62b79d73693308f885c06 + languageName: node + linkType: hard + "@adraffy/ens-normalize@npm:^1.10.1": version: 1.11.0 resolution: "@adraffy/ens-normalize@npm:1.11.0" @@ -100,6 +107,8 @@ __metadata: "@react-native-async-storage/async-storage": "npm:2.1.2" "@react-native-community/netinfo": "npm:11.4.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.2.3" + "@reown/appkit-ethers-react-native": "workspace:*" + "@reown/appkit-react-native": "workspace:*" "@reown/appkit-wagmi-react-native": "npm:1.2.3" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" @@ -109,6 +118,7 @@ __metadata: "@types/react": "npm:~18.2.79" "@walletconnect/react-native-compat": "npm:2.19.1" babel-plugin-module-resolver: "npm:^5.0.0" + ethers: "npm:6.13.5" expo: "npm:^52.0.38" expo-application: "npm:~6.0.2" expo-clipboard: "npm:~7.0.1" @@ -7198,11 +7208,12 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-ethers-react-native@workspace:packages/ethers": +"@reown/appkit-ethers-react-native@workspace:*, @reown/appkit-ethers-react-native@workspace:packages/ethers": version: 0.0.0-use.local resolution: "@reown/appkit-ethers-react-native@workspace:packages/ethers" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-react-native": "workspace:*" "@reown/appkit-scaffold-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" @@ -7240,6 +7251,23 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-react-native@npm:1.2.3, @reown/appkit-react-native@workspace:*, @reown/appkit-react-native@workspace:packages/appkit": + version: 0.0.0-use.local + resolution: "@reown/appkit-react-native@workspace:packages/appkit" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-core-react-native": "npm:1.2.3" + "@reown/appkit-siwe-react-native": "npm:1.2.3" + "@reown/appkit-ui-react-native": "npm:1.2.3" + "@walletconnect/universal-provider": "npm:2.19.2" + valtio: "npm:^1.13.2" + peerDependencies: + react: ">=17" + react-native: ">=0.68.5" + react-native-modal: ">=13" + languageName: unknown + linkType: soft + "@reown/appkit-scaffold-react-native@npm:1.2.3, @reown/appkit-scaffold-react-native@workspace:packages/scaffold": version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-react-native@workspace:packages/scaffold" @@ -7295,6 +7323,7 @@ __metadata: resolution: "@reown/appkit-wagmi-react-native@workspace:packages/wagmi" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-react-native": "npm:1.2.3" "@reown/appkit-scaffold-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" @@ -8714,6 +8743,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:22.7.5": + version: 22.7.5 + resolution: "@types/node@npm:22.7.5" + dependencies: + undici-types: "npm:~6.19.2" + checksum: cf11f74f1a26053ec58066616e3a8685b6bcd7259bc569738b8f752009f9f0f7f85a1b2d24908e5b0f752482d1e8b6babdf1fbb25758711ec7bb9500bfcd6e60 + languageName: node + linkType: hard + "@types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" @@ -9275,6 +9313,31 @@ __metadata: languageName: node linkType: hard +"@walletconnect/core@npm:2.19.2": + version: 2.19.2 + resolution: "@walletconnect/core@npm:2.19.2" + dependencies: + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/jsonrpc-ws-connection": "npm:1.0.16" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.1.0" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.19.2" + "@walletconnect/utils": "npm:2.19.2" + "@walletconnect/window-getters": "npm:1.0.1" + es-toolkit: "npm:1.33.0" + events: "npm:3.3.0" + uint8arrays: "npm:3.1.0" + checksum: 6ebc3c192fb667d4cbaa435c7391fd21b857508f0e3a43cf2c1fb10626dbe0ef374e01988330916dbeb8ae2fcaac4f56881af482dc37f4b1d1d39e63feb0aed3 + languageName: node + linkType: hard + "@walletconnect/environment@npm:^1.0.1": version: 1.0.1 resolution: "@walletconnect/environment@npm:1.0.1" @@ -9565,6 +9628,23 @@ __metadata: languageName: node linkType: hard +"@walletconnect/sign-client@npm:2.19.2": + version: 2.19.2 + resolution: "@walletconnect/sign-client@npm:2.19.2" + dependencies: + "@walletconnect/core": "npm:2.19.2" + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.19.2" + "@walletconnect/utils": "npm:2.19.2" + events: "npm:3.3.0" + checksum: 9d26928d3f52b068362e271ea4ffafb23bb077e265a792e420c1045bb38137a53681b82003e6a04108b4ba1a2ac183b759d42deaf9f4e0f3c9aabb1b0b632567 + languageName: node + linkType: hard + "@walletconnect/time@npm:1.0.2, @walletconnect/time@npm:^1.0.2": version: 1.0.2 resolution: "@walletconnect/time@npm:1.0.2" @@ -9602,6 +9682,20 @@ __metadata: languageName: node linkType: hard +"@walletconnect/types@npm:2.19.2": + version: 2.19.2 + resolution: "@walletconnect/types@npm:2.19.2" + dependencies: + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + events: "npm:3.3.0" + checksum: aa539e73851c0d744982119bf137555d1649f4b9aae6c4f2e296c85fe0a92b371334bb137329a0eb1c828de22f81991c91ce8e5975ee6a381bc03b864ed0dd9d + languageName: node + linkType: hard + "@walletconnect/universal-provider@npm:2.17.3": version: 2.17.3 resolution: "@walletconnect/universal-provider@npm:2.17.3" @@ -9642,6 +9736,26 @@ __metadata: languageName: node linkType: hard +"@walletconnect/universal-provider@npm:2.19.2": + version: 2.19.2 + resolution: "@walletconnect/universal-provider@npm:2.19.2" + dependencies: + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/sign-client": "npm:2.19.2" + "@walletconnect/types": "npm:2.19.2" + "@walletconnect/utils": "npm:2.19.2" + es-toolkit: "npm:1.33.0" + events: "npm:3.3.0" + checksum: e4d64e5e95ee56a0babe62242c636d1bc691ee9acd2d46c1632117bf88ec0f48387224493387c3d397f2e0c030d2c64385f592ad0e92d8f4a50406058697ddb5 + languageName: node + linkType: hard + "@walletconnect/utils@npm:2.17.3": version: 2.17.3 resolution: "@walletconnect/utils@npm:2.17.3" @@ -9695,6 +9809,31 @@ __metadata: languageName: node linkType: hard +"@walletconnect/utils@npm:2.19.2": + version: 2.19.2 + resolution: "@walletconnect/utils@npm:2.19.2" + dependencies: + "@noble/ciphers": "npm:1.2.1" + "@noble/curves": "npm:1.8.1" + "@noble/hashes": "npm:1.7.1" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.1.0" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.19.2" + "@walletconnect/window-getters": "npm:1.0.1" + "@walletconnect/window-metadata": "npm:1.0.1" + bs58: "npm:6.0.0" + detect-browser: "npm:5.3.0" + query-string: "npm:7.1.3" + uint8arrays: "npm:3.1.0" + viem: "npm:2.23.2" + checksum: 21eca1f5b94bfe90d329285388b9676de6f4f0a60dbf12b68d76448df24ef707b5ee0000a4aa38843baee14d79e2f6a7e15aa371d50eadf96f925ffdd1c36ac1 + languageName: node + linkType: hard + "@walletconnect/window-getters@npm:1.0.1, @walletconnect/window-getters@npm:^1.0.1": version: 1.0.1 resolution: "@walletconnect/window-getters@npm:1.0.1" @@ -10849,6 +10988,13 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^5.0.0": + version: 5.0.1 + resolution: "base-x@npm:5.0.1" + checksum: 4ab6b02262b4fd499b147656f63ce7328bd5f895450401ce58a2f9e87828aea507cf0c320a6d8725389f86e8a48397562661c0bca28ef3276a22821b30f7a713 + languageName: node + linkType: hard + "base64-js@npm:^1.2.3, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -11141,6 +11287,15 @@ __metadata: languageName: node linkType: hard +"bs58@npm:6.0.0": + version: 6.0.0 + resolution: "bs58@npm:6.0.0" + dependencies: + base-x: "npm:^5.0.0" + checksum: 61910839746625ee4f69369f80e2634e2123726caaa1da6b3bcefcf7efcd9bdca86603360fed9664ffdabe0038c51e542c02581c72ca8d44f60329fe1a6bc8f4 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -12503,6 +12658,15 @@ __metadata: languageName: node linkType: hard +"derive-valtio@npm:0.1.0": + version: 0.1.0 + resolution: "derive-valtio@npm:0.1.0" + peerDependencies: + valtio: "*" + checksum: c64ed74e2bc140dafe080a58fd499f803cebaa89774b5d2bd0fea8054728912f1c715c5c370b4ff01ab9908b64828a7f8f0c968dc9efd0aee037e5679dd804d8 + languageName: node + linkType: hard + "destr@npm:^2.0.1, destr@npm:^2.0.2": version: 2.0.2 resolution: "destr@npm:2.0.2" @@ -13127,6 +13291,18 @@ __metadata: languageName: node linkType: hard +"es-toolkit@npm:1.33.0": + version: 1.33.0 + resolution: "es-toolkit@npm:1.33.0" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + checksum: 4c8dea3167a813070812e5c3f827fb677b4729b622c209cfad68dd5b449a008df6f3b515e675a4a8519618f52b87fe1d157c320668be871165f934a15c1d2f37 + languageName: node + linkType: hard + "esbuild-register@npm:^3.5.0": version: 3.6.0 resolution: "esbuild-register@npm:3.6.0" @@ -13699,6 +13875,21 @@ __metadata: languageName: node linkType: hard +"ethers@npm:6.13.5": + version: 6.13.5 + resolution: "ethers@npm:6.13.5" + dependencies: + "@adraffy/ens-normalize": "npm:1.10.1" + "@noble/curves": "npm:1.2.0" + "@noble/hashes": "npm:1.3.2" + "@types/node": "npm:22.7.5" + aes-js: "npm:4.0.0-beta.5" + tslib: "npm:2.7.0" + ws: "npm:8.17.1" + checksum: 64bc7b8907de199392b8a88c15c9a085892919cff7efa2e5326abc7fe5c426001726c51d91e10c74e5fc5e2547188297ce4127f6e52ea42a97ade0b2ae474677 + languageName: node + linkType: hard + "event-target-shim@npm:^5.0.0, event-target-shim@npm:^5.0.1": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -20018,6 +20209,13 @@ __metadata: languageName: node linkType: hard +"proxy-compare@npm:2.6.0": + version: 2.6.0 + resolution: "proxy-compare@npm:2.6.0" + checksum: afd82ddc83f34af6116a5e222399fb7e626a1a443feb9d70e7a1af65561c97f670c5c8c4bde53bfe12a7cda7ef00f9863d265f3a0e949ff031a9869ecc5feb0c + languageName: node + linkType: hard + "pump@npm:^3.0.0": version: 3.0.0 resolution: "pump@npm:3.0.0" @@ -22660,6 +22858,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.7.0": + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 + languageName: node + linkType: hard + "tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0": version: 2.6.1 resolution: "tslib@npm:2.6.1" @@ -23440,6 +23645,25 @@ __metadata: languageName: node linkType: hard +"valtio@npm:^1.13.2": + version: 1.13.2 + resolution: "valtio@npm:1.13.2" + dependencies: + derive-valtio: "npm:0.1.0" + proxy-compare: "npm:2.6.0" + use-sync-external-store: "npm:1.2.0" + peerDependencies: + "@types/react": ">=16.8" + react: ">=16.8" + peerDependenciesMeta: + "@types/react": + optional: true + react: + optional: true + checksum: 514b8509308056e474c7d3ccfbbd6ac8e589740a92c53c53c78591a217e14da0694bd67f54195d8ec46920b6aab89eebab3c78c98c33d814b3606cdaacb6489b + languageName: node + linkType: hard + "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" From 4c95fae2415574da4db9e27a3473238ab55c8cdb Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:20:40 -0300 Subject: [PATCH 03/91] chore: moved connectionscontroller to core, moved types to common --- packages/appkit/src/AppKit.ts | 88 ++++++------ packages/appkit/src/adapters/types.ts | 100 -------------- .../src/connectors/WalletConnectConnector.ts | 42 ++---- .../src/controllers/ConnectionController.ts | 110 --------------- packages/appkit/src/hooks/useProvider.ts | 8 +- packages/appkit/src/index.ts | 4 +- packages/appkit/src/utils/ConnectorUtil.ts | 29 ++-- .../src/views/w3m-connecting-view/index.tsx | 23 +++- packages/common/src/utils/TypeUtil.ts | 118 +++++++++++++++++ .../src/controllers/ConnectionsController.ts | 125 ++++++++++++++++++ packages/core/src/index.ts | 5 + packages/core/src/utils/TypeUtil.ts | 2 + packages/ethers/src/adapter.ts | 17 +-- packages/wagmi/src/adapter.ts | 2 +- 14 files changed, 363 insertions(+), 310 deletions(-) delete mode 100644 packages/appkit/src/adapters/types.ts delete mode 100644 packages/appkit/src/controllers/ConnectionController.ts create mode 100644 packages/core/src/controllers/ConnectionsController.ts diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 4f816642..2f59812a 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -2,20 +2,26 @@ import { AccountController, EventsController, ModalController, + ConnectionsController, OptionsController, RouterController, TransactionsController, type Metadata } from '@reown/appkit-core-react-native'; -import type { ConnectorType, WalletConnector, BlockchainAdapter } from './adapters/types'; -import { ConnectionController } from './controllers/ConnectionController'; +import type { + WalletConnector, + BlockchainAdapter, + ProposalNamespaces, + New_ConnectorType + // Namespaces, +} from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; interface AppKitConfig { projectId: string; metadata: Metadata; adapters: BlockchainAdapter[]; - networks: string[]; + networks: any[]; extraConnectors?: WalletConnector[]; } @@ -23,8 +29,8 @@ export class AppKit { private projectId: string; private metadata: Metadata; private adapters: BlockchainAdapter[]; - private networks: string[]; - private namespaces: string[]; + private networks: any[]; //TODO: define type for networks + // private namespaces: Namespaces; private extraConnectors: WalletConnector[]; constructor(config: AppKitConfig) { @@ -32,22 +38,14 @@ export class AppKit { this.metadata = config.metadata; this.adapters = config.adapters; this.networks = config.networks; - this.namespaces = this.getNamespaces(config.networks); + // this.namespaces = this.getNamespaces(config.networks); this.extraConnectors = config.extraConnectors || []; console.log(this.networks?.length); this.initControllers(config); } - //TODO: define type for networks - private getNamespaces(networks: any[]): string[] { - // Extract unique namespaces from network identifiers - // Default to 'eip155' if no namespace is found - - return [...new Set(networks.map(network => network.id.split?.(':')[0] || 'eip155'))]; - } - - private async createConnector(type: ConnectorType): Promise { + private async createConnector(type: New_ConnectorType): Promise { // Check if an extra connector was provided by the developer const CustomConnector = this.extraConnectors.find( connector => connector.constructor.name.toLowerCase() === type.toLowerCase() @@ -60,43 +58,53 @@ export class AppKit { return WalletConnectConnector.create({ projectId: this.projectId, metadata: this.metadata }); } - async connect(type: ConnectorType, requestedNamespaces?: string[]): Promise { + async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { try { const connector = await this.createConnector(type); //Set connector in available adapters - const adapters = this.adapters.filter( - adapter => requestedNamespaces?.includes(adapter.getSupportedNamespace()) + const adapters = this.adapters.filter(adapter => + Object.keys(requestedNamespaces ?? {}).includes(adapter.getSupportedNamespace()) ); if (adapters.length === 0) { throw new Error('No compatible adapters found for the requested namespaces'); } - console.log(adapters); - adapters.forEach(adapter => { adapter.setConnector(connector); this.subscribeToAdapterEvents(adapter); }); // Connect using the connector and get approved namespaces - const approvedNamespaces = await connector.connect(requestedNamespaces ?? this.namespaces); + const approvedNamespaces = await connector.connect(requestedNamespaces); + + if (!approvedNamespaces) { + throw new Error('No approved namespaces found'); + } + + console.log('CONNECTED - approvedNamespaces', approvedNamespaces); // Find adapters that support the approved namespaces - const adapterInstances = adapters.filter( - adapter => approvedNamespaces?.includes(adapter.getSupportedNamespace()) + const adapterInstances = adapters.filter(adapter => + Object.keys(approvedNamespaces ?? {}).includes(adapter.getSupportedNamespace()) ); - // if (adapterInstances.length === 0) { - // throw new Error('No compatible adapters found for the approved namespaces'); - // } - - // Store the connection in supported adapters - adapterInstances.forEach(adapter => { - // adapter.setConnector(connector); - // this.subscribeToAdapterEvents(adapter); - ConnectionController.storeConnection(adapter.getSupportedNamespace(), adapter); + // Store the connection of supported adapters + adapterInstances.forEach(async adapter => { + const namespace = adapter.getSupportedNamespace(); + const accounts = approvedNamespaces[namespace]?.accounts ?? []; + const chains = approvedNamespaces[namespace]?.chains ?? []; + const methods = approvedNamespaces[namespace]?.methods ?? []; + const events = approvedNamespaces[namespace]?.events ?? []; + ConnectionsController.storeConnection({ + namespace, + adapter, + accounts, + chains, + methods, + events + }); }); // Unsubscribe evets from the not connected connectors @@ -117,17 +125,17 @@ export class AppKit { private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { adapter.on('accountsChanged', ({ accounts, namespace }) => { console.log(`Updating accounts for namespace: ${namespace}`); - ConnectionController.updateAccounts(namespace, accounts); + ConnectionsController.updateAccounts(namespace, accounts); }); adapter.on('chainChanged', ({ chainId, namespace }) => { console.log(`Chain changed for namespace: ${namespace}`); - ConnectionController.updateChainId(namespace, chainId); + ConnectionsController.updateChainId(namespace, chainId); }); adapter.on('disconnect', ({ namespace }) => { console.log(`Disconnect event received for ${namespace}`); - ConnectionController.disconnect(namespace); + ConnectionsController.disconnect(namespace); }); } @@ -140,9 +148,8 @@ export class AppKit { } async disconnect(namespace: string): Promise { - console.log('AppKit disconnecting', namespace); try { - await ConnectionController.disconnect(namespace); + await ConnectionsController.disconnect(namespace); ModalController.close(); AccountController.setIsConnected(false); RouterController.reset('Connect'); @@ -152,7 +159,6 @@ export class AppKit { event: 'DISCONNECT_SUCCESS' }); } catch (error) { - console.error('AppKit:disconnect - error', error); EventsController.sendEvent({ type: 'track', event: 'DISCONNECT_ERROR' @@ -162,12 +168,12 @@ export class AppKit { getProvider(namespace?: string): T | null { const connection = - ConnectionController.state.connections[ - namespace ?? ConnectionController.state.activeNamespace + ConnectionsController.state.connections[ + namespace ?? ConnectionsController.state.activeNamespace ]; if (!connection) return null; - return (connection.adapter as any).currentConnector?.getProvider() as T; + return connection.adapter.connector?.getProvider() as T; } } diff --git a/packages/appkit/src/adapters/types.ts b/packages/appkit/src/adapters/types.ts deleted file mode 100644 index ae7236b8..00000000 --- a/packages/appkit/src/adapters/types.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { EventEmitter } from "events"; - -//********** Adapter Types **********// - -export abstract class BlockchainAdapter extends EventEmitter { - public projectId: string; - public connector?: WalletConnector; - public supportedNamespace: string; - - constructor({ projectId, supportedNamespace }: { projectId: string, supportedNamespace: string }) { - super(); - this.projectId = projectId; - this.supportedNamespace = supportedNamespace; - } - - setConnector(connector: WalletConnector) { - this.connector = connector; - } - - removeConnector() { - this.connector = undefined; - } - - abstract disconnect(): Promise; - abstract request(method: string, params?: any[]): Promise; - abstract getSupportedNamespace(): string; -} - - - -export abstract class EVMAdapter extends BlockchainAdapter { - abstract signTransaction(tx: TransactionData): Promise; - abstract getBalance(address: string): Promise; - abstract sendTransaction(tx: TransactionData): Promise; -} - -//********** Connector Types **********// - -export abstract class WalletConnector extends EventEmitter { - public type: ConnectorType; - protected provider: Provider; - protected namespaces?: string[]; - - constructor({ type, provider }: { type: ConnectorType, provider: Provider }) { - super(); - this.type = type; - this.provider = provider; - } - - abstract connect(namespaces?: string[]): Promise; - abstract disconnect(): Promise; - abstract getProvider(): Provider; - abstract getNamespaces(): string[]; -} - -//********** Provider Types **********// - -export interface Provider { - connect(params?: any): Promise; - disconnect(): Promise; - request(args: RequestArguments, chain?: string | undefined, expiry?: number | undefined): Promise; - on(event: string, listener: (args?: any) => void): any; - off(event: string, listener: (args?: any) => void): any; -} - -export interface RequestArguments { - method: string; - params?: unknown[] | Record | object | undefined; -} - -export type ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; - -//********** Others **********// - -export interface TransactionData { - to: string; - value?: string; - data?: string; - [key: string]: any; -} - -export interface SignedTransaction { - raw: string; - [key: string]: any; -} - -export interface TransactionReceipt { - transactionHash: string; - [key: string]: any; -} - - -export interface ConnectionResponse { - accounts: string[]; - chainId: string; - [key: string]: any; -} - - - diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index fe7f47e1..a4a8c899 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -1,6 +1,11 @@ import { type Metadata, ConnectionController } from '@reown/appkit-core-react-native'; import { UniversalProvider, type IUniversalProvider } from '@walletconnect/universal-provider'; -import { WalletConnector, type Provider } from '../adapters/types'; +import { + WalletConnector, + type Namespaces, + type ProposalNamespaces, + type Provider +} from '@reown/appkit-common-react-native'; export class WalletConnectConnector extends WalletConnector { private constructor(provider: Provider) { @@ -27,10 +32,7 @@ export class WalletConnectConnector extends WalletConnector { return this.provider.disconnect(); } - async connect(namespaces?: string[]): Promise { - //TODO: use namespaces - this.namespaces = namespaces; - + override async connect(namespaces?: ProposalNamespaces) { function onUri(uri: string) { ConnectionController.setWcUri(uri); } @@ -38,39 +40,23 @@ export class WalletConnectConnector extends WalletConnector { this.provider.on('display_uri', onUri); const session = await this.provider.connect({ - optionalNamespaces: { - eip155: { - methods: [ - 'eth_sendTransaction', - 'eth_signTransaction', - 'eth_sign', - 'personal_sign', - 'eth_signTypedData' - ], - chains: ['eip155:1'], - events: ['chainChanged', 'accountsChanged'] - }, - solana: { - methods: ['solana_signMessage'], - chains: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], - events: ['chainChanged', 'accountsChanged'] - } - } + optionalNamespaces: namespaces }); - const approvedNamespaces = Object.keys(session?.namespaces ?? {}); - console.log('session', approvedNamespaces); + this.namespaces = session?.namespaces; + + console.log('session', session); this.provider.off('display_uri', onUri); - return approvedNamespaces; + return this.namespaces; } override getProvider(): Provider { return this.provider; } - override getNamespaces(): string[] { - return this.namespaces ?? []; + override getNamespaces(): Namespaces { + return this.namespaces ?? {}; } } diff --git a/packages/appkit/src/controllers/ConnectionController.ts b/packages/appkit/src/controllers/ConnectionController.ts deleted file mode 100644 index 043eb7eb..00000000 --- a/packages/appkit/src/controllers/ConnectionController.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { proxy } from 'valtio'; -import type { BlockchainAdapter } from '../adapters/types'; - -interface ConnectionState { - accounts: string[]; - balances: Record; - activeChainId: string; - adapter: BlockchainAdapter; -} - -interface State { - activeNamespace: string; - connections: Record; -} - -const state = proxy({ - activeNamespace: 'eip155', - connections: {} -}); - -function setActiveNamespace(namespace: string) { - state.activeNamespace = namespace; -} - -function storeConnection( - namespace: string, - adapter: BlockchainAdapter, - accounts: string[] = [], - chainId: string = '' -) { - state.connections[namespace] = { - accounts, - balances: {}, - activeChainId: chainId, - adapter - }; -} - -function updateAccounts(namespace: string, accounts: string[]) { - const connection = state.connections[namespace]; - if (!connection) { - return; - } - connection.accounts = accounts; -} - -function updateBalances(namespace: string, balances: Record) { - const connection = state.connections[namespace]; - if (!connection) { - return; - } - connection.balances = balances; -} - -function updateChainId(namespace: string, chainId: string) { - const connection = state.connections[namespace]; - if (!connection) { - return; - } - connection.activeChainId = chainId; -} - -async function disconnect(namespace: string) { - const connection = state.connections[namespace]; - if (!connection) return; - - console.log('ConnectionController:disconnect - connection', connection); - - // Get the current connector from the adapter - const connector = connection.adapter.connector; - if (!connector) return; - - console.log('ConnectionController:disconnect - connector', connector); - - // Find all namespaces that use the same connector - const namespacesUsingConnector = Object.keys(state.connections).filter( - ns => state.connections[ns]?.adapter.connector === connector - ); - - console.log( - 'ConnectionController:disconnect - namespacesUsingConnector', - namespacesUsingConnector - ); - - // Unsubscribe all event listeners from the adapter - namespacesUsingConnector.forEach(ns => { - const _connection = state.connections[ns]; - if (_connection?.adapter) { - _connection.adapter.removeAllListeners(); - } - }); - - // Disconnect the adapter - await connection.adapter.disconnect(); - - // Remove all namespaces that used this connector - namespacesUsingConnector.forEach(ns => { - delete state.connections[ns]; - }); -} - -export const ConnectionController = { - state, - setActiveNamespace, - storeConnection, - updateAccounts, - updateBalances, - updateChainId, - disconnect -}; diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts index 882046ce..f4aad356 100644 --- a/packages/appkit/src/hooks/useProvider.ts +++ b/packages/appkit/src/hooks/useProvider.ts @@ -1,11 +1,11 @@ import { useSnapshot } from 'valtio'; -import { ConnectionController } from '../controllers/ConnectionController'; +import { ConnectionsController } from '@reown/appkit-core-react-native'; export function useProvider(namespace: string): T | null { - const { connections } = useSnapshot(ConnectionController.state); + const { connections } = useSnapshot(ConnectionsController.state); const connection = connections[namespace]; if (!connection) return null; - return (connection.adapter as any).currentConnector?.getProvider() as T; -} \ No newline at end of file + return connection.adapter.connector?.getProvider() as T; +} diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 766fa0a2..eccf2141 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -21,8 +21,6 @@ export type * from '@reown/appkit-core-react-native'; export { CoreHelperUtil } from '@reown/appkit-core-react-native'; export * from './AppKit'; -export * from './adapters/types'; export { AppKitProvider, useAppKit } from './AppKitContext'; -export { ConnectionController } from './controllers/ConnectionController'; export { useProvider } from './hooks/useProvider'; -export { WalletConnectConnector } from './connectors/WalletConnectConnector'; \ No newline at end of file +export { WalletConnectConnector } from './connectors/WalletConnectConnector'; diff --git a/packages/appkit/src/utils/ConnectorUtil.ts b/packages/appkit/src/utils/ConnectorUtil.ts index 456e5190..dcf017bb 100644 --- a/packages/appkit/src/utils/ConnectorUtil.ts +++ b/packages/appkit/src/utils/ConnectorUtil.ts @@ -1,22 +1,31 @@ -import type { WalletConnector } from "../adapters/types"; -import { WalletConnectConnector } from "../connectors/WalletConnectConnector"; +import type { WalletConnector } from '@reown/appkit-common-react-native'; +import { WalletConnectConnector } from '../connectors/WalletConnectConnector'; const mockMetadata = { - name: "Reown", - description: "Reown App", - url: "https://reown.xyz", - icons: ["https://reown.xyz/icon.png"] -} + name: 'Reown', + description: 'Reown App', + url: 'https://reown.xyz', + icons: ['https://reown.xyz/icon.png'] +}; export const ConnectorUtil = { - async createConnector({ walletType, extraConnectors, projectId }: { walletType: string, extraConnectors: WalletConnector[], projectId: string }): Promise { + async createConnector({ + walletType, + extraConnectors, + projectId + }: { + walletType: string; + extraConnectors: WalletConnector[]; + projectId: string; + }): Promise { // Check if an extra connector was provided by the developer const CustomConnector = extraConnectors.find(connector => connector.type === walletType); if (CustomConnector) return CustomConnector; // If no extra connector is provided, default to WalletConnectConnector - if (walletType === "walletconnect") return WalletConnectConnector.create({ projectId, metadata: mockMetadata }); + if (walletType === 'walletconnect') + return WalletConnectConnector.create({ projectId, metadata: mockMetadata }); throw new Error(`Unsupported wallet type: ${walletType}`); } -} \ No newline at end of file +}; diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 5c386161..ad4c61a3 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -51,7 +51,28 @@ export function ConnectingView() { if (retry || CoreHelperUtil.isPairingExpired(wcPairingExpiry)) { ConnectionController.setWcError(false); // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); - const wcPromise = appKit?.connect('walletconnect', ['eip155']); + + //TODO: check this + const namespaces = { + eip155: { + methods: [ + 'eth_sendTransaction', + 'eth_signTransaction', + 'eth_sign', + 'personal_sign', + 'eth_signTypedData' + ], + chains: ['eip155:1'], + events: ['chainChanged', 'accountsChanged'] + }, + solana: { + methods: ['solana_signMessage'], + chains: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + events: ['chainChanged', 'accountsChanged'] + } + }; + + const wcPromise = appKit?.connect('walletconnect', namespaces); ConnectionController.setWcPromise(wcPromise); await wcPromise; // await ConnectionController.state.wcPromise; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 13ea7ede..70fa8ade 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -1,3 +1,5 @@ +import { EventEmitter } from 'events'; + export interface Balance { name: string; symbol: string; @@ -95,3 +97,119 @@ export interface ThemeVariables { } export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; + +//********** Adapter Types **********// + +export abstract class BlockchainAdapter extends EventEmitter { + public projectId: string; + public connector?: WalletConnector; + public supportedNamespace: string; + + constructor({ + projectId, + supportedNamespace + }: { + projectId: string; + supportedNamespace: string; + }) { + super(); + this.projectId = projectId; + this.supportedNamespace = supportedNamespace; + } + + setConnector(connector: WalletConnector) { + this.connector = connector; + } + + removeConnector() { + this.connector = undefined; + } + + abstract disconnect(): Promise; + abstract request(method: string, params?: any[]): Promise; + abstract getSupportedNamespace(): string; +} + +export abstract class EVMAdapter extends BlockchainAdapter { + abstract signTransaction(tx: TransactionData): Promise; + abstract getBalance(address: string): Promise; + abstract sendTransaction(tx: TransactionData): Promise; +} + +//********** Connector Types **********// +interface BaseNamespace { + chains?: string[]; + accounts: string[]; + methods: string[]; + events: string[]; +} + +type Namespace = BaseNamespace; + +export type Namespaces = Record; + +export type ProposalNamespaces = Record>; + +export abstract class WalletConnector extends EventEmitter { + public type: New_ConnectorType; + protected provider: Provider; + protected namespaces?: Namespaces; + + constructor({ type, provider }: { type: New_ConnectorType; provider: Provider }) { + super(); + this.type = type; + this.provider = provider; + } + + abstract connect(namespaces?: ProposalNamespaces): Promise; + abstract disconnect(): Promise; + abstract getProvider(): Provider; + abstract getNamespaces(): Namespaces; +} + +//********** Provider Types **********// + +export interface Provider { + connect(params?: any): Promise; + disconnect(): Promise; + request( + args: RequestArguments, + chain?: string | undefined, + expiry?: number | undefined + ): Promise; + on(event: string, listener: (args?: any) => void): any; + off(event: string, listener: (args?: any) => void): any; +} + +export interface RequestArguments { + method: string; + params?: unknown[] | Record | object | undefined; +} + +//TODO: rename this and remove the old one +export type New_ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; + +//********** Others **********// + +export interface TransactionData { + to: string; + value?: string; + data?: string; + [key: string]: any; +} + +export interface SignedTransaction { + raw: string; + [key: string]: any; +} + +export interface TransactionReceipt { + transactionHash: string; + [key: string]: any; +} + +export interface ConnectionResponse { + accounts: string[]; + chainId: string; + [key: string]: any; +} diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts new file mode 100644 index 00000000..0f9dc08c --- /dev/null +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -0,0 +1,125 @@ +import { proxy } from 'valtio'; +import type { BlockchainAdapter } from '@reown/appkit-common-react-native'; + +// -- Types --------------------------------------------- // +interface Connection { + accounts: string[]; + balances: Record; + activeChainId: string; + adapter: BlockchainAdapter; + events: string[]; + chains: string[]; + methods: string[]; +} + +export interface ConnectionsControllerState { + activeNamespace: string; + connections: Record; +} + +// -- State --------------------------------------------- // +const state = proxy({ + activeNamespace: 'eip155', + connections: {} +}); + +// -- Controller ---------------------------------------- // +export const ConnectionsController = { + state, + + setActiveNamespace(namespace: string) { + state.activeNamespace = namespace; + }, + + storeConnection({ + namespace, + adapter, + accounts, + events, + chains, + methods + }: { + namespace: string; + adapter: BlockchainAdapter; + accounts: string[]; + events: string[]; + chains: string[]; + methods: string[]; + }) { + state.connections[namespace] = { + balances: {}, + activeChainId: chains[0]!, + adapter, + accounts, + events, + chains, + methods + }; + console.log('ConnectionController:storeConnection - state.connections', state.connections); + }, + + updateAccounts(namespace: string, accounts: string[]) { + const connection = state.connections[namespace]; + if (!connection) { + return; + } + connection.accounts = accounts; + }, + + updateBalances(namespace: string, balances: Record) { + const connection = state.connections[namespace]; + if (!connection) { + return; + } + connection.balances = balances; + }, + + updateChainId(namespace: string, chainId: string) { + const connection = state.connections[namespace]; + if (!connection) { + return; + } + connection.activeChainId = chainId; + }, + + async disconnect(namespace: string) { + const connection = state.connections[namespace]; + if (!connection) return; + + console.log('ConnectionController:disconnect - connection', connection); + + // Get the current connector from the adapter + const connector = connection.adapter.connector; + if (!connector) return; + + console.log('ConnectionController:disconnect - connector', connector); + + // Find all namespaces that use the same connector + const namespacesUsingConnector = Object.keys(state.connections).filter( + ns => state.connections[ns]?.adapter.connector === connector + ); + + console.log( + 'ConnectionController:disconnect - namespacesUsingConnector', + namespacesUsingConnector + ); + + // Unsubscribe all event listeners from the adapter + namespacesUsingConnector.forEach(ns => { + const _connection = state.connections[ns]; + if (_connection?.adapter) { + _connection.adapter.removeAllListeners(); + } + }); + + // Disconnect the adapter + await connection.adapter.disconnect(); + + // Remove all namespaces that used this connector + namespacesUsingConnector.forEach(ns => { + delete state.connections[ns]; + }); + + console.log('ConnectionController:disconnect - state.connections', state.connections); + } +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b3fc085b..63e8b133 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -21,6 +21,11 @@ export { type ConnectionControllerState } from './controllers/ConnectionController'; +export { + ConnectionsController, + type ConnectionsControllerState +} from './controllers/ConnectionsController'; + export { ConnectorController, type ConnectorControllerState diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 841c797b..7e239b40 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,4 +1,5 @@ import { type EventEmitter } from 'events'; + import type { Balance, SocialProvider, @@ -6,6 +7,7 @@ import type { Transaction, ConnectorType } from '@reown/appkit-common-react-native'; + import { OnRampErrorType } from './ConstantsUtil'; export interface BaseError { diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 66a232a1..696296b0 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -4,7 +4,7 @@ import { type SignedTransaction, type TransactionData, type TransactionReceipt -} from '@reown/appkit-react-native'; +} from '@reown/appkit-common-react-native'; export class EthersAdapter extends EVMAdapter { private static supportedNamespace: string = 'eip155'; @@ -50,18 +50,18 @@ export class EthersAdapter extends EVMAdapter { } onChainChanged(chainId: string): void { - console.log('adapter chainChanged', chainId); + console.log('EthersAdapter - onChainChanged', chainId); this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); } onAccountsChanged(accounts: string[]): void { - console.log('adapter accountsChanged', accounts); + console.log('EthersAdapter - onAccountsChanged', accounts); // Emit this change to AppKit with the corresponding namespace. this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); } onDisconnect(): void { - console.log('adapter onDisconnect'); + console.log('EthersAdapter - onDisconnect'); this.emit('disconnect', { namespace: this.getSupportedNamespace() }); //the connector might be shared between adapters. Validate this @@ -70,7 +70,6 @@ export class EthersAdapter extends EVMAdapter { provider.off('chainChanged', this.onChainChanged.bind(this)); provider.off('accountsChanged', this.onAccountsChanged.bind(this)); provider.off('disconnect', this.onDisconnect.bind(this)); - console.log('Removed listeners from provider on disconnect'); } this.connector = undefined; @@ -85,15 +84,9 @@ export class EthersAdapter extends EVMAdapter { const provider = this.connector?.getProvider(); if (!provider) return; - console.log('subscribing to events'); + console.log('EthersAdapter - subscribing to events'); provider.on('chainChanged', this.onChainChanged.bind(this)); provider.on('accountsChanged', this.onAccountsChanged.bind(this)); provider.on('disconnect', this.onDisconnect.bind(this)); - //@ts-ignore - console.log('subscribed to events', provider?.events); - - provider.on('accountsChanged', () => { - console.log('accountsChanged'); - }); } } diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index 4ae17e16..bad2c3e0 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -4,7 +4,7 @@ import { type SignedTransaction, type TransactionData, type TransactionReceipt -} from '@reown/appkit-react-native'; +} from '@reown/appkit-common-react-native'; import { type Config, type CreateConfigParameters, From 3777d02a3dcb9e8a99622cb2768a10daecd083b6 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 10 Apr 2025 18:01:30 -0300 Subject: [PATCH 04/91] chore: restore session --- apps/native/App.tsx | 1 + package.json | 1 - packages/appkit/src/AppKit.ts | 213 +++++++++++++----- .../src/connectors/WalletConnectConnector.ts | 13 +- .../src/modal/w3m-account-button/index.tsx | 7 +- packages/appkit/src/modal/w3m-modal/index.tsx | 2 +- packages/appkit/src/utils/ConnectorUtil.ts | 31 --- .../views/w3m-account-default-view/index.tsx | 7 +- .../src/views/w3m-connecting-view/index.tsx | 6 +- .../src/views/w3m-networks-view/index.tsx | 2 +- .../w3m-unsupported-chain-view/index.tsx | 4 +- .../components/preview-send-details.tsx | 3 +- packages/common/src/utils/TypeUtil.ts | 15 +- .../core/src/controllers/AccountController.ts | 4 +- .../src/controllers/ConnectionsController.ts | 85 ++++--- .../src/controllers/ConnectorController.ts | 1 + .../core/src/controllers/NetworkController.ts | 2 +- .../src/controllers/PublicStateController.ts | 2 +- .../core/src/controllers/RouterController.ts | 3 +- packages/core/src/utils/AssetUtil.ts | 3 +- packages/core/src/utils/CoreHelperUtil.ts | 11 +- packages/core/src/utils/NetworkUtil.ts | 2 +- packages/core/src/utils/StorageUtil.ts | 44 ++++ packages/core/src/utils/TypeUtil.ts | 12 +- packages/ethers/src/client.ts | 6 +- packages/ethers/src/utils/helpers.ts | 2 +- packages/ethers5/src/client.ts | 6 +- packages/ethers5/src/utils/helpers.ts | 2 +- .../src/utils/EthersHelpersUtil.ts | 2 +- .../scaffold/src/modal/w3m-modal/index.tsx | 2 +- .../src/views/w3m-networks-view/index.tsx | 2 +- .../w3m-unsupported-chain-view/index.tsx | 3 +- packages/wagmi/src/client.ts | 8 +- packages/wagmi/src/utils/helpers.ts | 7 +- 34 files changed, 329 insertions(+), 185 deletions(-) delete mode 100644 packages/appkit/src/utils/ConnectorUtil.ts diff --git a/apps/native/App.tsx b/apps/native/App.tsx index fa057de7..96deff7b 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -101,6 +101,7 @@ const appKit = createAppKit({ adapters: [ethersAdapter], metadata, networks: chains, + // namespaces: custom namespaces }); export default function Native() { diff --git a/package.json b/package.json index 93265fcd..a2beebc9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "packages/common", "packages/wallet", "packages/scaffold-utils", - "packages/scaffold", "packages/siwe", "packages/wagmi", "packages/coinbase-wagmi", diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 2f59812a..d602bca1 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -6,14 +6,16 @@ import { OptionsController, RouterController, TransactionsController, - type Metadata + type Metadata, + StorageUtil } from '@reown/appkit-core-react-native'; import type { WalletConnector, BlockchainAdapter, ProposalNamespaces, - New_ConnectorType + New_ConnectorType, + Namespaces // Namespaces, } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; @@ -40,9 +42,10 @@ export class AppKit { this.networks = config.networks; // this.namespaces = this.getNamespaces(config.networks); this.extraConnectors = config.extraConnectors || []; - console.log(this.networks?.length); + // console.log(this.networks?.length); // Removed console log this.initControllers(config); + this.initConnectors(); } private async createConnector(type: New_ConnectorType): Promise { @@ -55,87 +58,167 @@ export class AppKit { return CustomConnector; } + // Default to WalletConnectConnector if no custom connector matches return WalletConnectConnector.create({ projectId: this.projectId, metadata: this.metadata }); } + /** + * Initializes connectors based on stored connection data. + * This attempts to restore previous sessions. + */ + private async initConnectors() { + const connectedConnectors = await StorageUtil.getConnectedConnectors(); // Fetch stored connectors + + for (const connected of connectedConnectors) { + try { + const connector = await this.createConnector(connected.type); + + const namespaces = connector.getNamespaces(); + if (namespaces && Object.keys(namespaces).length > 0) { + // Ensure namespaces is not empty + // Setup adapters and subscribe to events + const initializedAdapters = this._setupAdaptersAndSubscribe( + connector, + Object.keys(namespaces) + ); + + // If adapters were successfully initialized, store the connection details + if (initializedAdapters.length > 0) { + this._storeConnectionDetails(initializedAdapters, namespaces); + } + AccountController.setIsConnected(true); + } + } catch (error) { + // Use console.warn for non-critical initialization failures + console.warn(`Failed to initialize connector type ${connected.type}:`, error); + await StorageUtil.removeConnectedConnectors(connected.type); + } + } + } + + /** + * Sets up blockchain adapters for a given connector and namespaces, + * subscribes to adapter events. + * @param connector - The WalletConnector instance. + * @param namespaces - The namespaces to find adapters for. + * @returns The array of BlockchainAdapter instances that were set up. + */ + private _setupAdaptersAndSubscribe( + connector: WalletConnector, + namespaces: string[] + ): BlockchainAdapter[] { + const adapters = this.adapters.filter(adapter => + namespaces.includes(adapter.getSupportedNamespace()) + ); + + if (adapters.length === 0) { + // Log or handle cases where no adapters match + console.warn(`No compatible adapters found for namespaces: ${namespaces.join(', ')}`); + + return []; + } + + adapters.forEach(adapter => { + adapter.setConnector(connector); + this.subscribeToAdapterEvents(adapter); + }); + + return adapters; + } + + /** + * Handles the full connection flow for a given connector type. + * @param type - The type of connector to use. + * @param requestedNamespaces - Optional specific namespaces to request. + */ async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { try { const connector = await this.createConnector(type); - //Set connector in available adapters - const adapters = this.adapters.filter(adapter => - Object.keys(requestedNamespaces ?? {}).includes(adapter.getSupportedNamespace()) + // Connect using the connector and get approved namespaces first + const approvedNamespaces = await connector.connect(requestedNamespaces); + if (!approvedNamespaces || Object.keys(approvedNamespaces).length === 0) { + throw new Error('Connection cancelled or failed: No approved namespaces returned.'); + } + + // Now, setup adapters and subscribe *only* for the approved namespaces + const approvedAdapters = this._setupAdaptersAndSubscribe( + connector, + Object.keys(approvedNamespaces) ); - if (adapters.length === 0) { - throw new Error('No compatible adapters found for the requested namespaces'); + // Check if any compatible adapters were found for the *approved* namespaces + if (approvedAdapters.length === 0) { + // This case might happen if the user approved namespaces for which we have no adapters, + // or if _setupAdaptersAndSubscribe failed internally. + throw new Error('No compatible adapters found for the approved namespaces'); } - adapters.forEach(adapter => { - adapter.setConnector(connector); - this.subscribeToAdapterEvents(adapter); + // Store the connection details for the successfully connected adapters + this._storeConnectionDetails(approvedAdapters, approvedNamespaces); + + // Store connector type and namespaces in storage + await StorageUtil.setConnectedConnectors({ + type: connector.type, + namespaces: Object.keys(approvedNamespaces) }); - // Connect using the connector and get approved namespaces - const approvedNamespaces = await connector.connect(requestedNamespaces); + // Set connected state (consider if this should be more nuanced for multi-connections) + AccountController.setIsConnected(true); - if (!approvedNamespaces) { - throw new Error('No approved namespaces found'); - } + // No longer need to unsubscribe as we only subscribe to approved ones + } catch (error) { + // Log connection errors + console.warn('Connection failed:', error); // Using warn for potentially recoverable errors + // Rethrow or handle the error appropriately for the UI + throw error; + } + } - console.log('CONNECTED - approvedNamespaces', approvedNamespaces); + /** + * Stores connection details in the ConnectionsController. + * @param adapters - The adapters for which to store the connection. + * @param approvedNamespaces - The map of approved namespaces and their details. + */ + private _storeConnectionDetails(adapters: BlockchainAdapter[], approvedNamespaces: Namespaces) { + adapters.forEach(async adapter => { + const namespace = adapter.getSupportedNamespace(); + const namespaceDetails = approvedNamespaces[namespace]; + if (!namespaceDetails) return; // Should not happen if filtering is correct - // Find adapters that support the approved namespaces - const adapterInstances = adapters.filter(adapter => - Object.keys(approvedNamespaces ?? {}).includes(adapter.getSupportedNamespace()) - ); + const accounts = namespaceDetails.accounts ?? []; + const chains = namespaceDetails.chains ?? []; - // Store the connection of supported adapters - adapterInstances.forEach(async adapter => { - const namespace = adapter.getSupportedNamespace(); - const accounts = approvedNamespaces[namespace]?.accounts ?? []; - const chains = approvedNamespaces[namespace]?.chains ?? []; - const methods = approvedNamespaces[namespace]?.methods ?? []; - const events = approvedNamespaces[namespace]?.events ?? []; - ConnectionsController.storeConnection({ - namespace, - adapter, - accounts, - chains, - methods, - events - }); + ConnectionsController.storeConnection({ + namespace, + adapter, + accounts, + chains }); + }); - // Unsubscribe evets from the not connected connectors - const notConnectedAdapters = this.adapters.filter( - adapter => !adapterInstances.includes(adapter) - ); - - notConnectedAdapters.forEach(adapter => { - adapter.removeAllListeners(); - adapter.removeConnector(); - }); - } catch (error) { - console.error('Connection failed:', error); - throw error; + // Set the first connected adapter's namespace as active + if (adapters.length > 0 && adapters[0]) { + ConnectionsController.setActiveNamespace(adapters[0].getSupportedNamespace()); } } private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { adapter.on('accountsChanged', ({ accounts, namespace }) => { - console.log(`Updating accounts for namespace: ${namespace}`); + // console.log(`Updating accounts for namespace: ${namespace}`); // Removed console log ConnectionsController.updateAccounts(namespace, accounts); }); adapter.on('chainChanged', ({ chainId, namespace }) => { - console.log(`Chain changed for namespace: ${namespace}`); + // console.log(`Chain changed for namespace: ${namespace}`); // Removed console log ConnectionsController.updateChainId(namespace, chainId); }); adapter.on('disconnect', ({ namespace }) => { - console.log(`Disconnect event received for ${namespace}`); + // console.log(`Disconnect event received for ${namespace}`); // Removed console log ConnectionsController.disconnect(namespace); + // Potentially remove from storage on disconnect event as well + // StorageUtil.removeConnectedConnectors(connectorType); // Need connectorType here }); } @@ -149,9 +232,18 @@ export class AppKit { async disconnect(namespace: string): Promise { try { - await ConnectionsController.disconnect(namespace); + const connection = ConnectionsController.state.connections[namespace]; + const connectorType = connection?.adapter?.connector?.type; + + await ConnectionsController.disconnect(namespace); // This should trigger the 'disconnect' event handler via the adapter/connector + + if (connectorType) { + await StorageUtil.removeConnectedConnectors(connectorType); + } + ModalController.close(); - AccountController.setIsConnected(false); + // Resetting states after successful disconnect logic + AccountController.setIsConnected(false); // Might need adjustment based on multi-connection logic RouterController.reset('Connect'); TransactionsController.resetTransactions(); EventsController.sendEvent({ @@ -159,21 +251,24 @@ export class AppKit { event: 'DISCONNECT_SUCCESS' }); } catch (error) { + // Use console.warn for disconnect errors as they might not be critical app failures + console.warn('Disconnect failed:', error); // Keep error log for disconnect issues EventsController.sendEvent({ type: 'track', event: 'DISCONNECT_ERROR' }); + // Do not rethrow? Or handle differently? } } getProvider(namespace?: string): T | null { - const connection = - ConnectionsController.state.connections[ - namespace ?? ConnectionsController.state.activeNamespace - ]; - if (!connection) return null; + const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; + if (!activeNamespace) return null; + + const connection = ConnectionsController.state.connections[activeNamespace]; + if (!connection || !connection.adapter || !connection.adapter.connector) return null; - return connection.adapter.connector?.getProvider() as T; + return connection.adapter.connector.getProvider() as T | null; } } diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index a4a8c899..b7a457d3 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -8,8 +8,12 @@ import { } from '@reown/appkit-common-react-native'; export class WalletConnectConnector extends WalletConnector { - private constructor(provider: Provider) { - super({ type: 'walletconnect', provider }); + private constructor(provider: IUniversalProvider) { + super({ type: 'walletconnect', provider: provider as Provider }); + + if (provider.session?.namespaces) { + this.namespaces = provider.session.namespaces as Namespaces; + } } public static async create({ @@ -24,8 +28,7 @@ export class WalletConnectConnector extends WalletConnector { metadata }); - //TODO: Check this - return new WalletConnectConnector(provider as Provider); + return new WalletConnectConnector(provider); } override disconnect(): Promise { @@ -43,7 +46,7 @@ export class WalletConnectConnector extends WalletConnector { optionalNamespaces: namespaces }); - this.namespaces = session?.namespaces; + this.namespaces = session?.namespaces as Namespaces; console.log('session', session); diff --git a/packages/appkit/src/modal/w3m-account-button/index.tsx b/packages/appkit/src/modal/w3m-account-button/index.tsx index 8bb37376..64559c58 100644 --- a/packages/appkit/src/modal/w3m-account-button/index.tsx +++ b/packages/appkit/src/modal/w3m-account-button/index.tsx @@ -5,7 +5,8 @@ import { NetworkController, ModalController, AssetUtil, - ThemeController + ThemeController, + ConnectionsController } from '@reown/appkit-core-react-native'; import { AccountButton as AccountButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; @@ -21,7 +22,6 @@ export interface AccountButtonProps { export function AccountButton({ balance, disabled, style, testID }: AccountButtonProps) { const { - address, balance: balanceVal, balanceSymbol, profileImage, @@ -29,6 +29,7 @@ export function AccountButton({ balance, disabled, style, testID }: AccountButto } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); + const { activeAddress: address } = useSnapshot(ConnectionsController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); const showBalance = balance === 'show'; @@ -37,7 +38,7 @@ export function AccountButton({ balance, disabled, style, testID }: AccountButto ModalController.open()} - address={address} + address={address ?? ''} profileName={profileName} networkSrc={networkImage} imageHeaders={ApiController._getApiHeaders()} diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index 1d8d29d7..631bc2fa 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -14,11 +14,11 @@ import { OptionsController, RouterController, TransactionsController, - type CaipAddress, type AppKitFrameProvider, WebviewController, ThemeController } from '@reown/appkit-core-react-native'; +import type { CaipAddress } from '@reown/appkit-common-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; import { AppKitRouter } from '../w3m-router'; diff --git a/packages/appkit/src/utils/ConnectorUtil.ts b/packages/appkit/src/utils/ConnectorUtil.ts deleted file mode 100644 index dcf017bb..00000000 --- a/packages/appkit/src/utils/ConnectorUtil.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { WalletConnector } from '@reown/appkit-common-react-native'; -import { WalletConnectConnector } from '../connectors/WalletConnectConnector'; - -const mockMetadata = { - name: 'Reown', - description: 'Reown App', - url: 'https://reown.xyz', - icons: ['https://reown.xyz/icon.png'] -}; - -export const ConnectorUtil = { - async createConnector({ - walletType, - extraConnectors, - projectId - }: { - walletType: string; - extraConnectors: WalletConnector[]; - projectId: string; - }): Promise { - // Check if an extra connector was provided by the developer - const CustomConnector = extraConnectors.find(connector => connector.type === walletType); - if (CustomConnector) return CustomConnector; - - // If no extra connector is provided, default to WalletConnectConnector - if (walletType === 'walletconnect') - return WalletConnectConnector.create({ projectId, metadata: mockMetadata }); - - throw new Error(`Unsupported wallet type: ${walletType}`); - } -}; diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 0f49b7a2..41d53973 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -17,7 +17,8 @@ import { type AppKitFrameProvider, ConstantsUtil, SwapController, - OnRampController + OnRampController, + ConnectionsController } from '@reown/appkit-core-react-native'; import { Avatar, @@ -38,7 +39,6 @@ import styles from './styles'; export function AccountDefaultView() { const { - address, profileName, profileImage, balance, @@ -47,6 +47,7 @@ export function AccountDefaultView() { preferredAccountType } = useSnapshot(AccountController.state); const { loading } = useSnapshot(ModalController.state); + const { activeAddress: address } = useSnapshot(ConnectionsController.state); const [disconnecting, setDisconnecting] = useState(false); const { caipNetwork } = useSnapshot(NetworkController.state); const { connectedConnector } = useSnapshot(ConnectorController.state); @@ -190,7 +191,7 @@ export function AccountDefaultView() { /> - + {profileName diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index ad4c61a3..3a51b9fd 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -15,8 +15,8 @@ import { ConnectorController } from '@reown/appkit-core-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; import { useAppKit } from '../../AppKitContext'; - import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; import { ConnectingWeb } from '../../partials/w3m-connecting-web'; @@ -62,12 +62,12 @@ export function ConnectingView() { 'personal_sign', 'eth_signTypedData' ], - chains: ['eip155:1'], + chains: ['eip155:1'] as CaipNetworkId[], events: ['chainChanged', 'accountsChanged'] }, solana: { methods: ['solana_signMessage'], - chains: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + chains: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'] as CaipNetworkId[], events: ['chainChanged', 'accountsChanged'] } }; diff --git a/packages/appkit/src/views/w3m-networks-view/index.tsx b/packages/appkit/src/views/w3m-networks-view/index.tsx index 30bdd029..41f98b3b 100644 --- a/packages/appkit/src/views/w3m-networks-view/index.tsx +++ b/packages/appkit/src/views/w3m-networks-view/index.tsx @@ -13,11 +13,11 @@ import { AssetUtil, NetworkController, RouterController, - type CaipNetwork, EventsController, CoreHelperUtil, NetworkUtil } from '@reown/appkit-core-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; diff --git a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx index 9e17494a..63078a80 100644 --- a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx @@ -9,11 +9,9 @@ import { EventsController, NetworkController, NetworkUtil, - type CaipNetwork, type NetworkControllerState } from '@reown/appkit-core-react-native'; - -// import { useAppKit } from '@reown/appkit-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { useAppKit } from '../../AppKitContext'; import styles from './styles'; diff --git a/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx index d71df174..e05b35c8 100644 --- a/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx +++ b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx @@ -1,4 +1,5 @@ -import { AssetUtil, type CaipNetwork } from '@reown/appkit-core-react-native'; +import { AssetUtil } from '@reown/appkit-core-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { BorderRadius, FlexView, diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 70fa8ade..1ef20a0c 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -1,5 +1,16 @@ import { EventEmitter } from 'events'; +export type CaipAddress = `${string}:${string}:${string}`; + +export type CaipNetworkId = `${string}:${string}`; + +export interface CaipNetwork { + id: CaipNetworkId; + name?: string; + imageId?: string; + imageUrl?: string; +} + export interface Balance { name: string; symbol: string; @@ -138,8 +149,8 @@ export abstract class EVMAdapter extends BlockchainAdapter { //********** Connector Types **********// interface BaseNamespace { - chains?: string[]; - accounts: string[]; + chains?: CaipNetworkId[]; + accounts: CaipAddress[]; methods: string[]; events: string[]; } diff --git a/packages/core/src/controllers/AccountController.ts b/packages/core/src/controllers/AccountController.ts index 808d38f4..bd2172f7 100644 --- a/packages/core/src/controllers/AccountController.ts +++ b/packages/core/src/controllers/AccountController.ts @@ -1,9 +1,9 @@ import { proxy } from 'valtio'; import { subscribeKey as subKey } from 'valtio/utils'; -import type { Balance } from '@reown/appkit-common-react-native'; +import type { Balance, CaipAddress } from '@reown/appkit-common-react-native'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; -import type { AppKitFrameAccountType, CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil'; +import type { AppKitFrameAccountType, ConnectedWalletInfo } from '../utils/TypeUtil'; import { NetworkController } from './NetworkController'; import { BlockchainApiController } from './BlockchainApiController'; import { SnackController } from './SnackController'; diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 0f9dc08c..20691908 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -1,15 +1,18 @@ -import { proxy } from 'valtio'; -import type { BlockchainAdapter } from '@reown/appkit-common-react-native'; +import { proxy, ref } from 'valtio'; +import { derive } from 'valtio/utils'; +import type { + BlockchainAdapter, + CaipAddress, + CaipNetworkId +} from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // interface Connection { - accounts: string[]; + accounts: CaipAddress[]; balances: Record; activeChainId: string; adapter: BlockchainAdapter; - events: string[]; - chains: string[]; - methods: string[]; + chains: CaipNetworkId[]; } export interface ConnectionsControllerState { @@ -18,48 +21,66 @@ export interface ConnectionsControllerState { } // -- State --------------------------------------------- // -const state = proxy({ +const baseState = proxy({ activeNamespace: 'eip155', connections: {} }); +const derivedState = derive( + { + activeAddress: (get): string | null => { + const snap = get(baseState); + + if (!snap.activeNamespace) return null; + + const connection = snap.connections[snap.activeNamespace]; + + if (!connection || !connection.accounts || connection.accounts.length === 0) { + return null; + } + + const address = connection.accounts[0]?.split(':')[2]; + if (!address) return null; + + return address; + } + }, + { + proxy: baseState // Link derived proxy to the base state proxy + } +); + // -- Controller ---------------------------------------- // export const ConnectionsController = { - state, + state: derivedState, setActiveNamespace(namespace: string) { - state.activeNamespace = namespace; + baseState.activeNamespace = namespace; }, storeConnection({ namespace, adapter, accounts, - events, - chains, - methods + chains }: { namespace: string; adapter: BlockchainAdapter; - accounts: string[]; - events: string[]; - chains: string[]; - methods: string[]; + accounts: CaipAddress[]; + chains: CaipNetworkId[]; }) { - state.connections[namespace] = { + baseState.connections[namespace] = { balances: {}, activeChainId: chains[0]!, - adapter, + adapter: ref(adapter), accounts, - events, - chains, - methods + chains }; - console.log('ConnectionController:storeConnection - state.connections', state.connections); + console.log('ConnectionController:storeConnection - state.connections', baseState.connections); }, - updateAccounts(namespace: string, accounts: string[]) { - const connection = state.connections[namespace]; + updateAccounts(namespace: string, accounts: CaipAddress[]) { + const connection = baseState.connections[namespace]; if (!connection) { return; } @@ -67,7 +88,7 @@ export const ConnectionsController = { }, updateBalances(namespace: string, balances: Record) { - const connection = state.connections[namespace]; + const connection = baseState.connections[namespace]; if (!connection) { return; } @@ -75,7 +96,7 @@ export const ConnectionsController = { }, updateChainId(namespace: string, chainId: string) { - const connection = state.connections[namespace]; + const connection = baseState.connections[namespace]; if (!connection) { return; } @@ -83,7 +104,7 @@ export const ConnectionsController = { }, async disconnect(namespace: string) { - const connection = state.connections[namespace]; + const connection = baseState.connections[namespace]; if (!connection) return; console.log('ConnectionController:disconnect - connection', connection); @@ -95,8 +116,8 @@ export const ConnectionsController = { console.log('ConnectionController:disconnect - connector', connector); // Find all namespaces that use the same connector - const namespacesUsingConnector = Object.keys(state.connections).filter( - ns => state.connections[ns]?.adapter.connector === connector + const namespacesUsingConnector = Object.keys(baseState.connections).filter( + ns => baseState.connections[ns]?.adapter.connector === connector ); console.log( @@ -106,7 +127,7 @@ export const ConnectionsController = { // Unsubscribe all event listeners from the adapter namespacesUsingConnector.forEach(ns => { - const _connection = state.connections[ns]; + const _connection = baseState.connections[ns]; if (_connection?.adapter) { _connection.adapter.removeAllListeners(); } @@ -117,9 +138,9 @@ export const ConnectionsController = { // Remove all namespaces that used this connector namespacesUsingConnector.forEach(ns => { - delete state.connections[ns]; + delete baseState.connections[ns]; }); - console.log('ConnectionController:disconnect - state.connections', state.connections); + console.log('ConnectionController:disconnect - baseState.connections', baseState.connections); } }; diff --git a/packages/core/src/controllers/ConnectorController.ts b/packages/core/src/controllers/ConnectorController.ts index 7ff77664..64c4b525 100644 --- a/packages/core/src/controllers/ConnectorController.ts +++ b/packages/core/src/controllers/ConnectorController.ts @@ -50,6 +50,7 @@ export const ConnectorController = { if (saveStorage) { if (connectorType) { + //TODO: Check this StorageUtil.setConnectedConnector(connectorType); } else { StorageUtil.removeConnectedConnector(); diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index f1023c95..8901de32 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -1,5 +1,5 @@ import { proxy, ref } from 'valtio'; -import type { CaipNetwork, CaipNetworkId } from '../utils/TypeUtil'; +import type { CaipNetwork, CaipNetworkId } from '@reown/appkit-common-react-native'; import { PublicStateController } from './PublicStateController'; import { NetworkUtil } from '@reown/appkit-common-react-native'; import { ConstantsUtil } from '../utils/ConstantsUtil'; diff --git a/packages/core/src/controllers/PublicStateController.ts b/packages/core/src/controllers/PublicStateController.ts index ca95a55a..fa093aef 100644 --- a/packages/core/src/controllers/PublicStateController.ts +++ b/packages/core/src/controllers/PublicStateController.ts @@ -1,6 +1,6 @@ import { proxy, subscribe as sub } from 'valtio'; import { subscribeKey as subKey } from 'valtio/utils'; -import type { CaipNetworkId } from '../utils/TypeUtil.js'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface PublicStateControllerState { diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 703f369e..f6702908 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -1,7 +1,8 @@ import { proxy } from 'valtio'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; + import type { WcWallet, - CaipNetwork, Connector, SwapInputTarget, OnRampTransactionResult diff --git a/packages/core/src/utils/AssetUtil.ts b/packages/core/src/utils/AssetUtil.ts index 30efc3eb..3ea47027 100644 --- a/packages/core/src/utils/AssetUtil.ts +++ b/packages/core/src/utils/AssetUtil.ts @@ -1,5 +1,6 @@ +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { AssetController } from '../controllers/AssetController'; -import type { CaipNetwork, Connector, WcWallet } from './TypeUtil'; +import type { Connector, WcWallet } from './TypeUtil'; export const AssetUtil = { getWalletImage(wallet?: WcWallet) { diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 07914a69..1df99944 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -1,12 +1,17 @@ /* eslint-disable no-bitwise */ import { Linking, Platform } from 'react-native'; -import { ConstantsUtil as CommonConstants, type Balance } from '@reown/appkit-common-react-native'; +import { + ConstantsUtil as CommonConstants, + type Balance, + type CaipAddress, + type CaipNetwork +} from '@reown/appkit-common-react-native'; + import * as ct from 'countries-and-timezones'; import { ConstantsUtil } from './ConstantsUtil'; -import type { CaipAddress, CaipNetwork, DataWallet, LinkingRecord } from './TypeUtil'; - +import type { DataWallet, LinkingRecord } from './TypeUtil'; // -- Helpers ----------------------------------------------------------------- async function isAppInstalledIos(deepLink?: string): Promise { try { diff --git a/packages/core/src/utils/NetworkUtil.ts b/packages/core/src/utils/NetworkUtil.ts index 4ab2b5b4..4795b60f 100644 --- a/packages/core/src/utils/NetworkUtil.ts +++ b/packages/core/src/utils/NetworkUtil.ts @@ -4,7 +4,7 @@ import { NetworkController } from '../controllers/NetworkController'; import { AccountController } from '../controllers/AccountController'; import { ConnectorController } from '../controllers/ConnectorController'; import { SwapController } from '../controllers/SwapController'; -import type { CaipNetwork } from '../utils/TypeUtil'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; export const NetworkUtil = { async handleNetworkSwitch(network: CaipNetwork) { diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index acddf5c0..0c253503 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -10,6 +10,7 @@ import type { import { DateUtil, type SocialProvider, + type New_ConnectorType, type ConnectorType } from '@reown/appkit-common-react-native'; @@ -18,6 +19,7 @@ const WC_DEEPLINK = 'WALLETCONNECT_DEEPLINK_CHOICE'; const RECENT_WALLET = '@w3m/recent'; const CONNECTED_WALLET_IMAGE_URL = '@w3m/connected_wallet_image_url'; const CONNECTED_CONNECTOR = '@w3m/connected_connector'; +const CONNECTED_CONNECTORS = '@appkit/connected_connectors'; const CONNECTED_SOCIAL = '@appkit/connected_social'; const ONRAMP_PREFERRED_COUNTRY = '@appkit/onramp_preferred_country'; const ONRAMP_COUNTRIES = '@appkit/onramp_countries'; @@ -99,6 +101,7 @@ export const StorageUtil = { return []; }, + //TODO: remove this async setConnectedConnector(connectorType: ConnectorType) { try { await AsyncStorage.setItem(CONNECTED_CONNECTOR, JSON.stringify(connectorType)); @@ -127,6 +130,47 @@ export const StorageUtil = { } }, + async setConnectedConnectors({ + type, + namespaces + }: { + type: New_ConnectorType; + namespaces: string[]; + }) { + try { + const currentConnectors = (await StorageUtil.getConnectedConnectors()) || []; + // Only add if it doesn't exist already + if (!currentConnectors.some(c => c.type === type)) { + const updatedConnectors = [...currentConnectors, { type, namespaces }]; + await AsyncStorage.setItem(CONNECTED_CONNECTORS, JSON.stringify(updatedConnectors)); + } + } catch { + console.info('Unable to set Connected Connector'); + } + }, + + async getConnectedConnectors(): Promise<{ type: New_ConnectorType; namespaces: string[] }[]> { + try { + const connectors = await AsyncStorage.getItem(CONNECTED_CONNECTORS); + + return connectors ? JSON.parse(connectors) : []; + } catch { + console.info('Unable to get Connected Connector'); + } + + return []; + }, + + async removeConnectedConnectors(type: New_ConnectorType) { + try { + const currentConnectors = await StorageUtil.getConnectedConnectors(); + const updatedConnectors = currentConnectors.filter(c => c.type !== type); + await AsyncStorage.setItem(CONNECTED_CONNECTORS, JSON.stringify(updatedConnectors)); + } catch { + console.info('Unable to remove Connected Connector'); + } + }, + async setConnectedWalletImageUrl(url: string) { try { await AsyncStorage.setItem(CONNECTED_WALLET_IMAGE_URL, url); diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 7e239b40..357dcf1b 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,4 +1,5 @@ import { type EventEmitter } from 'events'; +import type { CaipAddress, CaipNetworkId } from '@reown/appkit-common-react-native'; import type { Balance, @@ -14,17 +15,6 @@ export interface BaseError { message?: string; } -export type CaipAddress = `${string}:${string}:${string}`; - -export type CaipNetworkId = `${string}:${string}`; - -export interface CaipNetwork { - id: CaipNetworkId; - name?: string; - imageId?: string; - imageUrl?: string; -} - export type ConnectedWalletInfo = | { name?: string; diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index c6204de2..8c40434e 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -13,9 +13,6 @@ import { toUtf8Bytes } from 'ethers'; import { - type CaipAddress, - type CaipNetwork, - type CaipNetworkId, type ConnectionControllerClient, type Connector, type LibraryOptions, @@ -29,6 +26,9 @@ import { type EstimateGasTransactionArgs } from '@reown/appkit-scaffold-react-native'; import { + type CaipAddress, + type CaipNetwork, + type CaipNetworkId, erc20ABI, ErrorUtil, NamesUtil, diff --git a/packages/ethers/src/utils/helpers.ts b/packages/ethers/src/utils/helpers.ts index 2035ecb4..e2197eb4 100644 --- a/packages/ethers/src/utils/helpers.ts +++ b/packages/ethers/src/utils/helpers.ts @@ -1,4 +1,4 @@ -import type { CaipNetworkId } from '@reown/appkit-scaffold-react-native'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; import EthereumProvider from '@walletconnect/ethereum-provider'; diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index c36e0c81..8f072962 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -1,9 +1,6 @@ import { Contract, ethers, utils } from 'ethers'; import { type AppKitFrameAccountType, - type CaipAddress, - type CaipNetwork, - type CaipNetworkId, type ConnectionControllerClient, type Connector, type EstimateGasTransactionArgs, @@ -37,6 +34,9 @@ import { type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; import { + type CaipAddress, + type CaipNetwork, + type CaipNetworkId, erc20ABI, ErrorUtil, NamesUtil, diff --git a/packages/ethers5/src/utils/helpers.ts b/packages/ethers5/src/utils/helpers.ts index 2035ecb4..e2197eb4 100644 --- a/packages/ethers5/src/utils/helpers.ts +++ b/packages/ethers5/src/utils/helpers.ts @@ -1,4 +1,4 @@ -import type { CaipNetworkId } from '@reown/appkit-scaffold-react-native'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; import EthereumProvider from '@walletconnect/ethereum-provider'; diff --git a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts index 338fbd47..9ec41582 100644 --- a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts @@ -1,4 +1,4 @@ -import type { CaipNetwork } from '@reown/appkit-scaffold-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common-react-native'; import type { Chain, Provider } from './EthersTypesUtil'; diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index 1d8d29d7..631bc2fa 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -14,11 +14,11 @@ import { OptionsController, RouterController, TransactionsController, - type CaipAddress, type AppKitFrameProvider, WebviewController, ThemeController } from '@reown/appkit-core-react-native'; +import type { CaipAddress } from '@reown/appkit-common-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; import { AppKitRouter } from '../w3m-router'; diff --git a/packages/scaffold/src/views/w3m-networks-view/index.tsx b/packages/scaffold/src/views/w3m-networks-view/index.tsx index 30bdd029..41f98b3b 100644 --- a/packages/scaffold/src/views/w3m-networks-view/index.tsx +++ b/packages/scaffold/src/views/w3m-networks-view/index.tsx @@ -13,11 +13,11 @@ import { AssetUtil, NetworkController, RouterController, - type CaipNetwork, EventsController, CoreHelperUtil, NetworkUtil } from '@reown/appkit-core-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; diff --git a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx b/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx index 3020ab02..38cbc626 100644 --- a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx @@ -9,9 +9,10 @@ import { EventsController, NetworkController, NetworkUtil, - type CaipNetwork, type NetworkControllerState } from '@reown/appkit-core-react-native'; +import type { CaipNetwork } from '@reown/appkit-common-react-native'; + import styles from './styles'; export function UnsupportedChainView() { diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 5c4a4aac..1fef84c5 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -26,9 +26,6 @@ import { mainnet, type Chain } from '@wagmi/core/chains'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { - type CaipAddress, - type CaipNetwork, - type CaipNetworkId, type ConnectionControllerClient, type Connector, type LibraryOptions, @@ -48,7 +45,10 @@ import { ErrorUtil, ConstantsUtil, PresetsUtil, - type ConnectorType + type ConnectorType, + type CaipAddress, + type CaipNetwork, + type CaipNetworkId } from '@reown/appkit-common-react-native'; import { SIWEController, diff --git a/packages/wagmi/src/utils/helpers.ts b/packages/wagmi/src/utils/helpers.ts index 78a325b9..f773d5fc 100644 --- a/packages/wagmi/src/utils/helpers.ts +++ b/packages/wagmi/src/utils/helpers.ts @@ -1,9 +1,10 @@ +import { CoreHelperUtil } from '@reown/appkit-scaffold-react-native'; import { - CoreHelperUtil, + PresetsUtil, + ConstantsUtil, type CaipNetwork, type CaipNetworkId -} from '@reown/appkit-scaffold-react-native'; -import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; +} from '@reown/appkit-common-react-native'; import type { Connector } from '@wagmi/core'; import { EthereumProvider } from '@walletconnect/ethereum-provider'; import type { AppKitClientOptions } from '../client'; From bc0ac74e66d37cce6a9ca793a28c5b9fb34b6876 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:56:01 -0300 Subject: [PATCH 05/91] chore: getting account balance from adapter --- packages/appkit/src/AppKit.ts | 72 ++++++++++++------- .../src/modal/w3m-account-button/index.tsx | 17 +++-- .../views/w3m-account-default-view/index.tsx | 26 ++++--- .../src/views/w3m-connecting-view/index.tsx | 5 +- .../w3m-unsupported-chain-view/index.tsx | 4 +- packages/common/src/utils/TypeUtil.ts | 12 +++- .../src/controllers/ConnectionsController.ts | 56 +++++++++++---- packages/ethers/src/adapter.ts | 48 ++++++++++++- packages/scaffold-utils/package.json | 1 + yarn.lock | 11 +-- 10 files changed, 177 insertions(+), 75 deletions(-) diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index d602bca1..43ea2d91 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -15,7 +15,8 @@ import type { BlockchainAdapter, ProposalNamespaces, New_ConnectorType, - Namespaces + Namespaces, + CaipNetworkId // Namespaces, } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; @@ -31,7 +32,7 @@ export class AppKit { private projectId: string; private metadata: Metadata; private adapters: BlockchainAdapter[]; - private networks: any[]; //TODO: define type for networks + // private networks: any[]; //TODO: define type for networks // private namespaces: Namespaces; private extraConnectors: WalletConnector[]; @@ -39,7 +40,7 @@ export class AppKit { this.projectId = config.projectId; this.metadata = config.metadata; this.adapters = config.adapters; - this.networks = config.networks; + // this.networks = config.networks; // this.namespaces = this.getNamespaces(config.networks); this.extraConnectors = config.extraConnectors || []; // console.log(this.networks?.length); // Removed console log @@ -69,30 +70,38 @@ export class AppKit { private async initConnectors() { const connectedConnectors = await StorageUtil.getConnectedConnectors(); // Fetch stored connectors - for (const connected of connectedConnectors) { - try { - const connector = await this.createConnector(connected.type); - - const namespaces = connector.getNamespaces(); - if (namespaces && Object.keys(namespaces).length > 0) { - // Ensure namespaces is not empty - // Setup adapters and subscribe to events - const initializedAdapters = this._setupAdaptersAndSubscribe( - connector, - Object.keys(namespaces) - ); - - // If adapters were successfully initialized, store the connection details - if (initializedAdapters.length > 0) { - this._storeConnectionDetails(initializedAdapters, namespaces); + if (connectedConnectors.length > 0) { + ModalController.setLoading(true); + + for (const connected of connectedConnectors) { + try { + const connector = await this.createConnector(connected.type); + + const namespaces = connector.getNamespaces(); + if (namespaces && Object.keys(namespaces).length > 0) { + // Ensure namespaces is not empty + // Setup adapters and subscribe to events + const initializedAdapters = this._setupAdaptersAndSubscribe( + connector, + Object.keys(namespaces) + ); + + // If adapters were successfully initialized, store the connection details + if (initializedAdapters.length > 0) { + this._storeConnectionDetails(initializedAdapters, namespaces); + } + + this.syncAccounts(initializedAdapters); + + AccountController.setIsConnected(true); } - AccountController.setIsConnected(true); + } catch (error) { + // Use console.warn for non-critical initialization failures + console.warn(`Failed to initialize connector type ${connected.type}:`, error); + await StorageUtil.removeConnectedConnectors(connected.type); } - } catch (error) { - // Use console.warn for non-critical initialization failures - console.warn(`Failed to initialize connector type ${connected.type}:`, error); - await StorageUtil.removeConnectedConnectors(connected.type); } + ModalController.setLoading(false); } } @@ -163,6 +172,8 @@ export class AppKit { namespaces: Object.keys(approvedNamespaces) }); + this.syncAccounts(approvedAdapters); + // Set connected state (consider if this should be more nuanced for multi-connections) AccountController.setIsConnected(true); @@ -175,6 +186,11 @@ export class AppKit { } } + private async syncAccounts(adapters: BlockchainAdapter[]) { + // Get account balance + adapters.map(adapter => adapter.getBalance({ address: adapter.getAccounts()?.[0] })); + } + /** * Stores connection details in the ConnectionsController. * @param adapters - The adapters for which to store the connection. @@ -211,7 +227,8 @@ export class AppKit { adapter.on('chainChanged', ({ chainId, namespace }) => { // console.log(`Chain changed for namespace: ${namespace}`); // Removed console log - ConnectionsController.updateChainId(namespace, chainId); + const chain = `${namespace}:${chainId}` as CaipNetworkId; + ConnectionsController.updateChain(namespace, chain); }); adapter.on('disconnect', ({ namespace }) => { @@ -220,6 +237,11 @@ export class AppKit { // Potentially remove from storage on disconnect event as well // StorageUtil.removeConnectedConnectors(connectorType); // Need connectorType here }); + + adapter.on('balanceChanged', ({ namespace, address, balance }) => { + // console.log('balanceChanged', namespace, address, balance); + ConnectionsController.updateBalance(namespace, address, balance); + }); } private async initControllers(options: AppKitConfig) { diff --git a/packages/appkit/src/modal/w3m-account-button/index.tsx b/packages/appkit/src/modal/w3m-account-button/index.tsx index 64559c58..beb7c191 100644 --- a/packages/appkit/src/modal/w3m-account-button/index.tsx +++ b/packages/appkit/src/modal/w3m-account-button/index.tsx @@ -21,15 +21,10 @@ export interface AccountButtonProps { } export function AccountButton({ balance, disabled, style, testID }: AccountButtonProps) { - const { - balance: balanceVal, - balanceSymbol, - profileImage, - profileName - } = useSnapshot(AccountController.state); + const { profileImage, profileName } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); - const { activeAddress: address } = useSnapshot(ConnectionsController.state); + const { activeAddress: address, activeBalance } = useSnapshot(ConnectionsController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); const showBalance = balance === 'show'; @@ -38,14 +33,18 @@ export function AccountButton({ balance, disabled, style, testID }: AccountButto ModalController.open()} - address={address ?? ''} + address={address?.split(':')[2] ?? ''} profileName={profileName} networkSrc={networkImage} imageHeaders={ApiController._getApiHeaders()} avatarSrc={profileImage} disabled={disabled} style={style} - balance={showBalance ? CoreHelperUtil.formatBalance(balanceVal, balanceSymbol) : ''} + balance={ + showBalance + ? CoreHelperUtil.formatBalance(activeBalance?.amount, activeBalance?.symbol) + : '' + } testID={testID} /> diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 41d53973..030ee03e 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -38,16 +38,14 @@ import { AuthButtons } from './components/auth-buttons'; import styles from './styles'; export function AccountDefaultView() { - const { - profileName, - profileImage, - balance, - balanceSymbol, - addressExplorerUrl, - preferredAccountType - } = useSnapshot(AccountController.state); + const { profileName, profileImage, addressExplorerUrl, preferredAccountType } = useSnapshot( + AccountController.state + ); const { loading } = useSnapshot(ModalController.state); - const { activeAddress: address } = useSnapshot(ConnectionsController.state); + const { activeAddress: address, activeBalance: balance } = useSnapshot( + ConnectionsController.state + ); + const account = address?.split(':')[2]; const [disconnecting, setDisconnecting] = useState(false); const { caipNetwork } = useSnapshot(NetworkController.state); const { connectedConnector } = useSnapshot(ConnectorController.state); @@ -63,10 +61,10 @@ export function AccountDefaultView() { const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); const { padding } = useCustomDimensions(); const { appKit } = useAppKit(); + async function onDisconnect() { setDisconnecting(true); - //TODO: USE ACTIVE NAMESPACE - await appKit?.disconnect('eip155'); + await appKit?.disconnect(ConnectionsController.state.activeNamespace); setDisconnecting(false); } @@ -191,7 +189,7 @@ export function AccountDefaultView() { /> - + {profileName @@ -202,7 +200,7 @@ export function AccountDefaultView() { truncate: 'end' }) : UiUtil.getTruncateString({ - string: address ?? '', + string: account ?? '', charsStart: 4, charsEnd: 6, truncate: 'middle' @@ -220,7 +218,7 @@ export function AccountDefaultView() { {showBalance && ( - {CoreHelperUtil.formatBalance(balance, balanceSymbol)} + {CoreHelperUtil.formatBalance(balance.amount, balance.symbol)} )} {showExplorer && ( diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 3a51b9fd..603556bd 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -11,8 +11,7 @@ import { type Platform, OptionsController, ApiController, - EventsController, - ConnectorController + EventsController } from '@reown/appkit-core-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; import type { CaipNetworkId } from '@reown/appkit-common-react-native'; @@ -76,7 +75,7 @@ export function ConnectingView() { ConnectionController.setWcPromise(wcPromise); await wcPromise; // await ConnectionController.state.wcPromise; - ConnectorController.setConnectedConnector('WALLET_CONNECT'); + // ConnectorController.setConnectedConnector('WALLET_CONNECT'); AccountController.setIsConnected(true); if (OptionsController.state.isSiweEnabled) { diff --git a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx index 63078a80..5c044f69 100644 --- a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx @@ -5,6 +5,7 @@ import { Icon, ListItem, Separator, Text } from '@reown/appkit-ui-react-native'; import { ApiController, AssetUtil, + ConnectionsController, CoreHelperUtil, EventsController, NetworkController, @@ -39,8 +40,7 @@ export function UnsupportedChainView() { const onDisconnect = async () => { setDisconnecting(true); - //TODO: USE ACTIVE NAMESPACE - await appKit?.disconnect('eip155'); + await appKit?.disconnect(ConnectionsController.state.activeNamespace); setDisconnecting(false); }; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 1ef20a0c..15ae2423 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -139,14 +139,24 @@ export abstract class BlockchainAdapter extends EventEmitter { abstract disconnect(): Promise; abstract request(method: string, params?: any[]): Promise; abstract getSupportedNamespace(): string; + abstract getBalance(params: GetBalanceParams): Promise; + abstract getAccounts(): CaipAddress[] | undefined; } export abstract class EVMAdapter extends BlockchainAdapter { abstract signTransaction(tx: TransactionData): Promise; - abstract getBalance(address: string): Promise; abstract sendTransaction(tx: TransactionData): Promise; } +export interface GetBalanceParams { + address?: CaipAddress; +} + +export interface GetBalanceResponse { + amount: string; + symbol: string; +} + //********** Connector Types **********// interface BaseNamespace { chains?: CaipNetworkId[]; diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 20691908..958357d6 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -7,12 +7,19 @@ import type { } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // + +interface Balance { + amount: string; + symbol: string; +} + interface Connection { accounts: CaipAddress[]; - balances: Record; - activeChainId: string; + activeAccount: CaipAddress; + balances: Record; adapter: BlockchainAdapter; chains: CaipNetworkId[]; + activeChain: CaipNetworkId; } export interface ConnectionsControllerState { @@ -28,21 +35,41 @@ const baseState = proxy({ const derivedState = derive( { - activeAddress: (get): string | null => { + activeAddress: (get): CaipAddress | undefined => { const snap = get(baseState); - if (!snap.activeNamespace) return null; + if (!snap.activeNamespace) return undefined; const connection = snap.connections[snap.activeNamespace]; - if (!connection || !connection.accounts || connection.accounts.length === 0) { - return null; + if ( + !connection || + !connection.accounts || + !connection.activeAccount || + connection.accounts.length === 0 + ) { + return undefined; } - const address = connection.accounts[0]?.split(':')[2]; - if (!address) return null; + return connection.activeAccount; + }, + activeBalance: (get): Balance | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + const connection = snap.connections[snap.activeNamespace]; + + if ( + !connection || + !connection.balances || + !connection.activeAccount || + Object.keys(connection.balances).length === 0 + ) { + return undefined; + } - return address; + return connection.balances[connection.activeAccount]; } }, { @@ -71,9 +98,10 @@ export const ConnectionsController = { }) { baseState.connections[namespace] = { balances: {}, - activeChainId: chains[0]!, + activeChain: chains[0]!, adapter: ref(adapter), accounts, + activeAccount: accounts[0]!, chains }; console.log('ConnectionController:storeConnection - state.connections', baseState.connections); @@ -87,20 +115,20 @@ export const ConnectionsController = { connection.accounts = accounts; }, - updateBalances(namespace: string, balances: Record) { + updateBalance(namespace: string, address: CaipAddress, balance: Balance) { const connection = baseState.connections[namespace]; if (!connection) { return; } - connection.balances = balances; + connection.balances[address] = balance; }, - updateChainId(namespace: string, chainId: string) { + updateChain(namespace: string, chain: CaipNetworkId) { const connection = baseState.connections[namespace]; if (!connection) { return; } - connection.activeChainId = chainId; + connection.activeChain = chain; }, async disconnect(namespace: string) { diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 696296b0..7ba4a877 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -1,6 +1,10 @@ +import { formatEther, JsonRpcProvider } from 'ethers'; import { EVMAdapter, WalletConnector, + type CaipAddress, + type GetBalanceParams, + type GetBalanceResponse, type SignedTransaction, type TransactionData, type TransactionReceipt @@ -22,10 +26,50 @@ export class EthersAdapter extends EVMAdapter { return this.request('eth_signTransaction', [tx]) as Promise; } - async getBalance(address: string): Promise { + async getBalance(params: GetBalanceParams): Promise { if (!this.connector) throw new Error('No active connector'); - return this.request('eth_getBalance', [address, 'latest']) as Promise; + const address = params.address || this.getAccounts()?.[0]; + + if (!address) { + return Promise.resolve({ amount: '0.00', symbol: 'ETH' }); + } + + const chainId = Number(address.split(':')[1]) ?? 1; + const account = address.split(':')[2]; + + try { + //TODO use networks + const jsonRpcProvider = new JsonRpcProvider('https://eth.llamarpc.com', { + chainId: chainId, + name: 'Ethereum Mainnet' + }); + let balance = { amount: '0.00', symbol: 'ETH' }; + + if (jsonRpcProvider && account) { + const _balance = await jsonRpcProvider.getBalance(account); + const formattedBalance = formatEther(_balance); + + balance = { amount: formattedBalance, symbol: 'ETH' }; + } + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address, + balance + }); + + return balance; + } catch (error) { + return { amount: '0.00', symbol: 'ETH' }; + } + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) throw new Error('No active connector'); + const namespaces = this.connector.getNamespaces(); + + return namespaces[this.getSupportedNamespace()]?.accounts; } sendTransaction(/*tx: TransactionData*/): Promise { diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index dbfa9b21..823a6b54 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -35,6 +35,7 @@ "access": "public" }, "dependencies": { + "@reown/appkit-core-react-native": "1.2.3", "@reown/appkit-common-react-native": "1.2.3", "@reown/appkit-scaffold-react-native": "1.2.3" }, diff --git a/yarn.lock b/yarn.lock index 623f2735..e1d6a5ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7268,9 +7268,9 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-scaffold-react-native@npm:1.2.3, @reown/appkit-scaffold-react-native@workspace:packages/scaffold": - version: 0.0.0-use.local - resolution: "@reown/appkit-scaffold-react-native@workspace:packages/scaffold" +"@reown/appkit-scaffold-react-native@npm:1.2.3": + version: 1.2.3 + resolution: "@reown/appkit-scaffold-react-native@npm:1.2.3" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" "@reown/appkit-core-react-native": "npm:1.2.3" @@ -7280,8 +7280,9 @@ __metadata: react: ">=17" react-native: ">=0.68.5" react-native-modal: ">=13" - languageName: unknown - linkType: soft + checksum: a8cd99392bc1b2afa69adf904d9b970bbf707b1aea41d147ff63d884e49a77da3cb6482ffde9f61244196d884c906d99c9c90a164af3146c4e68ad5f4f1bd730 + languageName: node + linkType: hard "@reown/appkit-scaffold-utils-react-native@npm:1.2.3, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": version: 0.0.0-use.local From ceb315613249f4d0d7d99233aa69d2ba1f6a04c7 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 11 Apr 2025 14:03:10 -0300 Subject: [PATCH 06/91] chore: implemented missing functions in wagmi adapter --- packages/wagmi/src/adapter.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index bad2c3e0..bf4cf983 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -1,6 +1,9 @@ import { EVMAdapter, WalletConnector, + type CaipAddress, + type GetBalanceParams, + type GetBalanceResponse, type SignedTransaction, type TransactionData, type TransactionReceipt @@ -61,10 +64,20 @@ export class WagmiAdapter extends EVMAdapter { return this.request('eth_signTransaction', [tx]) as Promise; } - async getBalance(address: string): Promise { + async getBalance(params: GetBalanceParams): Promise { if (!this.connector) throw new Error('No active connector'); + const address = params.address || this.getAccounts()?.[0]; - return this.request('eth_getBalance', [address, 'latest']) as Promise; + console.log('WagmiAdapter - getBalance', address); + + return Promise.resolve({ amount: '0.00', symbol: 'ETH' }); + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) throw new Error('No active connector'); + const namespaces = this.connector.getNamespaces(); + + return namespaces[this.getSupportedNamespace()]?.accounts; } sendTransaction(/*tx: TransactionData*/): Promise { From a8e11d2e7226c227efdadfc77b38690c605bfa15 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:08:12 -0300 Subject: [PATCH 07/91] chore: get balance/switch network --- apps/native/App.tsx | 53 ++--- apps/native/src/utils/ChainUtils.ts | 16 ++ packages/appkit/src/AppKit.ts | 72 ++++-- packages/appkit/src/utils/HelpersUtil.ts | 206 ++++++++++++++++++ .../views/w3m-account-default-view/index.tsx | 10 +- .../src/views/w3m-connecting-view/index.tsx | 24 +- .../src/views/w3m-networks-view/index.tsx | 33 +-- packages/common/src/utils/TypeUtil.ts | 23 ++ .../src/controllers/ConnectionsController.ts | 50 ++++- packages/core/src/utils/TypeUtil.ts | 2 +- packages/ethers/src/adapter.ts | 69 ++++-- packages/scaffold-utils/package.json | 2 +- .../src/utils/EthersHelpersUtil.ts | 1 + packages/wagmi/src/adapter.ts | 6 + yarn.lock | 1 + 15 files changed, 451 insertions(+), 117 deletions(-) create mode 100644 apps/native/src/utils/ChainUtils.ts create mode 100644 packages/appkit/src/utils/HelpersUtil.ts diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 96deff7b..3803b031 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -1,8 +1,8 @@ -import { Platform, SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; +import { SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; import { StatusBar } from 'expo-status-bar'; -import * as Clipboard from 'expo-clipboard'; +// import * as Clipboard from 'expo-clipboard'; import '@walletconnect/react-native-compat'; -import { WagmiProvider } from 'wagmi'; +// import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Toast from 'react-native-toast-message'; @@ -17,18 +17,20 @@ import Toast from 'react-native-toast-message'; import { AppKitProvider, createAppKit, AppKit, AppKitButton } from '@reown/appkit-react-native'; -import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; +// import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; import { Text } from '@reown/appkit-ui-react-native'; -import { siweConfig } from './src/utils/SiweUtils'; +// import { siweConfig } from './src/utils/SiweUtils'; -import { AccountView } from './src/views/AccountView'; -import { ActionsView } from './src/views/ActionsView'; -import { getCustomWallets } from './src/utils/misc'; -import { chains } from './src/utils/WagmiUtils'; -import { OpenButton } from './src/components/OpenButton'; -import { DisconnectButton } from './src/components/DisconnectButton'; +// import { AccountView } from './src/views/AccountView'; +// import { ActionsView } from './src/views/ActionsView'; +// import { getCustomWallets } from './src/utils/misc'; +// import { chains } from './src/utils/WagmiUtils'; +// import { OpenButton } from './src/components/OpenButton'; +// import { DisconnectButton } from './src/components/DisconnectButton'; import { EthersAdapter } from '@reown/appkit-ethers-react-native'; +import { mainnet, polygon, avalanche } from 'viem/chains'; +import { solana } from './src/utils/ChainUtils'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -43,19 +45,19 @@ const metadata = { } }; -const clipboardClient = { - setString: async (value: string) => { - await Clipboard.setStringAsync(value); - } -}; +// const clipboardClient = { +// setString: async (value: string) => { +// await Clipboard.setStringAsync(value); +// } +// }; -const auth = authConnector({ projectId, metadata }); +// const auth = authConnector({ projectId, metadata }); -const extraConnectors = Platform.select({ - ios: [auth], - android: [auth], - default: [] -}); +// const extraConnectors = Platform.select({ +// ios: [auth], +// android: [auth], +// default: [] +// }); // const wagmiConfig = defaultWagmiConfig({ // chains, @@ -66,7 +68,7 @@ const extraConnectors = Platform.select({ const queryClient = new QueryClient(); -const customWallets = getCustomWallets(); +// const customWallets = getCustomWallets(); // const wagmiAdapter = new WagmiAdapter({ // wagmiConfig, @@ -100,8 +102,7 @@ const appKit = createAppKit({ projectId, adapters: [ethersAdapter], metadata, - networks: chains, - // namespaces: custom namespaces + networks: [mainnet, polygon, avalanche] }); export default function Native() { @@ -109,7 +110,7 @@ export default function Native() { return ( // - + diff --git a/apps/native/src/utils/ChainUtils.ts b/apps/native/src/utils/ChainUtils.ts new file mode 100644 index 00000000..11a3c250 --- /dev/null +++ b/apps/native/src/utils/ChainUtils.ts @@ -0,0 +1,16 @@ +import { CaipNetworkId, ChainNamespace } from '@reown/appkit-common-react-native'; + +export const solana = { + id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana', + network: 'solana-mainnet', + nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + }, + blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, + testnet: false, + chainNamespace: 'solana' as ChainNamespace, + caipNetworkId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' as CaipNetworkId, + deprecatedCaipNetworkId: 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ' as CaipNetworkId +}; diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 43ea2d91..0c7d01a6 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -16,15 +16,18 @@ import type { ProposalNamespaces, New_ConnectorType, Namespaces, - CaipNetworkId - // Namespaces, + CaipNetworkId, + AppKitNetwork } from '@reown/appkit-common-react-native'; + import { WalletConnectConnector } from './connectors/WalletConnectConnector'; +import { WcHelpersUtil } from './utils/HelpersUtil'; + interface AppKitConfig { projectId: string; metadata: Metadata; adapters: BlockchainAdapter[]; - networks: any[]; + networks: AppKitNetwork[]; extraConnectors?: WalletConnector[]; } @@ -32,18 +35,17 @@ export class AppKit { private projectId: string; private metadata: Metadata; private adapters: BlockchainAdapter[]; - // private networks: any[]; //TODO: define type for networks - // private namespaces: Namespaces; + private networks: AppKitNetwork[]; + private namespaces: ProposalNamespaces; //TODO: check if its ok to use universal provider NamespaceConfig here private extraConnectors: WalletConnector[]; constructor(config: AppKitConfig) { this.projectId = config.projectId; this.metadata = config.metadata; this.adapters = config.adapters; - // this.networks = config.networks; - // this.namespaces = this.getNamespaces(config.networks); + this.networks = config.networks; + this.namespaces = WcHelpersUtil.createNamespaces(config.networks) as ProposalNamespaces; this.extraConnectors = config.extraConnectors || []; - // console.log(this.networks?.length); // Removed console log this.initControllers(config); this.initConnectors(); @@ -144,13 +146,12 @@ export class AppKit { try { const connector = await this.createConnector(type); - // Connect using the connector and get approved namespaces first - const approvedNamespaces = await connector.connect(requestedNamespaces); + const approvedNamespaces = await connector.connect(requestedNamespaces ?? this.namespaces); if (!approvedNamespaces || Object.keys(approvedNamespaces).length === 0) { throw new Error('Connection cancelled or failed: No approved namespaces returned.'); } - // Now, setup adapters and subscribe *only* for the approved namespaces + // Setup adapters and subscribe to adapter events const approvedAdapters = this._setupAdaptersAndSubscribe( connector, Object.keys(approvedNamespaces) @@ -158,8 +159,7 @@ export class AppKit { // Check if any compatible adapters were found for the *approved* namespaces if (approvedAdapters.length === 0) { - // This case might happen if the user approved namespaces for which we have no adapters, - // or if _setupAdaptersAndSubscribe failed internally. + //TODO: handle case where devs want to connect to a namespace that has no adapters. Could use the provider directly. throw new Error('No compatible adapters found for the approved namespaces'); } @@ -188,7 +188,14 @@ export class AppKit { private async syncAccounts(adapters: BlockchainAdapter[]) { // Get account balance - adapters.map(adapter => adapter.getBalance({ address: adapter.getAccounts()?.[0] })); + adapters.map(adapter => { + const namespace = adapter.getSupportedNamespace(); + const connection = ConnectionsController.state.connections[namespace]; + const network = this.networks.find( + n => n.id === Number(connection?.activeChain?.split(':')[1]) + ); + adapter.getBalance({ address: adapter.getAccounts()?.[0], network }); + }); } /** @@ -221,25 +228,21 @@ export class AppKit { private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { adapter.on('accountsChanged', ({ accounts, namespace }) => { - // console.log(`Updating accounts for namespace: ${namespace}`); // Removed console log ConnectionsController.updateAccounts(namespace, accounts); }); adapter.on('chainChanged', ({ chainId, namespace }) => { - // console.log(`Chain changed for namespace: ${namespace}`); // Removed console log const chain = `${namespace}:${chainId}` as CaipNetworkId; - ConnectionsController.updateChain(namespace, chain); + ConnectionsController.setActiveChain(namespace, chain); }); adapter.on('disconnect', ({ namespace }) => { - // console.log(`Disconnect event received for ${namespace}`); // Removed console log ConnectionsController.disconnect(namespace); // Potentially remove from storage on disconnect event as well // StorageUtil.removeConnectedConnectors(connectorType); // Need connectorType here }); adapter.on('balanceChanged', ({ namespace, address, balance }) => { - // console.log('balanceChanged', namespace, address, balance); ConnectionsController.updateBalance(namespace, address, balance); }); } @@ -250,6 +253,8 @@ export class AppKit { if (options.metadata) { OptionsController.setMetadata(options.metadata); } + + ConnectionsController.setNetworks(options.networks); } async disconnect(namespace: string): Promise { @@ -292,6 +297,35 @@ export class AppKit { return connection.adapter.connector.getProvider() as T | null; } + + getActiveAdapter(): BlockchainAdapter | null { + const activeNamespace = ConnectionsController.state.activeNamespace; + if (!activeNamespace) return null; + + const connection = ConnectionsController.state.connections[activeNamespace]; + + return connection?.adapter ?? null; + } + + async switchNetwork(network: AppKitNetwork): Promise { + const adapter = this.getActiveAdapter(); + if (!adapter) throw new Error('No active adapter'); + + await adapter.switchNetwork(network); + + EventsController.sendEvent({ + type: 'track', + event: 'SWITCH_NETWORK', + properties: { + network: network.id + } + }); + + ConnectionsController.setActiveChain( + adapter.getSupportedNamespace(), + `${adapter.getSupportedNamespace()}:${network.id}` as CaipNetworkId + ); + } } export function createAppKit(config: AppKitConfig): AppKit { diff --git a/packages/appkit/src/utils/HelpersUtil.ts b/packages/appkit/src/utils/HelpersUtil.ts new file mode 100644 index 00000000..88c46d9d --- /dev/null +++ b/packages/appkit/src/utils/HelpersUtil.ts @@ -0,0 +1,206 @@ +import type { Namespace, NamespaceConfig } from '@walletconnect/universal-provider'; + +import type { AppKitNetwork, ChainNamespace } from '@reown/appkit-common-react-native'; +// import { EnsController, type OptionsControllerState } from '@reown/appkit-controllers' + +// import { solana, solanaDevnet } from '../networks/index.js' + +export const DEFAULT_METHODS = { + solana: [ + 'solana_signMessage', + 'solana_signTransaction', + 'solana_requestAccounts', + 'solana_getAccounts', + 'solana_signAllTransactions', + 'solana_signAndSendTransaction' + ], + eip155: [ + 'eth_accounts', + 'eth_requestAccounts', + 'eth_sendRawTransaction', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + 'eth_sendTransaction', + 'personal_sign', + 'wallet_switchEthereumChain', + 'wallet_addEthereumChain', + 'wallet_getPermissions', + 'wallet_requestPermissions', + 'wallet_registerOnboarding', + 'wallet_watchAsset', + 'wallet_scanQRCode', + // EIP-5792 + 'wallet_getCallsStatus', + 'wallet_showCallsStatus', + 'wallet_sendCalls', + 'wallet_getCapabilities', + // EIP-7715 + 'wallet_grantPermissions', + 'wallet_revokePermissions', + //EIP-7811 + 'wallet_getAssets' + ], + bip122: ['sendTransfer', 'signMessage', 'signPsbt', 'getAccountAddresses'] +}; + +export const WcHelpersUtil = { + getMethodsByChainNamespace(chainNamespace: ChainNamespace): string[] { + return DEFAULT_METHODS[chainNamespace as keyof typeof DEFAULT_METHODS] || []; + }, + createDefaultNamespace(chainNamespace: ChainNamespace): Namespace { + return { + methods: this.getMethodsByChainNamespace(chainNamespace), + events: ['accountsChanged', 'chainChanged'], + chains: [], + rpcMap: {} + }; + }, + + applyNamespaceOverrides( + baseNamespaces: NamespaceConfig, + overrides?: any //TODO: fix this + ): NamespaceConfig { + if (!overrides) { + return { ...baseNamespaces }; + } + + const result = { ...baseNamespaces }; + + const namespacesToOverride = new Set(); + + if (overrides.methods) { + Object.keys(overrides.methods).forEach(ns => namespacesToOverride.add(ns)); + } + + if (overrides.chains) { + Object.keys(overrides.chains).forEach(ns => namespacesToOverride.add(ns)); + } + + if (overrides.events) { + Object.keys(overrides.events).forEach(ns => namespacesToOverride.add(ns)); + } + + if (overrides.rpcMap) { + Object.keys(overrides.rpcMap).forEach(chainId => { + const [ns] = chainId.split(':'); + if (ns) { + namespacesToOverride.add(ns); + } + }); + } + + namespacesToOverride.forEach(ns => { + if (!result[ns]) { + result[ns] = this.createDefaultNamespace(ns as ChainNamespace); + } + }); + + if (overrides.methods) { + Object.entries(overrides.methods).forEach(([ns, methods]) => { + if (result[ns]) { + //@ts-ignore + result[ns].methods = methods; + } + }); + } + + if (overrides.chains) { + Object.entries(overrides.chains).forEach(([ns, chains]) => { + if (result[ns]) { + //@ts-ignore + result[ns].chains = chains; + } + }); + } + + if (overrides.events) { + Object.entries(overrides.events).forEach(([ns, events]) => { + if (result[ns]) { + //@ts-ignore + result[ns].events = events; + } + }); + } + + if (overrides.rpcMap) { + const processedNamespaces = new Set(); + + Object.entries(overrides.rpcMap).forEach(([chainId, rpcUrl]) => { + const [ns, id] = chainId.split(':'); + if (!ns || !id || !result[ns]) { + return; + } + + //@ts-ignore + if (!result[ns].rpcMap) { + //@ts-ignore + result[ns].rpcMap = {}; + } + + //@ts-ignore + if (!processedNamespaces.has(ns)) { + //@ts-ignore + result[ns].rpcMap = {}; + processedNamespaces.add(ns); + } + + //@ts-ignore + result[ns].rpcMap[id] = rpcUrl; + }); + } + + return result; + }, + + createNamespaces( + caipNetworks: AppKitNetwork[], + configOverride?: any //TODO: fix this + ): NamespaceConfig { + const defaultNamespaces = caipNetworks.reduce((acc, chain) => { + const { id, rpcUrls } = chain; + const chainNamespace = chain.chainNamespace || 'eip155'; + const rpcUrl = rpcUrls.default.http[0]; + + if (!acc[chainNamespace]) { + acc[chainNamespace] = this.createDefaultNamespace(chainNamespace); + } + + const caipNetworkId = `${chainNamespace}:${id}`; + + const namespace = acc[chainNamespace]; + + //@ts-ignore + namespace.chains.push(caipNetworkId); + + // Workaround for wallets that only support deprecated Solana network ID + // switch (caipNetworkId) { + // case solana.caipNetworkId: + // namespace.chains.push(solana.deprecatedCaipNetworkId) + // break + // case solanaDevnet.caipNetworkId: + // namespace.chains.push(solanaDevnet.deprecatedCaipNetworkId) + // break + // default: + // } + + if (namespace?.rpcMap && rpcUrl) { + namespace.rpcMap[id] = rpcUrl; + } + + return acc; + }, {}); + + return this.applyNamespaceOverrides(defaultNamespaces, configOverride); + } +}; + +export namespace WcHelpersUtil { + export type SessionEventData = { + id: string; + topic: string; + params: { chainId: string; event: { data: unknown; name: string } }; + }; +} diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 030ee03e..f5ec09a9 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -42,9 +42,11 @@ export function AccountDefaultView() { AccountController.state ); const { loading } = useSnapshot(ModalController.state); - const { activeAddress: address, activeBalance: balance } = useSnapshot( - ConnectionsController.state - ); + const { + activeAddress: address, + activeBalance: balance, + activeNetwork + } = useSnapshot(ConnectionsController.state); const account = address?.split(':')[2]; const [disconnecting, setDisconnecting] = useState(false); const { caipNetwork } = useSnapshot(NetworkController.state); @@ -260,7 +262,7 @@ export function AccountDefaultView() { style={styles.actionButton} > - {caipNetwork?.name} + {activeNetwork?.name} {!isAuth && isOnRampEnabled && ( diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 603556bd..4b8b3347 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -14,7 +14,6 @@ import { EventsController } from '@reown/appkit-core-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; -import type { CaipNetworkId } from '@reown/appkit-common-react-native'; import { useAppKit } from '../../AppKitContext'; import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; @@ -51,27 +50,8 @@ export function ConnectingView() { ConnectionController.setWcError(false); // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); - //TODO: check this - const namespaces = { - eip155: { - methods: [ - 'eth_sendTransaction', - 'eth_signTransaction', - 'eth_sign', - 'personal_sign', - 'eth_signTypedData' - ], - chains: ['eip155:1'] as CaipNetworkId[], - events: ['chainChanged', 'accountsChanged'] - }, - solana: { - methods: ['solana_signMessage'], - chains: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'] as CaipNetworkId[], - events: ['chainChanged', 'accountsChanged'] - } - }; - - const wcPromise = appKit?.connect('walletconnect', namespaces); + //TODO: check linkmode + const wcPromise = appKit?.connect('walletconnect'); ConnectionController.setWcPromise(wcPromise); await wcPromise; // await ConnectionController.state.wcPromise; diff --git a/packages/appkit/src/views/w3m-networks-view/index.tsx b/packages/appkit/src/views/w3m-networks-view/index.tsx index 41f98b3b..a2097072 100644 --- a/packages/appkit/src/views/w3m-networks-view/index.tsx +++ b/packages/appkit/src/views/w3m-networks-view/index.tsx @@ -10,20 +10,17 @@ import { } from '@reown/appkit-ui-react-native'; import { ApiController, - AssetUtil, NetworkController, RouterController, EventsController, - CoreHelperUtil, - NetworkUtil + ConnectionsController } from '@reown/appkit-core-react-native'; -import type { CaipNetwork } from '@reown/appkit-common-react-native'; +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; - +import { useAppKit } from '../../AppKitContext'; export function NetworksView() { - const { caipNetwork, requestedCaipNetworks, approvedCaipNetworkIds, supportsAllNetworks } = - NetworkController.state; + const { caipNetwork } = NetworkController.state; const imageHeaders = ApiController._getApiHeaders(); const { maxWidth: width, padding } = useCustomDimensions(); const numColumns = 4; @@ -32,6 +29,7 @@ export function NetworksView() { const itemGap = Math.abs( Math.trunc((usableWidth - numColumns * CardSelectWidth) / numColumns) / 2 ); + const { appKit } = useAppKit(); const onHelpPress = () => { RouterController.push('WhatIsANetwork'); @@ -39,19 +37,12 @@ export function NetworksView() { }; const networksTemplate = () => { - const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); + //TODO: should show requested networks disabled + // const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); + const networks = ConnectionsController.getConnectedNetworks(); - const onNetworkPress = async (network: CaipNetwork) => { - const result = await NetworkUtil.handleNetworkSwitch(network); - if (result?.type === 'SWITCH_NETWORK') { - EventsController.sendEvent({ - type: 'track', - event: 'SWITCH_NETWORK', - properties: { - network: network.id - } - }); - } + const onNetworkPress = async (network: AppKitNetwork) => { + await appKit.switchNetwork(network); }; return networks.map(network => ( @@ -69,9 +60,9 @@ export function NetworksView() { testID={`w3m-network-switch-${network.name ?? network.id}`} name={network.name ?? 'Unknown'} type="network" - imageSrc={AssetUtil.getNetworkImage(network)} + // imageSrc={AssetUtil.getNetworkImage(network.caipNetworkId)} imageHeaders={imageHeaders} - disabled={!supportsAllNetworks && !approvedCaipNetworkIds?.includes(network.id)} + // disabled={!supportsAllNetworks && !approvedCaipNetworkIds?.includes(network.caipNetworkId)} selected={caipNetwork?.id === network.id} onPress={() => onNetworkPress(network)} /> diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 15ae2423..96bf317d 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -4,6 +4,27 @@ export type CaipAddress = `${string}:${string}:${string}`; export type CaipNetworkId = `${string}:${string}`; +export type ChainNamespace = 'eip155' | 'solana' | 'polkadot' | 'bip122' | string; + +export type AppKitNetwork = { + // Core viem/chain properties + id: number | string; + name: string; + nativeCurrency: { name: string; symbol: string; decimals: number }; + rpcUrls: { + default: { http: readonly string[] }; + [key: string]: { http: readonly string[] } | undefined; + }; + blockExplorers?: { + default: { name: string; url: string }; + [key: string]: { name: string; url: string } | undefined; + }; + + // AppKit specific / CAIP properties (Optional in type, but often needed in practice) + chainNamespace?: ChainNamespace; // e.g., 'eip155' + caipNetworkId?: CaipNetworkId; // e.g., 'eip155:1' +}; + export interface CaipNetwork { id: CaipNetworkId; name?: string; @@ -141,6 +162,7 @@ export abstract class BlockchainAdapter extends EventEmitter { abstract getSupportedNamespace(): string; abstract getBalance(params: GetBalanceParams): Promise; abstract getAccounts(): CaipAddress[] | undefined; + abstract switchNetwork(network: AppKitNetwork): Promise; } export abstract class EVMAdapter extends BlockchainAdapter { @@ -150,6 +172,7 @@ export abstract class EVMAdapter extends BlockchainAdapter { export interface GetBalanceParams { address?: CaipAddress; + network?: AppKitNetwork; } export interface GetBalanceResponse { diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 958357d6..1e940635 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -1,6 +1,7 @@ import { proxy, ref } from 'valtio'; import { derive } from 'valtio/utils'; import type { + AppKitNetwork, BlockchainAdapter, CaipAddress, CaipNetworkId @@ -25,12 +26,14 @@ interface Connection { export interface ConnectionsControllerState { activeNamespace: string; connections: Record; + networks: AppKitNetwork[]; } // -- State --------------------------------------------- // const baseState = proxy({ activeNamespace: 'eip155', - connections: {} + connections: {}, + networks: [] }); const derivedState = derive( @@ -70,6 +73,21 @@ const derivedState = derive( } return connection.balances[connection.activeAccount]; + }, + activeNetwork: (get): AppKitNetwork | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + const connection = snap.connections[snap.activeNamespace]; + + if (!connection) return undefined; + + return snap.networks.find( + network => + (network.chainNamespace ?? 'eip155') === snap.activeNamespace && + network.id?.toString() === connection.activeChain?.split(':')[1] + ); } }, { @@ -104,7 +122,7 @@ export const ConnectionsController = { activeAccount: accounts[0]!, chains }; - console.log('ConnectionController:storeConnection - state.connections', baseState.connections); + // console.log('ConnectionController:storeConnection - state.connections', baseState.connections); }, updateAccounts(namespace: string, accounts: CaipAddress[]) { @@ -123,35 +141,47 @@ export const ConnectionsController = { connection.balances[address] = balance; }, - updateChain(namespace: string, chain: CaipNetworkId) { + setActiveChain(namespace: string, chain: CaipNetworkId) { const connection = baseState.connections[namespace]; + if (!connection) { return; } + connection.activeChain = chain; }, + setNetworks(networks: AppKitNetwork[]) { + baseState.networks = networks; + }, + + getConnectedNetworks() { + return baseState.networks.filter( + network => baseState.connections[network.chainNamespace ?? 'eip155'] + ); + }, + async disconnect(namespace: string) { const connection = baseState.connections[namespace]; if (!connection) return; - console.log('ConnectionController:disconnect - connection', connection); + // console.log('ConnectionController:disconnect - connection', connection); // Get the current connector from the adapter const connector = connection.adapter.connector; if (!connector) return; - console.log('ConnectionController:disconnect - connector', connector); + // console.log('ConnectionController:disconnect - connector', connector); // Find all namespaces that use the same connector const namespacesUsingConnector = Object.keys(baseState.connections).filter( ns => baseState.connections[ns]?.adapter.connector === connector ); - console.log( - 'ConnectionController:disconnect - namespacesUsingConnector', - namespacesUsingConnector - ); + // console.log( + // 'ConnectionController:disconnect - namespacesUsingConnector', + // namespacesUsingConnector + // ); // Unsubscribe all event listeners from the adapter namespacesUsingConnector.forEach(ns => { @@ -169,6 +199,6 @@ export const ConnectionsController = { delete baseState.connections[ns]; }); - console.log('ConnectionController:disconnect - baseState.connections', baseState.connections); + // console.log('ConnectionController:disconnect - baseState.connections', baseState.connections); } }; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 357dcf1b..d7dc742b 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -453,7 +453,7 @@ export type Event = type: 'track'; event: 'SWITCH_NETWORK'; properties: { - network: string; + network: number | string; }; } | { diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 7ba4a877..bea3603b 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -1,7 +1,9 @@ import { formatEther, JsonRpcProvider } from 'ethers'; import { EVMAdapter, + PresetsUtil, WalletConnector, + type AppKitNetwork, type CaipAddress, type GetBalanceParams, type GetBalanceResponse, @@ -9,6 +11,7 @@ import { type TransactionData, type TransactionReceipt } from '@reown/appkit-common-react-native'; +import { EthersHelpersUtil } from '@reown/appkit-scaffold-utils-react-native'; export class EthersAdapter extends EVMAdapter { private static supportedNamespace: string = 'eip155'; @@ -28,29 +31,30 @@ export class EthersAdapter extends EVMAdapter { async getBalance(params: GetBalanceParams): Promise { if (!this.connector) throw new Error('No active connector'); + if (!params.network) throw new Error('No network provided'); const address = params.address || this.getAccounts()?.[0]; + const network = params.network; + + let balance = { amount: '0.00', symbol: network.nativeCurrency.symbol || 'ETH' }; if (!address) { - return Promise.resolve({ amount: '0.00', symbol: 'ETH' }); + return Promise.resolve(balance); } - const chainId = Number(address.split(':')[1]) ?? 1; const account = address.split(':')[2]; try { - //TODO use networks - const jsonRpcProvider = new JsonRpcProvider('https://eth.llamarpc.com', { - chainId: chainId, - name: 'Ethereum Mainnet' + const jsonRpcProvider = new JsonRpcProvider(network.rpcUrls.default.http[0], { + chainId: Number(network.id), + name: network.name }); - let balance = { amount: '0.00', symbol: 'ETH' }; if (jsonRpcProvider && account) { const _balance = await jsonRpcProvider.getBalance(account); const formattedBalance = formatEther(_balance); - balance = { amount: formattedBalance, symbol: 'ETH' }; + balance = { amount: formattedBalance, symbol: network.nativeCurrency.symbol || 'ETH' }; } this.emit('balanceChanged', { @@ -61,7 +65,46 @@ export class EthersAdapter extends EVMAdapter { return balance; } catch (error) { - return { amount: '0.00', symbol: 'ETH' }; + // console.error('EthersAdapter - getBalance', error); + + return balance; + } + } + + async switchNetwork(network: AppKitNetwork): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + try { + await provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util + }); + + this.getBalance({ address: this.getAccounts()?.[0], network }); + + return; + } catch (switchError: any) { + const message = switchError?.message as string; + if (/(?user rejected)/u.test(message?.toLowerCase())) { + throw new Error('Chain is not supported'); + } + + provider.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: EthersHelpersUtil.numberToHexString(Number(network.id)), + rpcUrls: network.rpcUrls, + chainName: network.name, + nativeCurrency: network.nativeCurrency, + blockExplorerUrls: network.blockExplorers, + iconUrls: [PresetsUtil.EIP155NetworkImageIds[network.id]] + } + ] + }); } } @@ -94,18 +137,18 @@ export class EthersAdapter extends EVMAdapter { } onChainChanged(chainId: string): void { - console.log('EthersAdapter - onChainChanged', chainId); + // console.log('EthersAdapter - onChainChanged', chainId); this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); } onAccountsChanged(accounts: string[]): void { - console.log('EthersAdapter - onAccountsChanged', accounts); + // console.log('EthersAdapter - onAccountsChanged', accounts); // Emit this change to AppKit with the corresponding namespace. this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); } onDisconnect(): void { - console.log('EthersAdapter - onDisconnect'); + // console.log('EthersAdapter - onDisconnect'); this.emit('disconnect', { namespace: this.getSupportedNamespace() }); //the connector might be shared between adapters. Validate this @@ -128,7 +171,7 @@ export class EthersAdapter extends EVMAdapter { const provider = this.connector?.getProvider(); if (!provider) return; - console.log('EthersAdapter - subscribing to events'); + // console.log('EthersAdapter - subscribing to events'); provider.on('chainChanged', this.onChainChanged.bind(this)); provider.on('accountsChanged', this.onAccountsChanged.bind(this)); provider.on('disconnect', this.onDisconnect.bind(this)); diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index 823a6b54..a34194ae 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -35,8 +35,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-core-react-native": "1.2.3", "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-core-react-native": "1.2.3", "@reown/appkit-scaffold-react-native": "1.2.3" }, "react-native": "src/index.ts", diff --git a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts index 9ec41582..ad117243 100644 --- a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts @@ -43,6 +43,7 @@ export const EthersHelpersUtil = { return address; }, async addEthereumChain(provider: Provider, chain: Chain) { + //TODO: Check if this is needed await provider.request({ method: 'wallet_addEthereumChain', params: [ diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index bf4cf983..140f8e28 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -1,6 +1,7 @@ import { EVMAdapter, WalletConnector, + type AppKitNetwork, type CaipAddress, type GetBalanceParams, type GetBalanceResponse, @@ -64,6 +65,11 @@ export class WagmiAdapter extends EVMAdapter { return this.request('eth_signTransaction', [tx]) as Promise; } + async switchNetwork(network: AppKitNetwork): Promise { + console.log('WagmiAdapter - switchNetwork', network); + throw new Error('Method not implemented.'); + } + async getBalance(params: GetBalanceParams): Promise { if (!this.connector) throw new Error('No active connector'); const address = params.address || this.getAccounts()?.[0]; diff --git a/yarn.lock b/yarn.lock index e1d6a5ac..2a17d9dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7289,6 +7289,7 @@ __metadata: resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-core-react-native": "npm:1.2.3" "@reown/appkit-scaffold-react-native": "npm:1.2.3" languageName: unknown linkType: soft From cf884562eaa2f5ea4661261375dc6010a5406234 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:14:25 -0300 Subject: [PATCH 08/91] chore: added basic solana adapter. getting account balances --- apps/native/App.tsx | 9 +- apps/native/src/utils/ChainUtils.ts | 2 +- package.json | 1 + packages/appkit/src/AppKit.ts | 14 +- .../src/modal/w3m-account-button/index.tsx | 10 +- .../src/modal/w3m-network-button/index.tsx | 8 +- .../partials/w3m-account-activity/index.tsx | 6 +- .../src/partials/w3m-account-tokens/index.tsx | 6 +- .../src/partials/w3m-selector-modal/index.tsx | 6 +- .../views/w3m-account-default-view/index.tsx | 3 +- .../src/views/w3m-account-view/index.tsx | 5 +- .../views/w3m-network-switch-view/index.tsx | 10 +- .../src/views/w3m-networks-view/index.tsx | 52 +-- .../views/w3m-onramp-checkout-view/index.tsx | 6 +- .../src/views/w3m-onramp-view/index.tsx | 8 +- .../w3m-swap-select-token-view/index.tsx | 6 +- .../w3m-unsupported-chain-view/index.tsx | 11 +- .../index.tsx | 2 +- .../views/w3m-wallet-receive-view/index.tsx | 22 +- .../components/preview-send-details.tsx | 2 +- .../index.tsx | 6 +- packages/common/src/utils/PresetsUtil.ts | 39 +- packages/common/src/utils/TypeUtil.ts | 15 +- .../core/src/controllers/ApiController.ts | 19 +- .../src/controllers/ConnectionsController.ts | 28 +- packages/core/src/utils/AssetUtil.ts | 11 +- packages/ethers/src/adapter.ts | 68 ++-- packages/ethers/src/client.ts | 4 +- packages/ethers5/src/client.ts | 4 +- .../src/utils/EthersHelpersUtil.ts | 4 +- packages/solana/.eslintignore | 2 + packages/solana/.eslintrc.json | 3 + packages/solana/.npmignore | 10 + packages/solana/bob.config.js | 14 + packages/solana/package.json | 53 +++ packages/solana/readme.md | 9 + packages/solana/src/adapter.ts | 190 +++++++++ packages/solana/src/index.tsx | 2 + packages/solana/tsconfig.json | 5 + packages/wagmi/src/adapter.ts | 11 + packages/wagmi/src/client.ts | 4 +- packages/wagmi/src/utils/helpers.ts | 2 +- yarn.lock | 363 +++++++++++++++++- 43 files changed, 893 insertions(+), 162 deletions(-) create mode 100644 packages/solana/.eslintignore create mode 100644 packages/solana/.eslintrc.json create mode 100644 packages/solana/.npmignore create mode 100644 packages/solana/bob.config.js create mode 100644 packages/solana/package.json create mode 100644 packages/solana/readme.md create mode 100644 packages/solana/src/adapter.ts create mode 100644 packages/solana/src/index.tsx create mode 100644 packages/solana/tsconfig.json diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 3803b031..99451f4b 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -29,6 +29,7 @@ import { Text } from '@reown/appkit-ui-react-native'; // import { OpenButton } from './src/components/OpenButton'; // import { DisconnectButton } from './src/components/DisconnectButton'; import { EthersAdapter } from '@reown/appkit-ethers-react-native'; +import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { mainnet, polygon, avalanche } from 'viem/chains'; import { solana } from './src/utils/ChainUtils'; @@ -80,6 +81,10 @@ const ethersAdapter = new EthersAdapter({ projectId }); +const solanaAdapter = new SolanaAdapter({ + projectId +}); + // createAppKit({ // projectId, // wagmiConfig, @@ -100,9 +105,9 @@ const ethersAdapter = new EthersAdapter({ const appKit = createAppKit({ projectId, - adapters: [ethersAdapter], + adapters: [ethersAdapter, solanaAdapter], metadata, - networks: [mainnet, polygon, avalanche] + networks: [mainnet, polygon, avalanche, solana] }); export default function Native() { diff --git a/apps/native/src/utils/ChainUtils.ts b/apps/native/src/utils/ChainUtils.ts index 11a3c250..a5afe037 100644 --- a/apps/native/src/utils/ChainUtils.ts +++ b/apps/native/src/utils/ChainUtils.ts @@ -6,7 +6,7 @@ export const solana = { network: 'solana-mainnet', nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, rpcUrls: { - default: { http: ['https://rpc.walletconnect.org/v1'] } + default: { http: ['https://api.mainnet-beta.solana.com'] } }, blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, testnet: false, diff --git a/package.json b/package.json index a2beebc9..b708af59 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "packages/coinbase-ethers", "packages/ethers5", "packages/ethers", + "packages/solana", "apps/*" ], "scripts": { diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 0c7d01a6..08ef62d3 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -307,8 +307,14 @@ export class AppKit { return connection?.adapter ?? null; } + getAdapterByNamespace(namespace: string = 'eip155'): BlockchainAdapter | null { + const namespaceConnection = ConnectionsController.state.connections[namespace]; + + return namespaceConnection?.adapter ?? null; + } + async switchNetwork(network: AppKitNetwork): Promise { - const adapter = this.getActiveAdapter(); + const adapter = this.getAdapterByNamespace(network.chainNamespace); if (!adapter) throw new Error('No active adapter'); await adapter.switchNetwork(network); @@ -325,6 +331,12 @@ export class AppKit { adapter.getSupportedNamespace(), `${adapter.getSupportedNamespace()}:${network.id}` as CaipNetworkId ); + + if (ConnectionsController.state.activeNamespace !== (network.chainNamespace ?? 'eip155')) { + ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); + } + + adapter.getBalance({ network }); } } diff --git a/packages/appkit/src/modal/w3m-account-button/index.tsx b/packages/appkit/src/modal/w3m-account-button/index.tsx index beb7c191..0370bf4b 100644 --- a/packages/appkit/src/modal/w3m-account-button/index.tsx +++ b/packages/appkit/src/modal/w3m-account-button/index.tsx @@ -2,7 +2,6 @@ import { useSnapshot } from 'valtio'; import { AccountController, CoreHelperUtil, - NetworkController, ModalController, AssetUtil, ThemeController, @@ -22,11 +21,14 @@ export interface AccountButtonProps { export function AccountButton({ balance, disabled, style, testID }: AccountButtonProps) { const { profileImage, profileName } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); - const { activeAddress: address, activeBalance } = useSnapshot(ConnectionsController.state); + const { + activeAddress: address, + activeBalance, + activeNetwork + } = useSnapshot(ConnectionsController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const showBalance = balance === 'show'; return ( diff --git a/packages/appkit/src/modal/w3m-network-button/index.tsx b/packages/appkit/src/modal/w3m-network-button/index.tsx index 353a1804..1c19e828 100644 --- a/packages/appkit/src/modal/w3m-network-button/index.tsx +++ b/packages/appkit/src/modal/w3m-network-button/index.tsx @@ -4,9 +4,9 @@ import { AccountController, ApiController, AssetUtil, + ConnectionsController, EventsController, ModalController, - NetworkController, ThemeController } from '@reown/appkit-core-react-native'; import { NetworkButton as NetworkButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; @@ -18,7 +18,7 @@ export interface NetworkButtonProps { export function NetworkButton({ disabled, style }: NetworkButtonProps) { const { isConnected } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const { loading } = useSnapshot(ModalController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); @@ -33,7 +33,7 @@ export function NetworkButton({ disabled, style }: NetworkButtonProps) { return ( - {caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} + {activeNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} ); diff --git a/packages/appkit/src/partials/w3m-account-activity/index.tsx b/packages/appkit/src/partials/w3m-account-activity/index.tsx index 3ec7ee05..dc70315d 100644 --- a/packages/appkit/src/partials/w3m-account-activity/index.tsx +++ b/packages/appkit/src/partials/w3m-account-activity/index.tsx @@ -14,8 +14,8 @@ import { type Transaction, type TransactionImage } from '@reown/appkit-common-re import { AccountController, AssetUtil, + ConnectionsController, EventsController, - NetworkController, OptionsController, TransactionsController } from '@reown/appkit-core-react-native'; @@ -32,8 +32,8 @@ export function AccountActivity({ style }: Props) { const [refreshing, setRefreshing] = useState(false); const [initialLoad, setInitialLoad] = useState(true); const { loading, transactions, next } = useSnapshot(TransactionsController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const handleLoadMore = () => { TransactionsController.fetchTransactions(AccountController.state.address); diff --git a/packages/appkit/src/partials/w3m-account-tokens/index.tsx b/packages/appkit/src/partials/w3m-account-tokens/index.tsx index 26db07f9..6f989f24 100644 --- a/packages/appkit/src/partials/w3m-account-tokens/index.tsx +++ b/packages/appkit/src/partials/w3m-account-tokens/index.tsx @@ -10,7 +10,7 @@ import { useSnapshot } from 'valtio'; import { AccountController, AssetUtil, - NetworkController, + ConnectionsController, RouterController } from '@reown/appkit-core-react-native'; import { @@ -30,8 +30,8 @@ export function AccountTokens({ style }: Props) { const Theme = useTheme(); const [refreshing, setRefreshing] = useState(false); const { tokenBalance } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const onRefresh = useCallback(async () => { setRefreshing(true); diff --git a/packages/appkit/src/partials/w3m-selector-modal/index.tsx b/packages/appkit/src/partials/w3m-selector-modal/index.tsx index 37c8c94e..bfb4d1dd 100644 --- a/packages/appkit/src/partials/w3m-selector-modal/index.tsx +++ b/packages/appkit/src/partials/w3m-selector-modal/index.tsx @@ -13,7 +13,7 @@ import { useTheme } from '@reown/appkit-ui-react-native'; import styles from './styles'; -import { AssetUtil, NetworkController } from '@reown/appkit-core-react-native'; +import { AssetUtil, ConnectionsController } from '@reown/appkit-core-react-native'; interface SelectorModalProps { title?: string; @@ -45,8 +45,8 @@ export function SelectorModal({ showNetwork }: SelectorModalProps) { const Theme = useTheme(); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const renderSeparator = () => { return ; diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index f5ec09a9..30561c96 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -49,12 +49,11 @@ export function AccountDefaultView() { } = useSnapshot(ConnectionsController.state); const account = address?.split(':')[2]; const [disconnecting, setDisconnecting] = useState(false); - const { caipNetwork } = useSnapshot(NetworkController.state); const { connectedConnector } = useSnapshot(ConnectorController.state); const { connectedSocialProvider } = useSnapshot(ConnectionController.state); const { features, isOnRampEnabled } = useSnapshot(OptionsController.state); const { history } = useSnapshot(RouterController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const showCopy = OptionsController.isClipboardAvailable(); const isAuth = connectedConnector === 'AUTH'; const showBalance = balance && !isAuth; diff --git a/packages/appkit/src/views/w3m-account-view/index.tsx b/packages/appkit/src/views/w3m-account-view/index.tsx index 14e19d85..5c72e1c9 100644 --- a/packages/appkit/src/views/w3m-account-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-view/index.tsx @@ -14,6 +14,7 @@ import { AccountController, ApiController, AssetUtil, + ConnectionsController, ModalController, NetworkController, RouterController, @@ -27,7 +28,7 @@ import styles from './styles'; export function AccountView() { const Theme = useTheme(); const { padding } = useCustomDimensions(); - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const { address, profileName, profileImage, preferredAccountType } = useSnapshot( AccountController.state ); @@ -74,7 +75,7 @@ export function AccountView() { ]} > (false); const [showRetry, setShowRetry] = useState(false); @@ -34,6 +35,7 @@ export function NetworkSwitchView() { const onSwitchNetwork = async () => { try { setError(false); + //TODO: change to appkit switchNetwork await NetworkController.switchActiveNetwork(network); EventsController.sendEvent({ type: 'track', @@ -55,10 +57,10 @@ export function NetworkSwitchView() { useEffect(() => { // Go back if network is already switched - if (caipNetwork?.id === network?.id) { + if (activeNetwork?.id === network?.id) { RouterUtil.navigateAfterNetworkSwitch(); } - }, [caipNetwork?.id, network?.id]); + }, [activeNetwork?.id, network?.id]); const retryTemplate = () => { if (!showRetry) return null; @@ -115,7 +117,7 @@ export function NetworkSwitchView() { diff --git a/packages/appkit/src/views/w3m-networks-view/index.tsx b/packages/appkit/src/views/w3m-networks-view/index.tsx index a2097072..c1cd35c3 100644 --- a/packages/appkit/src/views/w3m-networks-view/index.tsx +++ b/packages/appkit/src/views/w3m-networks-view/index.tsx @@ -13,7 +13,8 @@ import { NetworkController, RouterController, EventsController, - ConnectionsController + ConnectionsController, + AssetUtil } from '@reown/appkit-core-react-native'; import type { AppKitNetwork } from '@reown/appkit-common-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; @@ -43,31 +44,34 @@ export function NetworksView() { const onNetworkPress = async (network: AppKitNetwork) => { await appKit.switchNetwork(network); + RouterController.goBack(); }; - return networks.map(network => ( - - onNetworkPress(network)} - /> - - )); + return networks.map(network => { + return ( + + onNetworkPress(network)} + /> + + ); + }); }; return ( diff --git a/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx b/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx index a3085027..a3d83456 100644 --- a/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx +++ b/packages/appkit/src/views/w3m-onramp-checkout-view/index.tsx @@ -1,6 +1,6 @@ import { AssetUtil, - NetworkController, + ConnectionsController, OnRampController, RouterController, ThemeController @@ -27,8 +27,8 @@ export function OnRampCheckoutView() { OnRampController.state ); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { activeNetwork } = useSnapshot(ConnectionsController.state); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const value = NumberUtil.roundNumber(selectedQuote?.destinationAmount ?? 0, 6, 5); const symbol = selectedQuote?.destinationCurrencyCode; diff --git a/packages/appkit/src/views/w3m-onramp-view/index.tsx b/packages/appkit/src/views/w3m-onramp-view/index.tsx index 74a76291..f4a14bff 100644 --- a/packages/appkit/src/views/w3m-onramp-view/index.tsx +++ b/packages/appkit/src/views/w3m-onramp-view/index.tsx @@ -7,10 +7,10 @@ import { ThemeController, RouterController, type OnRampControllerState, - NetworkController, AssetUtil, SnackController, - ConstantsUtil + ConstantsUtil, + ConnectionsController } from '@reown/appkit-core-react-native'; import { Button, @@ -51,7 +51,7 @@ export function OnRampView() { loading, initialLoading } = useSnapshot(OnRampController.state) as OnRampControllerState; - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const [searchValue, setSearchValue] = useState(''); const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); const [isPaymentMethodModalVisible, setIsPaymentMethodModalVisible] = useState(false); @@ -59,7 +59,7 @@ export function OnRampView() { const suggestedValues = getCurrencySuggestedValues(paymentCurrency); const purchaseCurrencyCode = purchaseCurrency?.currencyCode?.split('_')[0] ?? purchaseCurrency?.currencyCode; - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const getQuotes = useCallback(() => { if (OnRampController.canGenerateQuote()) { diff --git a/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx b/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx index 0a716800..418f0b3e 100644 --- a/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx +++ b/packages/appkit/src/views/w3m-swap-select-token-view/index.tsx @@ -14,7 +14,7 @@ import { import { AssetUtil, - NetworkController, + ConnectionsController, RouterController, SwapController, type SwapTokenWithBalance @@ -28,9 +28,9 @@ import { createSections } from './utils'; export function SwapSelectTokenView() { const { padding } = useCustomDimensions(); const Theme = useTheme(); - const { caipNetwork } = useSnapshot(NetworkController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const { sourceToken, suggestedTokens } = useSnapshot(SwapController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const [tokenSearch, setTokenSearch] = useState(''); const isSourceToken = RouterController.state.data?.swapTarget === 'sourceToken'; diff --git a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx index 5c044f69..3b01b6aa 100644 --- a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx @@ -17,15 +17,18 @@ import { useAppKit } from '../../AppKitContext'; import styles from './styles'; export function UnsupportedChainView() { - const { caipNetwork, supportsAllNetworks, approvedCaipNetworkIds, requestedCaipNetworks } = - useSnapshot(NetworkController.state) as NetworkControllerState; + const { supportsAllNetworks, approvedCaipNetworkIds, requestedCaipNetworks } = useSnapshot( + NetworkController.state + ) as NetworkControllerState; + const { activeNetwork } = useSnapshot(ConnectionsController.state); const [disconnecting, setDisconnecting] = useState(false); const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); const imageHeaders = ApiController._getApiHeaders(); const { appKit } = useAppKit(); const onNetworkPress = async (network: CaipNetwork) => { + //TODO: change to appkit switchNetwork const result = await NetworkUtil.handleNetworkSwitch(network); if (result?.type === 'SWITCH_NETWORK') { EventsController.sendEvent({ @@ -61,7 +64,7 @@ export function UnsupportedChainView() { key={item.id} icon="networkPlaceholder" iconBackgroundColor="gray-glass-010" - imageSrc={AssetUtil.getNetworkImage(item)} + imageSrc={AssetUtil.getNetworkImage(item.id)} imageHeaders={imageHeaders} onPress={() => onNetworkPress(item)} testID="button-network" @@ -72,7 +75,7 @@ export function UnsupportedChainView() { {item.name ?? 'Unknown'} - {item.id === caipNetwork?.id && } + {item.id === activeNetwork?.id && } )} ListFooterComponent={ diff --git a/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx index 3695bc47..5d74c74b 100644 --- a/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx +++ b/packages/appkit/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -32,7 +32,7 @@ export function WalletCompatibleNetworks() { padding={['s', 's', 's', 's']} > network?.imageId) + .filter(network => network?.id) .slice(0, 5) - .map(AssetUtil.getNetworkImage) + .map(network => AssetUtil.getNetworkImage(network?.id)) .filter(Boolean) as string[]; const label = UiUtil.getTruncateString({ diff --git a/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx index e05b35c8..129adc20 100644 --- a/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx +++ b/packages/appkit/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx @@ -42,7 +42,7 @@ export function PreviewSendDetails({ truncate: 'middle' }); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const networkImage = AssetUtil.getNetworkImage(caipNetwork?.id); return ( (''); const [filteredTokens, setFilteredTokens] = useState(tokenBalance ?? []); diff --git a/packages/common/src/utils/PresetsUtil.ts b/packages/common/src/utils/PresetsUtil.ts index b949dded..3f9f6879 100644 --- a/packages/common/src/utils/PresetsUtil.ts +++ b/packages/common/src/utils/PresetsUtil.ts @@ -7,11 +7,11 @@ export const PresetsUtil = { 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa' } as Record, - EIP155NetworkImageIds: { + NetworkImageIds: { // Ethereum 1: 'ba0ba0cd-17c6-4806-ad93-f9d174f17900', // Arbitrum - 42161: '600a9a04-c1b9-42ca-6785-9b4b6ff85200', + 42161: '3bff954d-5cb0-47a0-9a23-d20192e74600', // Avalanche 43114: '30c46e53-e989-45fb-4549-be3bd4eb3b00', // Binance Smart Chain @@ -22,6 +22,20 @@ export const PresetsUtil = { 10: 'ab9c186a-c52f-464b-2906-ca59d760a400', // Polygon 137: '41d04d42-da3b-4453-8506-668cc0727900', + // Mantle + 5000: 'e86fae9b-b770-4eea-e520-150e12c81100', + // Hedera Mainnet + 295: '6a97d510-cac8-4e58-c7ce-e8681b044c00', + // Sepolia + 11_155_111: 'e909ea0a-f92a-4512-c8fc-748044ea6800', + // Base Sepolia + 84532: 'a18a7ecd-e307-4360-4746-283182228e00', + // Unichain Sepolia + 1301: '4eeea7ef-0014-4649-5d1d-07271a80f600', + // Unichain Mainnet + 130: '2257980a-3463-48c6-cbac-a42d2a956e00', + // Monad Testnet + 10_143: '0a728e83-bacb-46db-7844-948f05434900', // Gnosis 100: '02b53f6a-e3d4-479e-1cb4-21178987d100', // EVMos @@ -45,7 +59,26 @@ export const PresetsUtil = { // Base 8453: '7289c336-3981-4081-c5f4-efc26ac64a00', // Aurora - 1313161554: '3ff73439-a619-4894-9262-4470c773a100' + 1313161554: '3ff73439-a619-4894-9262-4470c773a100', + // Ronin Mainnet + 2020: 'b8101fc0-9c19-4b6f-ec65-f6dfff106e00', + // Saigon Testnet (a.k.a. Ronin) + 2021: 'b8101fc0-9c19-4b6f-ec65-f6dfff106e00', + // Berachain Mainnet + 80094: 'e329c2c9-59b0-4a02-83e4-212ff3779900', + // Abstract Mainnet + 2741: 'fc2427d1-5af9-4a9c-8da5-6f94627cd900', + + // Solana networks + /// Mainnet + '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'a1b58899-f671-4276-6a5e-56ca5bd59700', + /// Testnet + '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'a1b58899-f671-4276-6a5e-56ca5bd59700', + + // Bitcoin + '000000000019d6689c085ae165831e93': '0b4838db-0161-4ffe-022d-532bf03dba00', + // Bitcoin Testnet + '000000000933ea01ad0ee984209779ba': '39354064-d79b-420b-065d-f980c4b78200' } as Record, ConnectorNamesMap: { diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 96bf317d..f8a64323 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -166,7 +166,12 @@ export abstract class BlockchainAdapter extends EventEmitter { } export abstract class EVMAdapter extends BlockchainAdapter { - abstract signTransaction(tx: TransactionData): Promise; + abstract signMessage(params: SignMessageParams): Promise; + abstract sendTransaction(tx: TransactionData): Promise; +} + +export abstract class SolanaBaseAdapter extends BlockchainAdapter { + abstract signMessage(params: SignMessageParams): Promise; abstract sendTransaction(tx: TransactionData): Promise; } @@ -235,6 +240,14 @@ export type New_ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; //********** Others **********// +export interface SignMessageParams { + message: string; + address: string; +} +export interface SignMessageResult { + signature: string; +} + export interface TransactionData { to: string; value?: string; diff --git a/packages/core/src/controllers/ApiController.ts b/packages/core/src/controllers/ApiController.ts index aaafc162..132ca702 100644 --- a/packages/core/src/controllers/ApiController.ts +++ b/packages/core/src/controllers/ApiController.ts @@ -12,12 +12,13 @@ import type { WcWallet } from '../utils/TypeUtil'; import { AssetController } from './AssetController'; -import { NetworkController } from './NetworkController'; import { OptionsController } from './OptionsController'; import { ConnectorController } from './ConnectorController'; import { ConnectionController } from './ConnectionController'; import { ApiUtil } from '../utils/ApiUtil'; import { SnackController } from './SnackController'; +import { ConnectionsController } from './ConnectionsController'; +import { PresetsUtil } from '@reown/appkit-common-react-native'; // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getApiUrl(); @@ -92,11 +93,16 @@ export const ApiController = { } }, - async _fetchNetworkImage(imageId: string) { + async _fetchNetworkImage(networkId: string) { + const imageId = PresetsUtil.NetworkImageIds[networkId]; + if (!imageId) { + return; + } + const headers = ApiController._getApiHeaders(); const url = await api.fetchImage(`/public/getAssetImage/${imageId}`, headers); if (url) { - AssetController.setNetworkImage(imageId, url); + AssetController.setNetworkImage(networkId, url); } }, @@ -109,11 +115,10 @@ export const ApiController = { }, async fetchNetworkImages() { - const { requestedCaipNetworks } = NetworkController.state; - const ids = requestedCaipNetworks?.map(({ imageId }) => imageId).filter(Boolean); - if (ids) { + const networks = ConnectionsController.state.networks; + if (networks) { await CoreHelperUtil.allSettled( - (ids as string[]).map(id => ApiController._fetchNetworkImage(id)) + networks.map(network => ApiController._fetchNetworkImage(network.id as string)) ); } }, diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 1e940635..04a19cb0 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -16,7 +16,6 @@ interface Balance { interface Connection { accounts: CaipAddress[]; - activeAccount: CaipAddress; balances: Record; adapter: BlockchainAdapter; chains: CaipNetworkId[]; @@ -45,34 +44,40 @@ const derivedState = derive( const connection = snap.connections[snap.activeNamespace]; - if ( - !connection || - !connection.accounts || - !connection.activeAccount || - connection.accounts.length === 0 - ) { + if (!connection || !connection.accounts || connection.accounts.length === 0) { return undefined; } - return connection.activeAccount; + const activeAccount = connection.accounts.find(account => + account.startsWith(connection.activeChain) + ); + + return activeAccount; }, activeBalance: (get): Balance | undefined => { const snap = get(baseState); if (!snap.activeNamespace) return undefined; - const connection = snap.connections[snap.activeNamespace]; + if (!connection || !connection.accounts || connection.accounts.length === 0) { + return undefined; + } + + const activeAccount = connection.accounts.find(account => + account.startsWith(connection.activeChain) + ); + if ( !connection || !connection.balances || - !connection.activeAccount || + !activeAccount || Object.keys(connection.balances).length === 0 ) { return undefined; } - return connection.balances[connection.activeAccount]; + return connection.balances[activeAccount]; }, activeNetwork: (get): AppKitNetwork | undefined => { const snap = get(baseState); @@ -119,7 +124,6 @@ export const ConnectionsController = { activeChain: chains[0]!, adapter: ref(adapter), accounts, - activeAccount: accounts[0]!, chains }; // console.log('ConnectionController:storeConnection - state.connections', baseState.connections); diff --git a/packages/core/src/utils/AssetUtil.ts b/packages/core/src/utils/AssetUtil.ts index 3ea47027..89f783e3 100644 --- a/packages/core/src/utils/AssetUtil.ts +++ b/packages/core/src/utils/AssetUtil.ts @@ -1,4 +1,3 @@ -import type { CaipNetwork } from '@reown/appkit-common-react-native'; import { AssetController } from '../controllers/AssetController'; import type { Connector, WcWallet } from './TypeUtil'; @@ -15,13 +14,11 @@ export const AssetUtil = { return undefined; }, - getNetworkImage(network?: CaipNetwork) { - if (network?.imageUrl) { - return network?.imageUrl; - } + getNetworkImage(networkId?: string | number) { + //TODO: check if imageUrl case is needed - if (network?.imageId) { - return AssetController.state.networkImages[network.imageId]; + if (networkId) { + return AssetController.state.networkImages[networkId]; } return undefined; diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index bea3603b..06f17627 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -1,14 +1,13 @@ -import { formatEther, JsonRpcProvider } from 'ethers'; +import { formatEther, hexlify, isHexString, JsonRpcProvider, toUtf8Bytes } from 'ethers'; import { EVMAdapter, - PresetsUtil, WalletConnector, type AppKitNetwork, type CaipAddress, type GetBalanceParams, type GetBalanceResponse, - type SignedTransaction, - type TransactionData, + type SignMessageParams, + type SignMessageResult, type TransactionReceipt } from '@reown/appkit-common-react-native'; import { EthersHelpersUtil } from '@reown/appkit-scaffold-utils-react-native'; @@ -23,26 +22,40 @@ export class EthersAdapter extends EVMAdapter { }); } - async signTransaction(tx: TransactionData): Promise { + async signMessage(params: SignMessageParams): Promise { if (!this.connector) throw new Error('No active connector'); - return this.request('eth_signTransaction', [tx]) as Promise; + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + const { message, address } = params; + + const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); + + const signature = (await provider.request({ + method: 'personal_sign', + params: [hexMessage, address] + })) as `0x${string}`; + + return { signature }; } async getBalance(params: GetBalanceParams): Promise { + const { network, address } = params; + if (!this.connector) throw new Error('No active connector'); - if (!params.network) throw new Error('No network provided'); + if (!network) throw new Error('No network provided'); - const address = params.address || this.getAccounts()?.[0]; - const network = params.network; + const balanceAddress = + address || this.getAccounts()?.find(account => account.includes(network.id.toString())); let balance = { amount: '0.00', symbol: network.nativeCurrency.symbol || 'ETH' }; - if (!address) { + if (!balanceAddress) { return Promise.resolve(balance); } - const account = address.split(':')[2]; + const account = balanceAddress.split(':')[2]; try { const jsonRpcProvider = new JsonRpcProvider(network.rpcUrls.default.http[0], { @@ -59,14 +72,12 @@ export class EthersAdapter extends EVMAdapter { this.emit('balanceChanged', { namespace: this.getSupportedNamespace(), - address, + address: balanceAddress, balance }); return balance; } catch (error) { - // console.error('EthersAdapter - getBalance', error); - return balance; } } @@ -78,33 +89,20 @@ export class EthersAdapter extends EVMAdapter { if (!provider) throw new Error('No active provider'); try { - await provider.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util - }); - - this.getBalance({ address: this.getAccounts()?.[0], network }); - - return; + return await provider.request( + { + method: 'wallet_switchEthereumChain', + params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util + }, + `${network.chainNamespace ?? 'eip155'}:${network.id}` + ); } catch (switchError: any) { const message = switchError?.message as string; if (/(?user rejected)/u.test(message?.toLowerCase())) { throw new Error('Chain is not supported'); } - provider.request({ - method: 'wallet_addEthereumChain', - params: [ - { - chainId: EthersHelpersUtil.numberToHexString(Number(network.id)), - rpcUrls: network.rpcUrls, - chainName: network.name, - nativeCurrency: network.nativeCurrency, - blockExplorerUrls: network.blockExplorers, - iconUrls: [PresetsUtil.EIP155NetworkImageIds[network.id]] - } - ] - }); + throw switchError; } } diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 8c40434e..7deaafc6 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -583,7 +583,7 @@ export class AppKit extends AppKitScaffold { ({ id: `${ConstantsUtil.EIP155}:${chain.chainId}`, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageId: PresetsUtil.NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId] }) as CaipNetwork ); @@ -772,7 +772,7 @@ export class AppKit extends AppKitScaffold { this.setCaipNetwork({ id: caipChainId, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageId: PresetsUtil.NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId] }); if (isConnected && address) { diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index 8f072962..c369d75d 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -563,7 +563,7 @@ export class AppKit extends AppKitScaffold { ({ id: `${ConstantsUtil.EIP155}:${chain.chainId}`, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageId: PresetsUtil.NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId] }) as CaipNetwork ); @@ -752,7 +752,7 @@ export class AppKit extends AppKitScaffold { this.setCaipNetwork({ id: caipChainId, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId], + imageId: PresetsUtil.NetworkImageIds[chain.chainId], imageUrl: chainImages?.[chain.chainId] }); if (isConnected && address) { diff --git a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts index ad117243..7ec45f58 100644 --- a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts @@ -12,7 +12,7 @@ export const EthersHelpersUtil = { return { id: `${ConstantsUtil.EIP155}:${chain.chainId}`, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId] + imageId: PresetsUtil.NetworkImageIds[chain.chainId] } as CaipNetwork; }, hexStringToNumber(value: string) { @@ -57,7 +57,7 @@ export const EthersHelpersUtil = { symbol: chain.currency }, blockExplorerUrls: [chain.explorerUrl], - iconUrls: [PresetsUtil.EIP155NetworkImageIds[chain.chainId]] + iconUrls: [PresetsUtil.NetworkImageIds[chain.chainId]] } ] }); diff --git a/packages/solana/.eslintignore b/packages/solana/.eslintignore new file mode 100644 index 00000000..c18ed016 --- /dev/null +++ b/packages/solana/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +lib/ \ No newline at end of file diff --git a/packages/solana/.eslintrc.json b/packages/solana/.eslintrc.json new file mode 100644 index 00000000..b9233ee4 --- /dev/null +++ b/packages/solana/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} diff --git a/packages/solana/.npmignore b/packages/solana/.npmignore new file mode 100644 index 00000000..e203f76a --- /dev/null +++ b/packages/solana/.npmignore @@ -0,0 +1,10 @@ +*.log +*.env +npm-debug.log* +node_modules +package-lock.json +src +tests +index.ts +.eslintrc.json +.turbo diff --git a/packages/solana/bob.config.js b/packages/solana/bob.config.js new file mode 100644 index 00000000..b7ca0ad6 --- /dev/null +++ b/packages/solana/bob.config.js @@ -0,0 +1,14 @@ +module.exports = { + source: 'src', + output: 'lib', + targets: [ + 'commonjs', + 'module', + [ + 'typescript', + { + tsc: '../../node_modules/.bin/tsc' + } + ] + ] +}; diff --git a/packages/solana/package.json b/packages/solana/package.json new file mode 100644 index 00000000..f18fbd1e --- /dev/null +++ b/packages/solana/package.json @@ -0,0 +1,53 @@ +{ + "name": "@reown/appkit-solana-react-native", + "version": "1.2.3", + "main": "lib/commonjs/index.js", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index.js", + "source": "src/index.tsx", + "scripts": { + "build": "bob build", + "clean": "rm -rf lib", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" + }, + "files": [ + "src", + "lib", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": [ + "web3", + "crypto", + "solana", + "appkit", + "walletconnect", + "react-native" + ], + "repository": "https://github.com/reown-com/appkit-react-native", + "author": "Reown (https://reown.com)", + "homepage": "https://reown.com/appkit", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reown-com/appkit-react-native/issues" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "dependencies": { + "@reown/appkit-common-react-native": "1.2.3", + "@solana/web3.js": "1.98.2", + "bs58": "6.0.0" + }, + "peerDependencies": { + "@solana/web3.js": ">=1.90.0", + "bs58": ">=6.0.0" + }, + "devDependencies": { + "@solana/web3.js": "1.98.2", + "bs58": "6.0.0" + }, + "react-native": "src/index.tsx" +} diff --git a/packages/solana/readme.md b/packages/solana/readme.md new file mode 100644 index 00000000..60524ccd --- /dev/null +++ b/packages/solana/readme.md @@ -0,0 +1,9 @@ +#### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) + +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) + +#### 🔗 [Website](https://reown.com/appkit) + +# AppKit + +Your on-ramp to web3 multichain. AppKit is a versatile library that makes it super easy to connect users with your Dapp and start interacting with the blockchain. diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts new file mode 100644 index 00000000..3b643b22 --- /dev/null +++ b/packages/solana/src/adapter.ts @@ -0,0 +1,190 @@ +import { + SolanaBaseAdapter, + WalletConnector, + type AppKitNetwork, + type CaipAddress, + type GetBalanceParams, + type GetBalanceResponse, + type SignMessageParams, + type SignMessageResult, + type TransactionReceipt +} from '@reown/appkit-common-react-native'; +import { Connection, PublicKey } from '@solana/web3.js'; +import base58 from 'bs58'; + +export class SolanaAdapter extends SolanaBaseAdapter { + private static supportedNamespace: string = 'solana'; + + constructor(configParams: { projectId: string }) { + super({ + projectId: configParams.projectId, + supportedNamespace: SolanaAdapter.supportedNamespace + }); + } + + async signMessage(params: SignMessageParams): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + const { message } = params; + + // return this.request('eth_signTransaction', [tx]) as Promise; + // throw new Error('Method not implemented.'); + + const signParams = { + message: base58.encode(new TextEncoder().encode(message)), + pubkey: params.address || this.getAccounts()?.[0] //TODO: Check if this is correct + }; + + const signature = (await provider.request({ + method: 'solana_signTransaction', + params: [signParams] + })) as any; //TODO: check type + + return { signature }; + } + + async getBalance(params: GetBalanceParams): Promise { + const { network, address } = params; + + if (!this.connector) throw new Error('No active connector'); + if (!network) throw new Error('No network provided'); + + const balanceAddress = + address || this.getAccounts()?.find(account => account.includes(network.id.toString())); + + if (!balanceAddress) { + return Promise.resolve({ amount: '0.00', symbol: 'SOL' }); + } + + try { + const connection = new Connection(network?.rpcUrls?.default?.http?.[0] as string); //TODO: check connection settings + const balanceAmount = await connection.getBalance( + new PublicKey(balanceAddress.split(':')[2] as string) + ); + const formattedBalance = (balanceAmount / 1000000000).toString(); //TODO: add util with LAMPORTS_PER_SOL + + const balance = { + amount: formattedBalance, + symbol: network?.nativeCurrency.symbol || 'SOL' + }; + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address, + balance + }); + + return balance; + } catch (error) { + return { amount: '0.00', symbol: 'SOL' }; + } + } + + async switchNetwork(network: AppKitNetwork): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + try { + // await provider.request({ + // method: 'wallet_switchEthereumChain', + // params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util + // }); + + this.getBalance({ address: this.getAccounts()?.[0], network }); + + return; + } catch (switchError: any) { + // const message = switchError?.message as string; + // if (/(?user rejected)/u.test(message?.toLowerCase())) { + // throw new Error('Chain is not supported'); + // } + // provider.request({ + // method: 'wallet_addEthereumChain', + // params: [ + // { + // chainId: EthersHelpersUtil.numberToHexString(Number(network.id)), + // rpcUrls: network.rpcUrls, + // chainName: network.name, + // nativeCurrency: network.nativeCurrency, + // blockExplorerUrls: network.blockExplorers, + // iconUrls: [PresetsUtil.NetworkImageIds[network.id]] + // } + // ] + // }); + } + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) throw new Error('No active connector'); + const namespaces = this.connector.getNamespaces(); + + return namespaces[this.getSupportedNamespace()]?.accounts; + } + + sendTransaction(/*tx: TransactionData*/): Promise { + throw new Error('Method not implemented.'); + } + + disconnect(): Promise { + if (!this.connector) throw new Error('SolanaAdapter:disconnect - No active connector'); + + return this.connector.disconnect(); + } + + async request(method: string, params?: any[]) { + if (!this.connector) throw new Error('No active connector'); + const provider = this.connector.getProvider(); + + return provider.request({ method, params }); + } + + getSupportedNamespace(): string { + return SolanaAdapter.supportedNamespace; + } + + onChainChanged(chainId: string): void { + // console.log('SolanaAdapter - onChainChanged', chainId); + this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + } + + onAccountsChanged(accounts: string[]): void { + // console.log('SolanaAdapter - onAccountsChanged', accounts); + // Emit this change to AppKit with the corresponding namespace. + this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + } + + onDisconnect(): void { + // console.log('SolanaAdapter - onDisconnect'); + this.emit('disconnect', { namespace: this.getSupportedNamespace() }); + + //the connector might be shared between adapters. Validate this + const provider = this.connector?.getProvider(); + if (provider) { + provider.off('chainChanged', this.onChainChanged.bind(this)); + provider.off('accountsChanged', this.onAccountsChanged.bind(this)); + provider.off('disconnect', this.onDisconnect.bind(this)); + } + + this.connector = undefined; + } + + override setConnector(connector: WalletConnector): void { + super.setConnector(connector); + this.subscribeToEvents(); + } + + subscribeToEvents(): void { + const provider = this.connector?.getProvider(); + if (!provider) return; + + // console.log('SolanaAdapter - subscribing to events'); + provider.on('chainChanged', this.onChainChanged.bind(this)); + provider.on('accountsChanged', this.onAccountsChanged.bind(this)); + provider.on('disconnect', this.onDisconnect.bind(this)); + } +} diff --git a/packages/solana/src/index.tsx b/packages/solana/src/index.tsx new file mode 100644 index 00000000..616efc42 --- /dev/null +++ b/packages/solana/src/index.tsx @@ -0,0 +1,2 @@ +import { SolanaAdapter } from './adapter'; +export { SolanaAdapter }; diff --git a/packages/solana/tsconfig.json b/packages/solana/tsconfig.json new file mode 100644 index 00000000..512da539 --- /dev/null +++ b/packages/solana/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "src/index.tsx"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index 140f8e28..bbfff866 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -6,6 +6,8 @@ import { type GetBalanceParams, type GetBalanceResponse, type SignedTransaction, + type SignMessageParams, + type SignMessageResult, type TransactionData, type TransactionReceipt } from '@reown/appkit-common-react-native'; @@ -59,6 +61,15 @@ export class WagmiAdapter extends EVMAdapter { }); } + async signMessage(_params: SignMessageParams): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + throw new Error('Method not implemented.'); + } + async signTransaction(tx: TransactionData): Promise { if (!this.connector) throw new Error('No active connector'); diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 1fef84c5..84cb06f2 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -418,7 +418,7 @@ export class AppKit extends AppKitScaffold { ({ id: `${ConstantsUtil.EIP155}:${chain.id}`, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.id], + imageId: PresetsUtil.NetworkImageIds[chain.id], imageUrl: this.options?.chainImages?.[chain.id] }) as CaipNetwork ); @@ -467,7 +467,7 @@ export class AppKit extends AppKitScaffold { this.setCaipNetwork({ id: caipChainId, name, - imageId: PresetsUtil.EIP155NetworkImageIds[id], + imageId: PresetsUtil.NetworkImageIds[id], imageUrl: this.options?.chainImages?.[id] }); if (isConnected && address && chainId) { diff --git a/packages/wagmi/src/utils/helpers.ts b/packages/wagmi/src/utils/helpers.ts index f773d5fc..ffa4892f 100644 --- a/packages/wagmi/src/utils/helpers.ts +++ b/packages/wagmi/src/utils/helpers.ts @@ -18,7 +18,7 @@ export function getCaipDefaultChain(chain?: AppKitClientOptions['defaultChain']) return { id: `${ConstantsUtil.EIP155}:${chain.id}`, name: chain.name, - imageId: PresetsUtil.EIP155NetworkImageIds[chain.id] + imageId: PresetsUtil.NetworkImageIds[chain.id] } as CaipNetwork; } diff --git a/yarn.lock b/yarn.lock index 2a17d9dd..a21d8f8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6203,6 +6203,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.4.2": + version: 1.9.0 + resolution: "@noble/curves@npm:1.9.0" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: a76d57444b4d136f43363eb19229d990df15a00fb0e2efbf08a7a4cbaee655f73e46eb29b6ad07b8749be5f7b890c0a7a06a19f4324a4b149b06b3da1def8593 + languageName: node + linkType: hard + "@noble/curves@npm:^1.6.0, @noble/curves@npm:~1.6.0": version: 1.6.0 resolution: "@noble/curves@npm:1.6.0" @@ -6254,6 +6263,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.8.0": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: 06a0b52c81a6fa7f04d67762e08b2c476a00285858150caeaaff4037356dd5e119f45b2a530f638b77a5eeca013168ec1b655db41bae3236cb2e9d511484fc77 + languageName: node + linkType: hard + "@noble/hashes@npm:~1.3.1": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" @@ -7307,6 +7323,19 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-solana-react-native@workspace:packages/solana": + version: 0.0.0-use.local + resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" + "@solana/web3.js": "npm:1.98.2" + bs58: "npm:6.0.0" + peerDependencies: + "@solana/web3.js": ">=1.90.0" + bs58: ">=6.0.0" + languageName: unknown + linkType: soft + "@reown/appkit-ui-react-native@npm:1.2.3, @reown/appkit-ui-react-native@workspace:packages/ui": version: 0.0.0-use.local resolution: "@reown/appkit-ui-react-native@workspace:packages/ui" @@ -7572,6 +7601,75 @@ __metadata: languageName: node linkType: hard +"@solana/buffer-layout@npm:^4.0.1": + version: 4.0.1 + resolution: "@solana/buffer-layout@npm:4.0.1" + dependencies: + buffer: "npm:~6.0.3" + checksum: 6535f3908cf6dfc405b665795f0c2eaa0482a8c6b1811403945cf7b450e7eb7b40acce3e8af046f2fcc3eea1a15e61d48c418315d813bee4b720d56b00053305 + languageName: node + linkType: hard + +"@solana/codecs-core@npm:2.1.0": + version: 2.1.0 + resolution: "@solana/codecs-core@npm:2.1.0" + dependencies: + "@solana/errors": "npm:2.1.0" + peerDependencies: + typescript: ">=5" + checksum: 9435aa070433733b7cfee6b98abbf59503a7e828cbb8d70ecae5f7223d21a127fc750ba4fee11f08c2211cc88f7af4a8d460bf52e20aac1308c00be87b057c56 + languageName: node + linkType: hard + +"@solana/codecs-numbers@npm:^2.1.0": + version: 2.1.0 + resolution: "@solana/codecs-numbers@npm:2.1.0" + dependencies: + "@solana/codecs-core": "npm:2.1.0" + "@solana/errors": "npm:2.1.0" + peerDependencies: + typescript: ">=5" + checksum: ae5c256e4d49f7d2eb37ad5aa52e038eb3f13d611cd5799f3d7e549b6b983571511a0d924fb287f49b687a101bb849114de4e7b29eb103a2369126e4fd3dbebb + languageName: node + linkType: hard + +"@solana/errors@npm:2.1.0": + version: 2.1.0 + resolution: "@solana/errors@npm:2.1.0" + dependencies: + chalk: "npm:^5.3.0" + commander: "npm:^13.1.0" + peerDependencies: + typescript: ">=5" + bin: + errors: bin/cli.mjs + checksum: 707f657721d5421af3d016fb876a20c6f9f9c70e9012c7ddb23c02d9f29349edee273e6d2ec219fa38dc569df4a5aa90111bfd49eef435b232e381e243c013de + languageName: node + linkType: hard + +"@solana/web3.js@npm:1.98.2": + version: 1.98.2 + resolution: "@solana/web3.js@npm:1.98.2" + dependencies: + "@babel/runtime": "npm:^7.25.0" + "@noble/curves": "npm:^1.4.2" + "@noble/hashes": "npm:^1.4.0" + "@solana/buffer-layout": "npm:^4.0.1" + "@solana/codecs-numbers": "npm:^2.1.0" + agentkeepalive: "npm:^4.5.0" + bn.js: "npm:^5.2.1" + borsh: "npm:^0.7.0" + bs58: "npm:^4.0.1" + buffer: "npm:6.0.3" + fast-stable-stringify: "npm:^1.0.0" + jayson: "npm:^4.1.1" + node-fetch: "npm:^2.7.0" + rpc-websockets: "npm:^9.0.2" + superstruct: "npm:^2.0.2" + checksum: 04230d8f9d3f1aa7665d8acf9f54342c022bd84070790909f5b6ff17d27b03e95373d3491f4a25f4ee2e10a9e82765ee541db33fd9f63be2efa49a4490bc1a0e + languageName: node + linkType: hard + "@stablelib/aead@npm:^1.0.1": version: 1.0.1 resolution: "@stablelib/aead@npm:1.0.1" @@ -8280,6 +8378,15 @@ __metadata: languageName: node linkType: hard +"@swc/helpers@npm:^0.5.11": + version: 0.5.17 + resolution: "@swc/helpers@npm:0.5.17" + dependencies: + tslib: "npm:^2.8.0" + checksum: fe1f33ebb968558c5a0c595e54f2e479e4609bff844f9ca9a2d1ffd8dd8504c26f862a11b031f48f75c95b0381c2966c3dd156e25942f90089badd24341e7dbb + languageName: node + linkType: hard + "@tanstack/query-async-storage-persister@npm:^5.40.0": version: 5.40.0 resolution: "@tanstack/query-async-storage-persister@npm:5.40.0" @@ -8538,6 +8645,15 @@ __metadata: languageName: node linkType: hard +"@types/connect@npm:^3.4.33": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.9 resolution: "@types/debug@npm:4.1.9" @@ -8754,7 +8870,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^12.7.1": +"@types/node@npm:^12.12.54, @types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" checksum: 3b190bb0410047d489c49bbaab592d2e6630de6a50f00ba3d7d513d59401d279972a8f5a598b5bb8ddc1702f8a2f4ec57a65d93852f9c329639738e7053637d1 @@ -8913,6 +9029,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^8.3.4": + version: 8.3.4 + resolution: "@types/uuid@npm:8.3.4" + checksum: b9ac98f82fcf35962317ef7dc44d9ac9e0f6fdb68121d384c88fe12ea318487d5585d3480fa003cf28be86a3bbe213ca688ba786601dce4a97724765eb5b1cf2 + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.1": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -8920,6 +9043,24 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^7.4.4": + version: 7.4.7 + resolution: "@types/ws@npm:7.4.7" + dependencies: + "@types/node": "npm:*" + checksum: f1f53febd8623a85cef2652949acd19d83967e350ea15a851593e3033501750a1e04f418552e487db90a3d48611a1cff3ffcf139b94190c10f2fd1e1dc95ff10 + languageName: node + linkType: hard + +"@types/ws@npm:^8.2.2": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" + dependencies: + "@types/node": "npm:*" + checksum: 61aff1129143fcc4312f083bc9e9e168aa3026b7dd6e70796276dcfb2c8211c4292603f9c4864fae702f2ed86e4abd4d38aa421831c2fd7f856c931a481afbab + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -10187,6 +10328,15 @@ __metadata: languageName: node linkType: hard +"agentkeepalive@npm:^4.5.0": + version: 4.6.0 + resolution: "agentkeepalive@npm:4.6.0" + dependencies: + humanize-ms: "npm:^1.2.1" + checksum: 235c182432f75046835b05f239708107138a40103deee23b6a08caee5136873709155753b394ec212e49e60e94a378189562cb01347765515cff61b692c69187 + languageName: node + linkType: hard + "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -10990,6 +11140,15 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^3.0.2": + version: 3.0.11 + resolution: "base-x@npm:3.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 4c5b8cd9cef285973b0460934be4fc890eedfd22a8aca527fac3527f041c5d1c912f7b9a6816f19e43e69dc7c29a5deabfa326bd3d6a57ee46af0ad46e3991d5 + languageName: node + linkType: hard + "base-x@npm:^5.0.0": version: 5.0.1 resolution: "base-x@npm:5.0.1" @@ -11075,6 +11234,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:^5.2.0": + version: 5.2.2 + resolution: "bn.js@npm:5.2.2" + checksum: cb97827d476aab1a0194df33cd84624952480d92da46e6b4a19c32964aa01553a4a613502396712704da2ec8f831cf98d02e74ca03398404bd78a037ba93f2ab + languageName: node + linkType: hard + "body-parser@npm:1.20.3": version: 1.20.3 resolution: "body-parser@npm:1.20.3" @@ -11102,6 +11268,17 @@ __metadata: languageName: node linkType: hard +"borsh@npm:^0.7.0": + version: 0.7.0 + resolution: "borsh@npm:0.7.0" + dependencies: + bn.js: "npm:^5.2.0" + bs58: "npm:^4.0.0" + text-encoding-utf-8: "npm:^1.0.2" + checksum: 513b3e51823d2bf5be77cec27742419d2b0427504825dd7ceb00dedb820f246a4762f04b83d5e3aa39c8e075b3cbaeb7ca3c90bd1cbeecccb4a510575be8c581 + languageName: node + linkType: hard + "bowser@npm:^2.9.0": version: 2.11.0 resolution: "bowser@npm:2.11.0" @@ -11298,6 +11475,15 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^4.0.0, bs58@npm:^4.0.1": + version: 4.0.1 + resolution: "bs58@npm:4.0.1" + dependencies: + base-x: "npm:^3.0.2" + checksum: 613a1b1441e754279a0e3f44d1faeb8c8e838feef81e550efe174ff021dd2e08a4c9ae5805b52dfdde79f97b5c0918c78dd24a0eb726c4a94365f0984a0ffc65 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -11338,7 +11524,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:6.0.3, buffer@npm:^6.0.3": +"buffer@npm:6.0.3, buffer@npm:^6.0.3, buffer@npm:~6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" dependencies: @@ -11358,6 +11544,16 @@ __metadata: languageName: node linkType: hard +"bufferutil@npm:^4.0.1": + version: 4.0.9 + resolution: "bufferutil@npm:4.0.9" + dependencies: + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: f8a93279fc9bdcf32b42eba97edc672b39ca0fe5c55a8596099886cffc76ea9dd78e0f6f51ecee3b5ee06d2d564aa587036b5d4ea39b8b5ac797262a363cdf7d + languageName: node + linkType: hard + "bufferutil@npm:^4.0.8": version: 4.0.8 resolution: "bufferutil@npm:4.0.8" @@ -11606,6 +11802,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.3.0": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -11968,7 +12171,14 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.20.0": +"commander@npm:^13.1.0": + version: 13.1.0 + resolution: "commander@npm:13.1.0" + checksum: 7b8c5544bba704fbe84b7cab2e043df8586d5c114a4c5b607f83ae5060708940ed0b5bd5838cf8ce27539cde265c1cbd59ce3c8c6b017ed3eec8943e3a415164 + languageName: node + linkType: hard + +"commander@npm:^2.20.0, commander@npm:^2.20.3": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 @@ -12614,6 +12824,13 @@ __metadata: languageName: node linkType: hard +"delay@npm:^5.0.0": + version: 5.0.0 + resolution: "delay@npm:5.0.0" + checksum: 01cdc4cd0cd35fb622518a3df848e67e09763a38e7cdada2232b6fda9ddda72eddcf74f0e24211200fbe718434f2335f2a2633875a6c96037fefa6de42896ad7 + languageName: node + linkType: hard + "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -13305,6 +13522,22 @@ __metadata: languageName: node linkType: hard +"es6-promise@npm:^4.0.3": + version: 4.2.8 + resolution: "es6-promise@npm:4.2.8" + checksum: 2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3 + languageName: node + linkType: hard + +"es6-promisify@npm:^5.0.0": + version: 5.0.0 + resolution: "es6-promisify@npm:5.0.0" + dependencies: + es6-promise: "npm:^4.0.3" + checksum: 23284c6a733cbf7842ec98f41eac742c9f288a78753c4fe46652bae826446ced7615b9e8a5c5f121a08812b1cd478ea58630f3e1c3d70835bd5dcd69c7cd75c9 + languageName: node + linkType: hard + "esbuild-register@npm:^3.5.0": version: 3.6.0 resolution: "esbuild-register@npm:3.6.0" @@ -14348,6 +14581,13 @@ __metadata: languageName: node linkType: hard +"eyes@npm:^0.1.8": + version: 0.1.8 + resolution: "eyes@npm:0.1.8" + checksum: 4c79a9cbf45746d8c9f48cc957e35ad8ea336add1c7b8d5a0e002efc791a7a62b27b2188184ef1a1eea7bc3cd06b161791421e0e6c5fe78309705a162c53eea8 + languageName: node + linkType: hard + "fast-base64-decode@npm:^1.0.0": version: 1.0.0 resolution: "fast-base64-decode@npm:1.0.0" @@ -14437,6 +14677,13 @@ __metadata: languageName: node linkType: hard +"fast-stable-stringify@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-stable-stringify@npm:1.0.0" + checksum: 1d773440c7a9615950577665074746c2e92edafceefa789616ecb6166229e0ccc6dae206ca9b9f7da0d274ba5779162aab2d07940a0f6e52a41a4e555392eb3b + languageName: node + linkType: hard + "fast-text-encoding@npm:1.0.6": version: 1.0.6 resolution: "fast-text-encoding@npm:1.0.6" @@ -15652,6 +15899,15 @@ __metadata: languageName: node linkType: hard +"humanize-ms@npm:^1.2.1": + version: 1.2.1 + resolution: "humanize-ms@npm:1.2.1" + dependencies: + ms: "npm:^2.0.0" + checksum: f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a + languageName: node + linkType: hard + "hyphenate-style-name@npm:^1.0.3": version: 1.0.4 resolution: "hyphenate-style-name@npm:1.0.4" @@ -16347,6 +16603,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^4.0.1": + version: 4.0.1 + resolution: "isomorphic-ws@npm:4.0.1" + peerDependencies: + ws: "*" + checksum: 7cb90dc2f0eb409825558982fb15d7c1d757a88595efbab879592f9d2b63820d6bbfb5571ab8abe36c715946e165a413a99f6aafd9f40ab1f514d73487bc9996 + languageName: node + linkType: hard + "isows@npm:1.0.4": version: 1.0.4 resolution: "isows@npm:1.0.4" @@ -16456,6 +16721,28 @@ __metadata: languageName: node linkType: hard +"jayson@npm:^4.1.1": + version: 4.2.0 + resolution: "jayson@npm:4.2.0" + dependencies: + "@types/connect": "npm:^3.4.33" + "@types/node": "npm:^12.12.54" + "@types/ws": "npm:^7.4.4" + commander: "npm:^2.20.3" + delay: "npm:^5.0.0" + es6-promisify: "npm:^5.0.0" + eyes: "npm:^0.1.8" + isomorphic-ws: "npm:^4.0.1" + json-stringify-safe: "npm:^5.0.1" + stream-json: "npm:^1.9.1" + uuid: "npm:^8.3.2" + ws: "npm:^7.5.10" + bin: + jayson: bin/jayson.js + checksum: 062f525a0d15232c4361d10e0cd26960e998897e483408de03101e147c7bdf275db525bc1d5cc8aff4b777d1b1389004c8e9a5715304aedcf9930557787df6e3 + languageName: node + linkType: hard + "jest-changed-files@npm:^29.7.0": version: 29.7.0 resolution: "jest-changed-files@npm:29.7.0" @@ -17174,6 +17461,13 @@ __metadata: languageName: node linkType: hard +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + "json5@npm:^2.1.1, json5@npm:^2.2.1, json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -18711,7 +19005,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 @@ -18879,7 +19173,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.5.0": +"node-fetch@npm:^2.5.0, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -21400,6 +21694,28 @@ __metadata: languageName: node linkType: hard +"rpc-websockets@npm:^9.0.2": + version: 9.1.1 + resolution: "rpc-websockets@npm:9.1.1" + dependencies: + "@swc/helpers": "npm:^0.5.11" + "@types/uuid": "npm:^8.3.4" + "@types/ws": "npm:^8.2.2" + buffer: "npm:^6.0.3" + bufferutil: "npm:^4.0.1" + eventemitter3: "npm:^5.0.1" + utf-8-validate: "npm:^5.0.2" + uuid: "npm:^8.3.2" + ws: "npm:^8.5.0" + dependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: c1c9d78e90bf1c9c9f0607d924f4ff00e42d5b27b76053a384ae37479656912f06b726c1bbc463dbb8b420fcc1bbcaca487db35227a04ecd55e39ecff5cd5653 + languageName: node + linkType: hard + "run-applescript@npm:^5.0.0": version: 5.0.0 resolution: "run-applescript@npm:5.0.0" @@ -22062,6 +22378,22 @@ __metadata: languageName: node linkType: hard +"stream-chain@npm:^2.2.5": + version: 2.2.5 + resolution: "stream-chain@npm:2.2.5" + checksum: c512f50190d7c92d688fa64e7af540c51b661f9c2b775fc72bca38ea9bca515c64c22c2197b1be463741daacbaaa2dde8a8ea24ebda46f08391224f15249121a + languageName: node + linkType: hard + +"stream-json@npm:^1.9.1": + version: 1.9.1 + resolution: "stream-json@npm:1.9.1" + dependencies: + stream-chain: "npm:^2.2.5" + checksum: 0521e5cb3fb6b0e2561d715975e891bd81fa77d0239c8d0b1756846392bc3c7cdd7f1ddb0fe7ed77e6fdef58daab9e665d3b39f7d677bd0859e65a2bff59b92c + languageName: node + linkType: hard + "stream-shift@npm:^1.0.0": version: 1.0.1 resolution: "stream-shift@npm:1.0.1" @@ -22361,6 +22693,13 @@ __metadata: languageName: node linkType: hard +"superstruct@npm:^2.0.2": + version: 2.0.2 + resolution: "superstruct@npm:2.0.2" + checksum: c6853db5240b4920f47b3c864dd1e23ede6819ea399ad29a65387d746374f6958c5f1c5b7e5bb152d9db117a74973e5005056d9bb83c24e26f18ec6bfae4a718 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -22581,6 +22920,13 @@ __metadata: languageName: node linkType: hard +"text-encoding-utf-8@npm:^1.0.2": + version: 1.0.2 + resolution: "text-encoding-utf-8@npm:1.0.2" + checksum: 87a64b394c850e8387c2ca7fc6929a26ce97fb598f1c55cd0fdaec4b8e2c3ed6770f65b2f3309c9175ef64ac5e403c8e48b53ceeb86d2897940c5e19cc00bb99 + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -22881,6 +23227,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" From ddf374b61d141611ed4cd6604503c97c114df252 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 2 May 2025 13:28:57 -0300 Subject: [PATCH 09/91] chore: removed unnecesary adapter methods, getting balance --- packages/appkit/src/AppKit.ts | 18 ++++-- .../src/connectors/WalletConnectConnector.ts | 2 - packages/appkit/src/hooks/useAppKitAccount.ts | 17 +++++ packages/appkit/src/hooks/useProvider.ts | 6 +- packages/appkit/src/index.ts | 1 + packages/common/src/utils/TypeUtil.ts | 35 +---------- packages/ethers/src/adapter.ts | 31 +--------- packages/solana/src/adapter.ts | 62 ++----------------- packages/wagmi/src/adapter.ts | 26 +------- 9 files changed, 44 insertions(+), 154 deletions(-) create mode 100644 packages/appkit/src/hooks/useAppKitAccount.ts diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 08ef62d3..f1d7397f 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -17,7 +17,8 @@ import type { New_ConnectorType, Namespaces, CaipNetworkId, - AppKitNetwork + AppKitNetwork, + Provider } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; @@ -257,12 +258,17 @@ export class AppKit { ConnectionsController.setNetworks(options.networks); } - async disconnect(namespace: string): Promise { + async disconnect(namespace?: string): Promise { try { - const connection = ConnectionsController.state.connections[namespace]; + const connection = + ConnectionsController.state.connections[ + namespace ?? ConnectionsController.state.activeNamespace + ]; const connectorType = connection?.adapter?.connector?.type; - await ConnectionsController.disconnect(namespace); // This should trigger the 'disconnect' event handler via the adapter/connector + await ConnectionsController.disconnect( + namespace ?? ConnectionsController.state.activeNamespace + ); if (connectorType) { await StorageUtil.removeConnectedConnectors(connectorType); @@ -288,14 +294,14 @@ export class AppKit { } } - getProvider(namespace?: string): T | null { + getProvider(namespace?: string): T | null { const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; if (!activeNamespace) return null; const connection = ConnectionsController.state.connections[activeNamespace]; if (!connection || !connection.adapter || !connection.adapter.connector) return null; - return connection.adapter.connector.getProvider() as T | null; + return connection.adapter.connector.getProvider() as T; } getActiveAdapter(): BlockchainAdapter | null { diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index b7a457d3..5888bdd1 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -48,8 +48,6 @@ export class WalletConnectConnector extends WalletConnector { this.namespaces = session?.namespaces as Namespaces; - console.log('session', session); - this.provider.off('display_uri', onUri); return this.namespaces; diff --git a/packages/appkit/src/hooks/useAppKitAccount.ts b/packages/appkit/src/hooks/useAppKitAccount.ts new file mode 100644 index 00000000..dc7ab5e6 --- /dev/null +++ b/packages/appkit/src/hooks/useAppKitAccount.ts @@ -0,0 +1,17 @@ +import { ConnectionsController } from '@reown/appkit-core-react-native'; +import { useSnapshot } from 'valtio'; + +export function useAppKitAccount() { + const { + activeAddress: address, + activeNamespace, + connections + } = useSnapshot(ConnectionsController.state); + const connection = connections[activeNamespace]; + + return { + address: address?.split(':')[2], + isConnected: !!address, + chainId: connection?.activeChain + }; +} diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts index f4aad356..f38d75ae 100644 --- a/packages/appkit/src/hooks/useProvider.ts +++ b/packages/appkit/src/hooks/useProvider.ts @@ -1,9 +1,9 @@ import { useSnapshot } from 'valtio'; import { ConnectionsController } from '@reown/appkit-core-react-native'; -export function useProvider(namespace: string): T | null { - const { connections } = useSnapshot(ConnectionsController.state); - const connection = connections[namespace]; +export function useProvider(namespace?: string): T | null { + const { connections, activeNamespace } = useSnapshot(ConnectionsController.state); + const connection = connections[namespace ?? activeNamespace]; if (!connection) return null; diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index eccf2141..79240dba 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -23,4 +23,5 @@ export { CoreHelperUtil } from '@reown/appkit-core-react-native'; export * from './AppKit'; export { AppKitProvider, useAppKit } from './AppKitContext'; export { useProvider } from './hooks/useProvider'; +export { useAppKitAccount } from './hooks/useAppKitAccount'; export { WalletConnectConnector } from './connectors/WalletConnectConnector'; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index f8a64323..c85308c9 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -165,15 +165,9 @@ export abstract class BlockchainAdapter extends EventEmitter { abstract switchNetwork(network: AppKitNetwork): Promise; } -export abstract class EVMAdapter extends BlockchainAdapter { - abstract signMessage(params: SignMessageParams): Promise; - abstract sendTransaction(tx: TransactionData): Promise; -} +export abstract class EVMAdapter extends BlockchainAdapter {} -export abstract class SolanaBaseAdapter extends BlockchainAdapter { - abstract signMessage(params: SignMessageParams): Promise; - abstract sendTransaction(tx: TransactionData): Promise; -} +export abstract class SolanaBaseAdapter extends BlockchainAdapter {} export interface GetBalanceParams { address?: CaipAddress; @@ -240,31 +234,6 @@ export type New_ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; //********** Others **********// -export interface SignMessageParams { - message: string; - address: string; -} -export interface SignMessageResult { - signature: string; -} - -export interface TransactionData { - to: string; - value?: string; - data?: string; - [key: string]: any; -} - -export interface SignedTransaction { - raw: string; - [key: string]: any; -} - -export interface TransactionReceipt { - transactionHash: string; - [key: string]: any; -} - export interface ConnectionResponse { accounts: string[]; chainId: string; diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 06f17627..c9769e13 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -1,14 +1,11 @@ -import { formatEther, hexlify, isHexString, JsonRpcProvider, toUtf8Bytes } from 'ethers'; +import { formatEther, JsonRpcProvider } from 'ethers'; import { EVMAdapter, WalletConnector, type AppKitNetwork, type CaipAddress, type GetBalanceParams, - type GetBalanceResponse, - type SignMessageParams, - type SignMessageResult, - type TransactionReceipt + type GetBalanceResponse } from '@reown/appkit-common-react-native'; import { EthersHelpersUtil } from '@reown/appkit-scaffold-utils-react-native'; @@ -22,24 +19,6 @@ export class EthersAdapter extends EVMAdapter { }); } - async signMessage(params: SignMessageParams): Promise { - if (!this.connector) throw new Error('No active connector'); - - const provider = this.connector.getProvider(); - if (!provider) throw new Error('No active provider'); - - const { message, address } = params; - - const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); - - const signature = (await provider.request({ - method: 'personal_sign', - params: [hexMessage, address] - })) as `0x${string}`; - - return { signature }; - } - async getBalance(params: GetBalanceParams): Promise { const { network, address } = params; @@ -89,7 +68,7 @@ export class EthersAdapter extends EVMAdapter { if (!provider) throw new Error('No active provider'); try { - return await provider.request( + await provider.request( { method: 'wallet_switchEthereumChain', params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util @@ -113,10 +92,6 @@ export class EthersAdapter extends EVMAdapter { return namespaces[this.getSupportedNamespace()]?.accounts; } - sendTransaction(/*tx: TransactionData*/): Promise { - throw new Error('Method not implemented.'); - } - disconnect(): Promise { if (!this.connector) throw new Error('EthersAdapter:disconnect - No active connector'); diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 3b643b22..8e288867 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -4,13 +4,9 @@ import { type AppKitNetwork, type CaipAddress, type GetBalanceParams, - type GetBalanceResponse, - type SignMessageParams, - type SignMessageResult, - type TransactionReceipt + type GetBalanceResponse } from '@reown/appkit-common-react-native'; import { Connection, PublicKey } from '@solana/web3.js'; -import base58 from 'bs58'; export class SolanaAdapter extends SolanaBaseAdapter { private static supportedNamespace: string = 'solana'; @@ -22,30 +18,6 @@ export class SolanaAdapter extends SolanaBaseAdapter { }); } - async signMessage(params: SignMessageParams): Promise { - if (!this.connector) throw new Error('No active connector'); - - const provider = this.connector.getProvider(); - if (!provider) throw new Error('No active provider'); - - const { message } = params; - - // return this.request('eth_signTransaction', [tx]) as Promise; - // throw new Error('Method not implemented.'); - - const signParams = { - message: base58.encode(new TextEncoder().encode(message)), - pubkey: params.address || this.getAccounts()?.[0] //TODO: Check if this is correct - }; - - const signature = (await provider.request({ - method: 'solana_signTransaction', - params: [signParams] - })) as any; //TODO: check type - - return { signature }; - } - async getBalance(params: GetBalanceParams): Promise { const { network, address } = params; @@ -73,7 +45,7 @@ export class SolanaAdapter extends SolanaBaseAdapter { this.emit('balanceChanged', { namespace: this.getSupportedNamespace(), - address, + address: balanceAddress, balance }); @@ -90,32 +62,12 @@ export class SolanaAdapter extends SolanaBaseAdapter { if (!provider) throw new Error('No active provider'); try { - // await provider.request({ - // method: 'wallet_switchEthereumChain', - // params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util - // }); - - this.getBalance({ address: this.getAccounts()?.[0], network }); + //@ts-ignore //TODO: check this + await provider?.setDefaultChain(network.caipNetworkId); return; } catch (switchError: any) { - // const message = switchError?.message as string; - // if (/(?user rejected)/u.test(message?.toLowerCase())) { - // throw new Error('Chain is not supported'); - // } - // provider.request({ - // method: 'wallet_addEthereumChain', - // params: [ - // { - // chainId: EthersHelpersUtil.numberToHexString(Number(network.id)), - // rpcUrls: network.rpcUrls, - // chainName: network.name, - // nativeCurrency: network.nativeCurrency, - // blockExplorerUrls: network.blockExplorers, - // iconUrls: [PresetsUtil.NetworkImageIds[network.id]] - // } - // ] - // }); + throw switchError; } } @@ -126,10 +78,6 @@ export class SolanaAdapter extends SolanaBaseAdapter { return namespaces[this.getSupportedNamespace()]?.accounts; } - sendTransaction(/*tx: TransactionData*/): Promise { - throw new Error('Method not implemented.'); - } - disconnect(): Promise { if (!this.connector) throw new Error('SolanaAdapter:disconnect - No active connector'); diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index bbfff866..86bae9fa 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -4,12 +4,7 @@ import { type AppKitNetwork, type CaipAddress, type GetBalanceParams, - type GetBalanceResponse, - type SignedTransaction, - type SignMessageParams, - type SignMessageResult, - type TransactionData, - type TransactionReceipt + type GetBalanceResponse } from '@reown/appkit-common-react-native'; import { type Config, @@ -61,21 +56,6 @@ export class WagmiAdapter extends EVMAdapter { }); } - async signMessage(_params: SignMessageParams): Promise { - if (!this.connector) throw new Error('No active connector'); - - const provider = this.connector.getProvider(); - if (!provider) throw new Error('No active provider'); - - throw new Error('Method not implemented.'); - } - - async signTransaction(tx: TransactionData): Promise { - if (!this.connector) throw new Error('No active connector'); - - return this.request('eth_signTransaction', [tx]) as Promise; - } - async switchNetwork(network: AppKitNetwork): Promise { console.log('WagmiAdapter - switchNetwork', network); throw new Error('Method not implemented.'); @@ -97,10 +77,6 @@ export class WagmiAdapter extends EVMAdapter { return namespaces[this.getSupportedNamespace()]?.accounts; } - sendTransaction(/*tx: TransactionData*/): Promise { - throw new Error('Method not implemented.'); - } - disconnect(): Promise { throw new Error('Method not implemented.'); } From d352090ea1434e1f9ae0eab82d8b9a0e9f3a5254 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 2 May 2025 13:29:12 -0300 Subject: [PATCH 10/91] chore: added solana and ethers actions to sample app --- apps/native/App.tsx | 12 ++-- apps/native/src/views/EthersActionsView.tsx | 69 ++++++++++++++++++++ apps/native/src/views/SolanaActionsView.tsx | 72 +++++++++++++++++++++ 3 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 apps/native/src/views/EthersActionsView.tsx create mode 100644 apps/native/src/views/SolanaActionsView.tsx diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 99451f4b..bdd04246 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -18,12 +18,12 @@ import Toast from 'react-native-toast-message'; import { AppKitProvider, createAppKit, AppKit, AppKitButton } from '@reown/appkit-react-native'; // import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; -import { Text } from '@reown/appkit-ui-react-native'; +import { Button, Text } from '@reown/appkit-ui-react-native'; // import { siweConfig } from './src/utils/SiweUtils'; // import { AccountView } from './src/views/AccountView'; -// import { ActionsView } from './src/views/ActionsView'; +import { EthersActionsView } from './src/views/EthersActionsView'; // import { getCustomWallets } from './src/utils/misc'; // import { chains } from './src/utils/WagmiUtils'; // import { OpenButton } from './src/components/OpenButton'; @@ -32,7 +32,7 @@ import { EthersAdapter } from '@reown/appkit-ethers-react-native'; import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { mainnet, polygon, avalanche } from 'viem/chains'; import { solana } from './src/utils/ChainUtils'; - +import { SolanaActionsView } from './src/views/SolanaActionsView'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; const metadata = { @@ -130,10 +130,14 @@ export default function Native() { balance="show" /> {/* */} - {/* */} + + {/* */} {/* */} {/* */} + diff --git a/apps/native/src/views/EthersActionsView.tsx b/apps/native/src/views/EthersActionsView.tsx new file mode 100644 index 00000000..be9e9297 --- /dev/null +++ b/apps/native/src/views/EthersActionsView.tsx @@ -0,0 +1,69 @@ +import { StyleSheet } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { useAppKit, useAppKitAccount } from '@reown/appkit-react-native'; +import { hexlify, isHexString, toUtf8Bytes } from 'ethers'; + +import { ToastUtils } from '../utils/ToastUtils'; + +export function EthersActionsView() { + const isConnected = true; + const { appKit } = useAppKit(); + const { address, chainId } = useAppKitAccount(); + const provider = appKit?.getProvider('eip155'); + console.log('provider', provider?.setDefaultChain); + + const onSignSuccess = (data: any) => { + ToastUtils.showSuccessToast('Sign successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Sign failed', error.message); + }; + + const signMessage = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + + const message = 'hello appkit + ethers'; + const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); + + const signature = + (await provider.request({ + method: 'personal_sign', + params: [hexMessage, address] + }), + 'eip155:1'); + + onSignSuccess(signature); + } catch (error) { + console.log('error', error); + onSignError(error as Error); + } + }; + + return isConnected ? ( + + EVM Actions + + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginVertical: 16, + gap: 8 + } +}); diff --git a/apps/native/src/views/SolanaActionsView.tsx b/apps/native/src/views/SolanaActionsView.tsx new file mode 100644 index 00000000..7082aec6 --- /dev/null +++ b/apps/native/src/views/SolanaActionsView.tsx @@ -0,0 +1,72 @@ +import { StyleSheet } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { useAppKit, useAppKitAccount } from '@reown/appkit-react-native'; +import base58 from 'bs58'; + +import { ToastUtils } from '../utils/ToastUtils'; + +export function SolanaActionsView() { + const isConnected = true; + const { appKit } = useAppKit(); + const { address, chainId } = useAppKitAccount(); + + const provider = appKit?.getProvider('solana'); + + const onSignSuccess = (data: any) => { + ToastUtils.showSuccessToast('Sign successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Sign failed', error.message); + }; + + const signMessage = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + const encodedMessage = new TextEncoder().encode('Hello from AppKit'); + + const params = { + message: base58.encode(encodedMessage), + pubkey: address + }; + + const { signature } = (await provider.request( + { + method: 'solana_signMessage', + params + }, + chainId + )) as any; //TODO: check type + + onSignSuccess(signature); + } catch (error) { + onSignError(error as Error); + } + }; + + return isConnected ? ( + + Solana Actions + + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginVertical: 16, + gap: 8 + } +}); From 9021e565b15f860a2c38eb847fc033fc9edbf0b7 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 2 May 2025 16:30:45 -0300 Subject: [PATCH 11/91] chore: filter accountChanged --- apps/native/App.tsx | 6 +- apps/native/package.json | 6 +- apps/native/src/views/ActionsView.tsx | 70 +- apps/native/src/views/EthersActionsView.tsx | 10 +- apps/native/src/views/WagmiActionsView.tsx | 76 ++ package.json | 10 +- packages/appkit/src/AppKit.ts | 4 +- .../partials/w3m-account-activity/utils.ts | 3 +- .../src/controllers/ConnectionsController.ts | 1 - packages/ethers/package.json | 2 +- packages/ethers/src/adapter.ts | 18 +- packages/ethers/src/index.tsx | 167 ---- packages/ethers5/package.json | 2 +- packages/solana/src/adapter.ts | 18 +- packages/ui/src/index.ts | 3 +- yarn.lock | 915 +++++------------- 16 files changed, 388 insertions(+), 923 deletions(-) create mode 100644 apps/native/src/views/WagmiActionsView.tsx diff --git a/apps/native/App.tsx b/apps/native/App.tsx index bdd04246..9cd7d43f 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -23,7 +23,6 @@ import { Button, Text } from '@reown/appkit-ui-react-native'; // import { siweConfig } from './src/utils/SiweUtils'; // import { AccountView } from './src/views/AccountView'; -import { EthersActionsView } from './src/views/EthersActionsView'; // import { getCustomWallets } from './src/utils/misc'; // import { chains } from './src/utils/WagmiUtils'; // import { OpenButton } from './src/components/OpenButton'; @@ -32,7 +31,7 @@ import { EthersAdapter } from '@reown/appkit-ethers-react-native'; import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { mainnet, polygon, avalanche } from 'viem/chains'; import { solana } from './src/utils/ChainUtils'; -import { SolanaActionsView } from './src/views/SolanaActionsView'; +import { ActionsView } from './src/views/ActionsView'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; const metadata = { @@ -130,8 +129,7 @@ export default function Native() { balance="show" /> {/* */} - - + {/* */} {/* */} {/* */} diff --git a/apps/native/package.json b/apps/native/package.json index 6d4da975..1ceb3978 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -29,7 +29,7 @@ "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-query-persist-client": "5.56.2", - "@walletconnect/react-native-compat": "2.19.1", + "@walletconnect/react-native-compat": "2.20.2", "ethers": "6.13.5", "expo": "^52.0.38", "expo-application": "~6.0.2", @@ -46,8 +46,8 @@ "react-native-web": "~0.19.13", "react-native-webview": "13.12.5", "uuid": "^11.1.0", - "viem": "2.23.10", - "wagmi": "2.14.13" + "viem": "2.28.3", + "wagmi": "2.15.1" }, "devDependencies": { "@babel/core": "^7.24.0", diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index ba284e2b..1b8af8ce 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -1,76 +1,24 @@ -import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; import { StyleSheet } from 'react-native'; -import { useSignMessage, useAccount, useSendTransaction, useEstimateGas } from 'wagmi'; -import { Hex, parseEther } from 'viem'; -import { SendTransactionData, SignMessageData } from 'wagmi/query'; -import { ToastUtils } from '../utils/ToastUtils'; +import { FlexView } from '@reown/appkit-ui-react-native'; +import { useAppKitAccount } from '@reown/appkit-react-native'; -export function ActionsView() { - const { isConnected } = useAccount(); - - const onSignSuccess = (data: SignMessageData) => { - ToastUtils.showSuccessToast('Signature successful', data); - }; - - const onSignError = (error: Error) => { - ToastUtils.showErrorToast('Signature failed', error.message); - }; - - const onSendSuccess = (data: SendTransactionData) => { - ToastUtils.showSuccessToast('Transaction successful', data); - }; - - const onSendError = (error: Error) => { - ToastUtils.showErrorToast('Transaction failed', error.message); - }; +import { EthersActionsView } from './EthersActionsView'; +import { SolanaActionsView } from './SolanaActionsView'; - const { isPending, signMessage } = useSignMessage({ - mutation: { - onSuccess: onSignSuccess, - onError: onSignError - } - }); - const TX = { - to: '0x704457b418E9Fb723e1Bc0cB98106a6B8Cf87689' as Hex, // Test wallet - value: parseEther('0.001'), - data: '0x' as Hex - }; - - const { data: gas, isError: isGasError } = useEstimateGas(TX); - - const { - isPending: isSending, - - sendTransaction - } = useSendTransaction({ - mutation: { - onSuccess: onSendSuccess, - onError: onSendError - } - }); +export function ActionsView() { + const isConnected = true; + const { chainId } = useAppKitAccount(); return isConnected ? ( - Wagmi Actions - - {isGasError && Error estimating gas} - - {isSending && Check Wallet} + {chainId?.startsWith('eip155') ? : } ) : null; } const styles = StyleSheet.create({ container: { - marginTop: 16, + marginVertical: 16, gap: 8 } }); diff --git a/apps/native/src/views/EthersActionsView.tsx b/apps/native/src/views/EthersActionsView.tsx index be9e9297..1370b307 100644 --- a/apps/native/src/views/EthersActionsView.tsx +++ b/apps/native/src/views/EthersActionsView.tsx @@ -10,7 +10,6 @@ export function EthersActionsView() { const { appKit } = useAppKit(); const { address, chainId } = useAppKitAccount(); const provider = appKit?.getProvider('eip155'); - console.log('provider', provider?.setDefaultChain); const onSignSuccess = (data: any) => { ToastUtils.showSuccessToast('Sign successful', data); @@ -37,12 +36,13 @@ export function EthersActionsView() { const message = 'hello appkit + ethers'; const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); - const signature = - (await provider.request({ + const signature = await provider.request( + { method: 'personal_sign', params: [hexMessage, address] - }), - 'eip155:1'); + }, + chainId + ); onSignSuccess(signature); } catch (error) { diff --git a/apps/native/src/views/WagmiActionsView.tsx b/apps/native/src/views/WagmiActionsView.tsx new file mode 100644 index 00000000..7e201997 --- /dev/null +++ b/apps/native/src/views/WagmiActionsView.tsx @@ -0,0 +1,76 @@ +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; +import { useSignMessage, useAccount, useSendTransaction, useEstimateGas } from 'wagmi'; +import { Hex, parseEther } from 'viem'; +import { SendTransactionData, SignMessageData } from 'wagmi/query'; +import { ToastUtils } from '../utils/ToastUtils'; + +export function WagmiActionsView() { + const { isConnected } = useAccount(); + + const onSignSuccess = (data: SignMessageData) => { + ToastUtils.showSuccessToast('Signature successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Signature failed', error.message); + }; + + const onSendSuccess = (data: SendTransactionData) => { + ToastUtils.showSuccessToast('Transaction successful', data); + }; + + const onSendError = (error: Error) => { + ToastUtils.showErrorToast('Transaction failed', error.message); + }; + + const { isPending, signMessage } = useSignMessage({ + mutation: { + onSuccess: onSignSuccess, + onError: onSignError + } + }); + const TX = { + to: '0x704457b418E9Fb723e1Bc0cB98106a6B8Cf87689' as Hex, // Test wallet + value: parseEther('0.001'), + data: '0x' as Hex + }; + + const { data: gas, isError: isGasError } = useEstimateGas(TX); + + const { + isPending: isSending, + + sendTransaction + } = useSendTransaction({ + mutation: { + onSuccess: onSendSuccess, + onError: onSendError + } + }); + + return isConnected ? ( + + Wagmi Actions + + {isGasError && Error estimating gas} + + {isSending && Check Wallet} + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginTop: 16, + gap: 8 + } +}); diff --git a/package.json b/package.json index b708af59..9a594214 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@types/jest": "29.5.7", "@types/qrcode": "1.5.5", "@types/react": "18.2.79", - "@walletconnect/react-native-compat": "2.19.1", + "@walletconnect/react-native-compat": "2.20.2", "babel-jest": "^29.7.0", "eslint": "^8.46.0", "eslint-plugin-ft-flow": "2.0.3", @@ -78,8 +78,8 @@ "tsconfig": "*", "turbo": "2.1.1", "typescript": "5.2.2", - "viem": "2.23.10", - "wagmi": "2.14.13" + "viem": "2.28.3", + "wagmi": "2.15.1" }, "packageManager": "yarn@4.0.2", "resolutions": { @@ -92,6 +92,8 @@ "esbuild": "0.25.0", "postcss": "8.4.31", "cookie": "0.7.0", - "ip": "^2.0.1" + "ip": "^2.0.1", + "@walletconnect/ethereum-provider": "2.20.2", + "@walletconnect/universal-provider": "2.20.2" } } diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index f1d7397f..fa7bb9e0 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -229,7 +229,8 @@ export class AppKit { private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { adapter.on('accountsChanged', ({ accounts, namespace }) => { - ConnectionsController.updateAccounts(namespace, accounts); + console.log('accountsChanged', accounts, namespace); + //TODO: do i need this? }); adapter.on('chainChanged', ({ chainId, namespace }) => { @@ -244,6 +245,7 @@ export class AppKit { }); adapter.on('balanceChanged', ({ namespace, address, balance }) => { + // console.log('balanceChanged', namespace, address, balance); ConnectionsController.updateBalance(namespace, address, balance); }); } diff --git a/packages/appkit/src/partials/w3m-account-activity/utils.ts b/packages/appkit/src/partials/w3m-account-activity/utils.ts index be865523..90eb4d11 100644 --- a/packages/appkit/src/partials/w3m-account-activity/utils.ts +++ b/packages/appkit/src/partials/w3m-account-activity/utils.ts @@ -1,6 +1,5 @@ import { DateUtil, type Transaction } from '@reown/appkit-common-react-native'; -import { TransactionUtil } from '@reown/appkit-ui-react-native'; -import type { TransactionType } from '@reown/appkit-ui-react-native/lib/typescript/utils/TypesUtil'; +import { TransactionUtil, type TransactionType } from '@reown/appkit-ui-react-native'; export function getTransactionListItemProps(transaction: Transaction) { const date = DateUtil.formatDate(transaction?.metadata?.minedAt); diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 04a19cb0..7aca2af8 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -126,7 +126,6 @@ export const ConnectionsController = { accounts, chains }; - // console.log('ConnectionController:storeConnection - state.connections', baseState.connections); }, updateAccounts(namespace: string, accounts: CaipAddress[]) { diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 0212d5f6..9384cbb6 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -43,7 +43,7 @@ "@reown/appkit-scaffold-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", - "@walletconnect/ethereum-provider": "2.17.3" + "@walletconnect/ethereum-provider": "2.20.2" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index c9769e13..5fdf59a5 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -109,15 +109,21 @@ export class EthersAdapter extends EVMAdapter { return EthersAdapter.supportedNamespace; } - onChainChanged(chainId: string): void { - // console.log('EthersAdapter - onChainChanged', chainId); + override onChainChanged(chainId: string): void { this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); } - onAccountsChanged(accounts: string[]): void { - // console.log('EthersAdapter - onAccountsChanged', accounts); - // Emit this change to AppKit with the corresponding namespace. - this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + override onAccountsChanged(accounts: string[]): void { + const _accounts = this.getAccounts(); + const shouldEmit = _accounts?.some(account => { + const accountAddress = account.split(':')[2]; + + return accountAddress !== undefined && accounts.includes(accountAddress); + }); + + if (shouldEmit) { + this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + } } onDisconnect(): void { diff --git a/packages/ethers/src/index.tsx b/packages/ethers/src/index.tsx index 3781f889..c9acfc5b 100644 --- a/packages/ethers/src/index.tsx +++ b/packages/ethers/src/index.tsx @@ -1,169 +1,2 @@ -import { useEffect, useState, useSyncExternalStore } from 'react'; -import { useSnapshot } from 'valtio'; -import { EthersStoreUtil, type Provider } from '@reown/appkit-scaffold-utils-react-native'; - -export { - AccountButton, - AppKitButton, - ConnectButton, - NetworkButton, - AppKit -} from '@reown/appkit-scaffold-react-native'; -import type { EventName, EventsControllerState } from '@reown/appkit-scaffold-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; -export { defaultConfig } from './utils/defaultConfig'; - -import type { AppKitOptions } from './client'; -import { AppKit } from './client'; - import { EthersAdapter } from './adapter'; export { EthersAdapter }; - -// -- Types ------------------------------------------------------------------- -export type { AppKitOptions } from './client'; - -type OpenOptions = Parameters[0]; - - -// -- Setup ------------------------------------------------------------------- -let modal: AppKit | undefined; - -export function createAppKit(options: AppKitOptions) { - if (!modal) { - modal = new AppKit({ - ...options, - _sdkVersion: `react-native-ethers-${ConstantsUtil.VERSION}` - }); - } - - return modal; -} - -// -- Hooks ------------------------------------------------------------------- -export function useAppKit() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKit" hook'); - } - - async function open(options?: OpenOptions) { - await modal?.open(options); - } - - async function close() { - await modal?.close(); - } - - return { open, close }; -} - -export function useAppKitState() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitState" hook'); - } - - const [state, setState] = useState(modal.getState()); - - useEffect(() => { - const unsubscribe = modal?.subscribeState(newState => { - if (newState) setState({ ...newState }); - }); - - return () => { - unsubscribe?.(); - }; - }, []); - - return state; -} - -export function useAppKitProvider() { - const { provider, providerType } = useSnapshot(EthersStoreUtil.state); - - const walletProvider = provider as Provider | undefined; - const walletProviderType = providerType; - - return { - walletProvider, - walletProviderType - }; -} - -export function useDisconnect() { - async function disconnect() { - await modal?.disconnect(); - } - - return { - disconnect - }; -} - -export function useAppKitAccount() { - const { address, isConnected, chainId } = useSnapshot(EthersStoreUtil.state); - - return { - address, - isConnected, - chainId - }; -} - -export function useWalletInfo() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useWalletInfo" hook'); - } - - const walletInfo = useSyncExternalStore( - modal.subscribeWalletInfo, - modal.getWalletInfo, - modal.getWalletInfo - ); - - return { walletInfo }; -} - -export function useAppKitError() { - const { error } = useSnapshot(EthersStoreUtil.state); - - return { - error - }; -} - -export function useAppKitEvents(callback?: (newEvent: EventsControllerState) => void) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEvents" hook'); - } - - const [event, setEvents] = useState(modal.getEvent()); - - useEffect(() => { - const unsubscribe = modal?.subscribeEvents(newEvent => { - setEvents({ ...newEvent }); - callback?.(newEvent); - }); - - return () => { - unsubscribe?.(); - }; - }, [callback]); - - return event; -} - -export function useAppKitEventSubscription( - event: EventName, - callback: (newEvent: EventsControllerState) => void -) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEventSubscription" hook'); - } - - useEffect(() => { - const unsubscribe = modal?.subscribeEvent(event, callback); - - return () => { - unsubscribe?.(); - }; - }, [callback, event]); -} diff --git a/packages/ethers5/package.json b/packages/ethers5/package.json index ff548157..63bd9136 100644 --- a/packages/ethers5/package.json +++ b/packages/ethers5/package.json @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", - "@walletconnect/ethereum-provider": "2.17.3" + "@walletconnect/ethereum-provider": "2.20.2" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 8e288867..03a6da27 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -95,15 +95,21 @@ export class SolanaAdapter extends SolanaBaseAdapter { return SolanaAdapter.supportedNamespace; } - onChainChanged(chainId: string): void { - // console.log('SolanaAdapter - onChainChanged', chainId); + override onChainChanged(chainId: string): void { this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); } - onAccountsChanged(accounts: string[]): void { - // console.log('SolanaAdapter - onAccountsChanged', accounts); - // Emit this change to AppKit with the corresponding namespace. - this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + override onAccountsChanged(accounts: string[]): void { + const _accounts = this.getAccounts(); + const shouldEmit = _accounts?.some(account => { + const accountAddress = account.split(':')[2]; + + return accountAddress !== undefined && accounts.includes(accountAddress); + }); + + if (shouldEmit) { + this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + } } onDisconnect(): void { diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index da47af0c..4bcfb1a3 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -80,7 +80,8 @@ export type { SizeType, TagType, TextType, - VisualType + VisualType, + TransactionType } from './utils/TypesUtil'; export { UiUtil } from './utils/UiUtil'; export { TransactionUtil } from './utils/TransactionUtil'; diff --git a/yarn.lock b/yarn.lock index a21d8f8d..7ec4d59e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -116,7 +116,7 @@ __metadata: "@types/gh-pages": "npm:^6" "@types/node": "npm:^22.10.1" "@types/react": "npm:~18.2.79" - "@walletconnect/react-native-compat": "npm:2.19.1" + "@walletconnect/react-native-compat": "npm:2.20.2" babel-plugin-module-resolver: "npm:^5.0.0" ethers: "npm:6.13.5" expo: "npm:^52.0.38" @@ -136,8 +136,8 @@ __metadata: react-native-webview: "npm:13.12.5" typescript: "npm:~5.3.3" uuid: "npm:^11.1.0" - viem: "npm:2.23.10" - wagmi: "npm:2.14.13" + viem: "npm:2.28.3" + wagmi: "npm:2.15.1" languageName: unknown linkType: soft @@ -5761,19 +5761,19 @@ __metadata: languageName: node linkType: hard -"@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0": - version: 1.1.1 - resolution: "@lit-labs/ssr-dom-shim@npm:1.1.1" - checksum: bc530a6d390a71e44a74f0d79ab78df0c3cf814f5a69e64c60271d626f4b871d0269c82f2b1bcaf9ef1a84f361f50a1fc70c790873cded769e8f0e4f1fa01ff8 +"@lit-labs/ssr-dom-shim@npm:^1.2.0": + version: 1.3.0 + resolution: "@lit-labs/ssr-dom-shim@npm:1.3.0" + checksum: 743a9b295ef2f186712f08883da553c9990be291409615309c99aa4946cfe440a184e4213c790c24505c80beb86b9cfecf10b5fb30ce17c83698f8424f48678d languageName: node linkType: hard -"@lit/reactive-element@npm:^1.3.0, @lit/reactive-element@npm:^1.6.0": - version: 1.6.3 - resolution: "@lit/reactive-element@npm:1.6.3" +"@lit/reactive-element@npm:^2.0.0, @lit/reactive-element@npm:^2.1.0": + version: 2.1.0 + resolution: "@lit/reactive-element@npm:2.1.0" dependencies: - "@lit-labs/ssr-dom-shim": "npm:^1.0.0" - checksum: 10f1d25e24e32feb21c4c6f9e11d062901241602e12c4ecf746b3138f87fed4d8394194645514d5c1bfd5f33f3fd56ee8ef41344e2cb4413c40fe4961ec9d419 + "@lit-labs/ssr-dom-shim": "npm:^1.2.0" + checksum: 3cd61c4e7cc8effeb2c246d5dada8fbe0a730e9e0dd488eb38c91a4f63b773e3b7f86f8384051677298e73de470c7ca6b5634df3ca190b307f8bb8e0d51bb91c languageName: node linkType: hard @@ -6041,91 +6041,6 @@ __metadata: languageName: node linkType: hard -"@motionone/animation@npm:^10.15.1, @motionone/animation@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/animation@npm:10.16.3" - dependencies: - "@motionone/easing": "npm:^10.16.3" - "@motionone/types": "npm:^10.16.3" - "@motionone/utils": "npm:^10.16.3" - tslib: "npm:^2.3.1" - checksum: c1bb7a03acc9c09647321a4653bf53878ea05ce91305507cb4000d75641dcad85faa8696ef12d0c28fa52d4b3708bc7ae34334c95ef532567a26082f0176ea4a - languageName: node - linkType: hard - -"@motionone/dom@npm:^10.16.2, @motionone/dom@npm:^10.16.4": - version: 10.16.4 - resolution: "@motionone/dom@npm:10.16.4" - dependencies: - "@motionone/animation": "npm:^10.16.3" - "@motionone/generators": "npm:^10.16.4" - "@motionone/types": "npm:^10.16.3" - "@motionone/utils": "npm:^10.16.3" - hey-listen: "npm:^1.0.8" - tslib: "npm:^2.3.1" - checksum: 1efaa29a18471c18dbe7f849a7c83b12c27edf85209cb366856720e051870302c27567f5eab2a1aef3aa7ae1438c6fbc3a7e686077f5ed4e173e4cca8d22e0d5 - languageName: node - linkType: hard - -"@motionone/easing@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/easing@npm:10.16.3" - dependencies: - "@motionone/utils": "npm:^10.16.3" - tslib: "npm:^2.3.1" - checksum: df98a643f0b2955afd16b78063899d050b22cfcf3db1bb86ecdbde831614f24c41143d5d887bc287f6de979baa20a00e8e1dca39ef7b2dfb67c0ec1b1ca0bcaa - languageName: node - linkType: hard - -"@motionone/generators@npm:^10.16.4": - version: 10.16.4 - resolution: "@motionone/generators@npm:10.16.4" - dependencies: - "@motionone/types": "npm:^10.16.3" - "@motionone/utils": "npm:^10.16.3" - tslib: "npm:^2.3.1" - checksum: cef71d1236a625b3579791d480ebd1875bec2a62e249771eb2af883981074016cc6f2ef112c2bf27f93d05d19830893f3f486944cd68d2fbf35a990c41729152 - languageName: node - linkType: hard - -"@motionone/svelte@npm:^10.16.2": - version: 10.16.4 - resolution: "@motionone/svelte@npm:10.16.4" - dependencies: - "@motionone/dom": "npm:^10.16.4" - tslib: "npm:^2.3.1" - checksum: a3f91d3ac5617ac8a2847abc0c8fad417cdc2cd9d814d60f7de2c909e4beeaf834b45a4288c8af6d26f62958a6c69714313b37ea6cd5aa2a9d1ad5198ec5881f - languageName: node - linkType: hard - -"@motionone/types@npm:^10.15.1, @motionone/types@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/types@npm:10.16.3" - checksum: a792acd8bacd7949c29fd47fda1d3d7919b86ab209499a374a1f3c85f57a92d16f7a05f94edc6d46831c55180da2ff5e1193fa538bcb76e0ff38a24e25da2e87 - languageName: node - linkType: hard - -"@motionone/utils@npm:^10.15.1, @motionone/utils@npm:^10.16.3": - version: 10.16.3 - resolution: "@motionone/utils@npm:10.16.3" - dependencies: - "@motionone/types": "npm:^10.16.3" - hey-listen: "npm:^1.0.8" - tslib: "npm:^2.3.1" - checksum: c5a1cce9bf5d1e8c5051a4636bd6a7030bf67f5662a94a8ec1524a72de3baca3f4c59e46cee9a41b111806fdd2956256c65c7e99b7de260803f2e44840bbae11 - languageName: node - linkType: hard - -"@motionone/vue@npm:^10.16.2": - version: 10.16.4 - resolution: "@motionone/vue@npm:10.16.4" - dependencies: - "@motionone/dom": "npm:^10.16.4" - tslib: "npm:^2.3.1" - checksum: 0f3096c0956848cb67c4926e65b7034d854cf704573a277679713c5a8045347c3c043f50adad0c84ee3e88c046d35ab88ec4380e5acd729f81900381e0b1fd0d - languageName: node - linkType: hard - "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -6194,6 +6109,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.8.2": + version: 1.8.2 + resolution: "@noble/curves@npm:1.8.2" + dependencies: + "@noble/hashes": "npm:1.7.2" + checksum: e7ef119b114681d6b7530b29a21f9bbea6fa6973bc369167da2158d05054cc6e6dbfb636ba89fad7707abacc150de30188b33192f94513911b24bdb87af50bbd + languageName: node + linkType: hard + "@noble/curves@npm:^1.4.0, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -6263,6 +6187,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.7.2": + version: 1.7.2 + resolution: "@noble/hashes@npm:1.7.2" + checksum: b1411eab3c0b6691d847e9394fe7f1fcd45eeb037547c8f97e7d03c5068a499b4aef188e8e717eee67389dca4fee17d69d7e0f58af6c092567b0b76359b114b2 + languageName: node + linkType: hard + "@noble/hashes@npm:1.8.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" @@ -7209,6 +7140,30 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-common@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-common@npm:1.7.3" + dependencies: + big.js: "npm:6.2.2" + dayjs: "npm:1.11.13" + viem: "npm:>=2.23.11" + checksum: c938dffc42494daa0e970a22c7b5282da378c08e585d0d2e5c774faa59143f881e24deb0dcc0eb933b3d7b057edf39ef804c5c08439147ff952b1508735bc638 + languageName: node + linkType: hard + +"@reown/appkit-controllers@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-controllers@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + "@walletconnect/universal-provider": "npm:2.19.2" + valtio: "npm:1.13.2" + viem: "npm:>=2.23.11" + checksum: 775c25f7697a0ff59720cfd17c7317ac87284416d2b5e187bd05fba42f6f1294d6cab45c0509fb6faec9477ee60ce3b7cd72b78ae6c393df14fabd1874858650 + languageName: node + linkType: hard + "@reown/appkit-core-react-native@npm:1.2.3, @reown/appkit-core-react-native@workspace:packages/core": version: 0.0.0-use.local resolution: "@reown/appkit-core-react-native@workspace:packages/core" @@ -7233,7 +7188,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" - "@walletconnect/ethereum-provider": "npm:2.17.3" + "@walletconnect/ethereum-provider": "npm:2.20.2" ethers: "npm:6.10.0" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -7254,7 +7209,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" - "@walletconnect/ethereum-provider": "npm:2.17.3" + "@walletconnect/ethereum-provider": "npm:2.20.2" ethers: "npm:5.7.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -7267,6 +7222,15 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-polyfills@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-polyfills@npm:1.7.3" + dependencies: + buffer: "npm:6.0.3" + checksum: c2f347ba0dbfc435ca05e53abcc38ec0114478fe6aaaf198a58bf0a938eb44fd18ba4ed5c955f76fa79df7d4e1a15276afc7864573dd2b9d251f58bc33567e6d + languageName: node + linkType: hard + "@reown/appkit-react-native@npm:1.2.3, @reown/appkit-react-native@workspace:*, @reown/appkit-react-native@workspace:packages/appkit": version: 0.0.0-use.local resolution: "@reown/appkit-react-native@workspace:packages/appkit" @@ -7300,6 +7264,20 @@ __metadata: languageName: node linkType: hard +"@reown/appkit-scaffold-ui@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-scaffold-ui@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-ui": "npm:1.7.3" + "@reown/appkit-utils": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + lit: "npm:3.1.0" + checksum: e1511b06ef44da380cd5ff2f11dec65d920f32569de72a862d0ae36cce00e591dd73287df20e16af93734723086340057fbcb156429707cf9cf29a989964a9e0 + languageName: node + linkType: hard + "@reown/appkit-scaffold-utils-react-native@npm:1.2.3, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" @@ -7349,6 +7327,37 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-ui@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-ui@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + lit: "npm:3.1.0" + qrcode: "npm:1.5.3" + checksum: d70c1ad9a143cb831c1d005ce1c72a0b8ce1c6cd8aa4a3bc1f515902386873544905ca86b431419bc8b68e4ef608b405538619a55ff4db490020766bf84edbf3 + languageName: node + linkType: hard + +"@reown/appkit-utils@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-utils@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-polyfills": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/universal-provider": "npm:2.19.2" + valtio: "npm:1.13.2" + viem: "npm:>=2.23.11" + peerDependencies: + valtio: 1.13.2 + checksum: dbcf4e2b8dc2edf653ba4e9725addd4aed8f36fed7c24d875afcf26300100cc88fa0b3a8048fcdd3ed21d3371d5a63fb43276aa5fefd779f30186b69fe2ad6c1 + languageName: node + linkType: hard + "@reown/appkit-wagmi-react-native@npm:1.2.3, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": version: 0.0.0-use.local resolution: "@reown/appkit-wagmi-react-native@workspace:packages/wagmi" @@ -7383,13 +7392,45 @@ __metadata: languageName: unknown linkType: soft -"@safe-global/safe-apps-provider@npm:0.18.5": - version: 0.18.5 - resolution: "@safe-global/safe-apps-provider@npm:0.18.5" +"@reown/appkit-wallet@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit-wallet@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-polyfills": "npm:1.7.3" + "@walletconnect/logger": "npm:2.1.2" + zod: "npm:3.22.4" + checksum: 8468fa16a0fb64d7c45e4e7ed400f49aacf58e7aa74033b68c54b3fdbd74f934f6156c5c95839189b5a9adb746f2611dc2d57ba2ab87efbed4017e286edff50f + languageName: node + linkType: hard + +"@reown/appkit@npm:1.7.3": + version: 1.7.3 + resolution: "@reown/appkit@npm:1.7.3" + dependencies: + "@reown/appkit-common": "npm:1.7.3" + "@reown/appkit-controllers": "npm:1.7.3" + "@reown/appkit-polyfills": "npm:1.7.3" + "@reown/appkit-scaffold-ui": "npm:1.7.3" + "@reown/appkit-ui": "npm:1.7.3" + "@reown/appkit-utils": "npm:1.7.3" + "@reown/appkit-wallet": "npm:1.7.3" + "@walletconnect/types": "npm:2.19.2" + "@walletconnect/universal-provider": "npm:2.19.2" + bs58: "npm:6.0.0" + valtio: "npm:1.13.2" + viem: "npm:>=2.23.11" + checksum: 0dd83161b3468ffda5c76503540f69eb8f9c9c77ce9e4efb2d5f0bf94127815f44d2b32d0bfb9d30db6e29877905b64379e1d035c6d0a40f5445ac94827714a4 + languageName: node + linkType: hard + +"@safe-global/safe-apps-provider@npm:0.18.6": + version: 0.18.6 + resolution: "@safe-global/safe-apps-provider@npm:0.18.6" dependencies: "@safe-global/safe-apps-sdk": "npm:^9.1.0" events: "npm:^3.3.0" - checksum: 5699b4abd63d1042aca299cddb466ebf79b0e6709a22b277c7320343edce36e50f4d5356c4eda4497e1c2f4d6a92b14b29c7aefe0cf673f5614752f5ff6fbac5 + checksum: e8567a97e43740bfe21b6f8a7759cabed2bc96eb50fd494118cab13a20f14797fbca3e02d18f0395054fcfbf2fd86315e5433d5b26f73bed6c3c86881087716c languageName: node linkType: hard @@ -7670,176 +7711,6 @@ __metadata: languageName: node linkType: hard -"@stablelib/aead@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/aead@npm:1.0.1" - checksum: 8ec16795a6f94264f93514661e024c5b0434d75000ea133923c57f0db30eab8ddc74fa35f5ff1ae4886803a8b92e169b828512c9e6bc02c818688d0f5b9f5aef - languageName: node - linkType: hard - -"@stablelib/binary@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/binary@npm:1.0.1" - dependencies: - "@stablelib/int": "npm:^1.0.1" - checksum: 154cb558d8b7c20ca5dc2e38abca2a3716ce36429bf1b9c298939cea0929766ed954feb8a9c59245ac64c923d5d3466bb7d99f281debd3a9d561e1279b11cd35 - languageName: node - linkType: hard - -"@stablelib/bytes@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/bytes@npm:1.0.1" - checksum: ee99bb15dac2f4ae1aa4e7a571e76483617a441feff422442f293993bc8b2c7ef021285c98f91a043bc05fb70502457799e28ffd43a8564a17913ee5ce889237 - languageName: node - linkType: hard - -"@stablelib/chacha20poly1305@npm:1.0.1": - version: 1.0.1 - resolution: "@stablelib/chacha20poly1305@npm:1.0.1" - dependencies: - "@stablelib/aead": "npm:^1.0.1" - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/chacha": "npm:^1.0.1" - "@stablelib/constant-time": "npm:^1.0.1" - "@stablelib/poly1305": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: fe202aa8aface111c72bc9ec099f9c36a7b1470eda9834e436bb228618a704929f095b937f04e867fe4d5c40216ff089cbfeb2eeb092ab33af39ff333eb2c1e6 - languageName: node - linkType: hard - -"@stablelib/chacha@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/chacha@npm:1.0.1" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 4d70b484ae89416d21504024f977f5517bf16b344b10fb98382c9e3e52fe8ca77ac65f5d6a358d8b152f2c9ffed101a1eb15ed1707cdf906e1b6624db78d2d16 - languageName: node - linkType: hard - -"@stablelib/constant-time@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/constant-time@npm:1.0.1" - checksum: 694a282441215735a1fdfa3d06db5a28ba92423890967a154514ef28e0d0298ce7b6a2bc65ebc4273573d6669a6b601d330614747aa2e69078c1d523d7069e12 - languageName: node - linkType: hard - -"@stablelib/ed25519@npm:^1.0.2": - version: 1.0.3 - resolution: "@stablelib/ed25519@npm:1.0.3" - dependencies: - "@stablelib/random": "npm:^1.0.2" - "@stablelib/sha512": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: b4a05e3c24dabd8a9e0b5bd72dea761bfb4b5c66404308e9f0529ef898e75d6f588234920762d5372cb920d9d47811250160109f02d04b6eed53835fb6916eb9 - languageName: node - linkType: hard - -"@stablelib/hash@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/hash@npm:1.0.1" - checksum: 58b5572a4067820b77a1606ed2d4a6dc4068c5475f68ba0918860a5f45adf60b33024a0cea9532dcd8b7345c53b3c9636a23723f5f8ae83e0c3648f91fb5b5cc - languageName: node - linkType: hard - -"@stablelib/hkdf@npm:1.0.1": - version: 1.0.1 - resolution: "@stablelib/hkdf@npm:1.0.1" - dependencies: - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/hmac": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 722d30e36afa8029fda2a9e8c65ad753deff92a234e708820f9fd39309d2494e1c035a4185f29ae8d7fbf8a74862b27128c66a1fb4bd7a792bd300190080dbe9 - languageName: node - linkType: hard - -"@stablelib/hmac@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/hmac@npm:1.0.1" - dependencies: - "@stablelib/constant-time": "npm:^1.0.1" - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: a111d5e687966b62c81f7dbd390f13582b027edee9bd39df6474a6472e5ad89d705e735af32bae2c9280a205806649f54b5ff8c4e8c8a7b484083a35b257e9e6 - languageName: node - linkType: hard - -"@stablelib/int@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/int@npm:1.0.1" - checksum: e1a6a7792fc2146d65de56e4ef42e8bc385dd5157eff27019b84476f564a1a6c43413235ed0e9f7c9bb8907dbdab24679467aeb10f44c92e6b944bcd864a7ee0 - languageName: node - linkType: hard - -"@stablelib/keyagreement@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/keyagreement@npm:1.0.1" - dependencies: - "@stablelib/bytes": "npm:^1.0.1" - checksum: 18c9e09772a058edee265c65992ec37abe4ab5118171958972e28f3bbac7f2a0afa6aaf152ec1d785452477bdab5366b3f5b750e8982ae9ad090f5fa2e5269ba - languageName: node - linkType: hard - -"@stablelib/poly1305@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/poly1305@npm:1.0.1" - dependencies: - "@stablelib/constant-time": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 080185ffa92f5111e6ecfeab7919368b9984c26d048b9c09a111fbc657ea62bb5dfe6b56245e1804ce692a445cc93ab6625936515fa0e7518b8f2d86feda9630 - languageName: node - linkType: hard - -"@stablelib/random@npm:1.0.2, @stablelib/random@npm:^1.0.1, @stablelib/random@npm:^1.0.2": - version: 1.0.2 - resolution: "@stablelib/random@npm:1.0.2" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: ebb217cfb76db97d98ec07bd7ce03a650fa194b91f0cb12382738161adff1830f405de0e9bad22bbc352422339ff85f531873b6a874c26ea9b59cfcc7ea787e0 - languageName: node - linkType: hard - -"@stablelib/sha256@npm:1.0.1": - version: 1.0.1 - resolution: "@stablelib/sha256@npm:1.0.1" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: e29ee9bc76eece4345e9155ce4bdeeb1df8652296be72bd2760523ad565e3b99dca85b81db3b75ee20b34837077eb8542ca88f153f162154c62ba1f75aecc24a - languageName: node - linkType: hard - -"@stablelib/sha512@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/sha512@npm:1.0.1" - dependencies: - "@stablelib/binary": "npm:^1.0.1" - "@stablelib/hash": "npm:^1.0.1" - "@stablelib/wipe": "npm:^1.0.1" - checksum: 84549070a383f4daf23d9065230eb81bc8f590c68bf5f7968f1b78901236b3bb387c14f63773dc6c3dc78e823b1c15470d2a04d398a2506391f466c16ba29b58 - languageName: node - linkType: hard - -"@stablelib/wipe@npm:^1.0.1": - version: 1.0.1 - resolution: "@stablelib/wipe@npm:1.0.1" - checksum: c5a54f769c286a5b3ecff979471dfccd4311f2e84a959908e8c0e3aa4eed1364bd9707f7b69d1384b757e62cc295c221fa27286c7f782410eb8a690f30cfd796 - languageName: node - linkType: hard - -"@stablelib/x25519@npm:1.0.3": - version: 1.0.3 - resolution: "@stablelib/x25519@npm:1.0.3" - dependencies: - "@stablelib/keyagreement": "npm:^1.0.1" - "@stablelib/random": "npm:^1.0.2" - "@stablelib/wipe": "npm:^1.0.1" - checksum: d8afe8a120923a434359d7d1c6759780426fed117a84a6c0f84d1a4878834cb4c2d7da78a1fa7cf227ce3924fdc300cd6ed6e46cf2508bf17b1545c319ab8418 - languageName: node - linkType: hard - "@storybook/addon-actions@npm:8.3.0": version: 8.3.0 resolution: "@storybook/addon-actions@npm:8.3.0" @@ -9365,30 +9236,30 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.7.9": - version: 5.7.9 - resolution: "@wagmi/connectors@npm:5.7.9" +"@wagmi/connectors@npm:5.8.0": + version: 5.8.0 + resolution: "@wagmi/connectors@npm:5.8.0" dependencies: "@coinbase/wallet-sdk": "npm:4.3.0" "@metamask/sdk": "npm:0.32.0" - "@safe-global/safe-apps-provider": "npm:0.18.5" + "@safe-global/safe-apps-provider": "npm:0.18.6" "@safe-global/safe-apps-sdk": "npm:9.1.0" - "@walletconnect/ethereum-provider": "npm:2.19.0" + "@walletconnect/ethereum-provider": "npm:2.20.0" cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.16.5 + "@wagmi/core": 2.17.0 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 28c50b6fe52a131418eebed01c0219b4583a06580d0bec7147adfa7c09bf0bb9b67d3dab43aae57febf68f057b72fdb0c08055c2fe016c0f2ba2ee99d85c525c + checksum: 620f668843e8799dd990d3f1c1645462f04f6ddb96b958e6d449f0a3c7ca05330ba44be2551238fec8b3f6a11f0b1967cb246d31669b6c504535f268b6641178 languageName: node linkType: hard -"@wagmi/core@npm:2.16.5": - version: 2.16.5 - resolution: "@wagmi/core@npm:2.16.5" +"@wagmi/core@npm:2.17.0": + version: 2.17.0 + resolution: "@wagmi/core@npm:2.17.0" dependencies: eventemitter3: "npm:5.0.1" mipd: "npm:0.0.7" @@ -9402,38 +9273,13 @@ __metadata: optional: true typescript: optional: true - checksum: 3c58155071fa2aa8a2941f2e4e80c68958f78f6d8a3462112d772ccf7e030261ec1b641243aeab2b21df0d4e89b583321c1771efe18b23b58357b64918b1c801 + checksum: cf691f134b3335302f3230bca064b587b5b085b36b6bc6f0a96e32888b7f5220fe4def2b0727fddef0f07fd11c0241ccd352980ae761cd0fa8c9010315621739 languageName: node linkType: hard -"@walletconnect/core@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/core@npm:2.17.3" - dependencies: - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/jsonrpc-ws-connection": "npm:1.0.16" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/relay-api": "npm:1.0.11" - "@walletconnect/relay-auth": "npm:1.0.4" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" - "@walletconnect/window-getters": "npm:1.0.1" - events: "npm:3.3.0" - lodash.isequal: "npm:4.5.0" - uint8arrays: "npm:3.1.0" - checksum: e6a841a0d5b27922b83fbb7a1dbcb519b825d70489f9bd6a909cf0b3c543ab3a6c209a0775a95c5dc452a875757f04c9ca27d02c6f002c39974d2ce2061e5887 - languageName: node - linkType: hard - -"@walletconnect/core@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/core@npm:2.19.0" +"@walletconnect/core@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/core@npm:2.20.2" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -9446,38 +9292,13 @@ __metadata: "@walletconnect/relay-auth": "npm:1.1.0" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" - "@walletconnect/window-getters": "npm:1.0.1" - events: "npm:3.3.0" - lodash.isequal: "npm:4.5.0" - uint8arrays: "npm:3.1.0" - checksum: c0ac9eeb576af7ed31edbfff10fcd80d2917c03e6962747977c31ec7ce0734c9dc26c7c2481e975f42615749c8e34122fe1dc81ec3d361573ae85fee3185121e - languageName: node - linkType: hard - -"@walletconnect/core@npm:2.19.2": - version: 2.19.2 - resolution: "@walletconnect/core@npm:2.19.2" - dependencies: - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/jsonrpc-ws-connection": "npm:1.0.16" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/relay-api": "npm:1.0.11" - "@walletconnect/relay-auth": "npm:1.1.0" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.2" - "@walletconnect/utils": "npm:2.19.2" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" "@walletconnect/window-getters": "npm:1.0.1" es-toolkit: "npm:1.33.0" events: "npm:3.3.0" uint8arrays: "npm:3.1.0" - checksum: 6ebc3c192fb667d4cbaa435c7391fd21b857508f0e3a43cf2c1fb10626dbe0ef374e01988330916dbeb8ae2fcaac4f56881af482dc37f4b1d1d39e63feb0aed3 + checksum: 2ed3737b4cfc22df5fbca5d8c551f82eb5811865300f8990ee5b035fde0e90894c0a63172b021736ebc97ed7913f39e89e1c87bfaccae34db18eb5bf4dd4fd92 languageName: node linkType: hard @@ -9490,41 +9311,22 @@ __metadata: languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/ethereum-provider@npm:2.17.3" +"@walletconnect/ethereum-provider@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/ethereum-provider@npm:2.20.2" dependencies: + "@reown/appkit": "npm:1.7.3" "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/modal": "npm:2.7.0" - "@walletconnect/sign-client": "npm:2.17.3" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/universal-provider": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" + "@walletconnect/sign-client": "npm:2.20.2" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/universal-provider": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" events: "npm:3.3.0" - checksum: 6ca5aaf5f72dfe0c8edd54f4bd30a55ee22e28cf766a6fe1052a22ad252f0aab4d41c9e105b97e1a4ce29f25fbb8aaed3081a447ecb1759664306b4725948774 - languageName: node - linkType: hard - -"@walletconnect/ethereum-provider@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/ethereum-provider@npm:2.19.0" - dependencies: - "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/modal": "npm:2.7.0" - "@walletconnect/sign-client": "npm:2.19.0" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/universal-provider": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" - events: "npm:3.3.0" - checksum: a7f356c469bdd8ba68a037a3facbc60580a702a9615d37475e2d996c4ecb373da42e42fb93ad3129e23e0d93e94043f7f41474caea7279c89f68dfd4d3d98b17 + checksum: e1a809f91abef108cec52621144bd48adc9408db11f5b741c2607a1ca6abfbb81f6120f68b4be2728733168ce653ffc68689d8e067ec4fb12d9c53ff0f872420 languageName: node linkType: hard @@ -9641,40 +9443,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal-core@npm:2.7.0": - version: 2.7.0 - resolution: "@walletconnect/modal-core@npm:2.7.0" - dependencies: - valtio: "npm:1.11.2" - checksum: 84b11735c005e37e661aa0f08b2e8c8098db3b2cacd957c4a73f4d3de11b2d5e04dd97ab970f8d22fc3e8269fea3297b9487e177343bbab8dd69b3b917fb7f60 - languageName: node - linkType: hard - -"@walletconnect/modal-ui@npm:2.7.0": - version: 2.7.0 - resolution: "@walletconnect/modal-ui@npm:2.7.0" - dependencies: - "@walletconnect/modal-core": "npm:2.7.0" - lit: "npm:2.8.0" - motion: "npm:10.16.2" - qrcode: "npm:1.5.3" - checksum: b717f1fc9854b7d14a4364720fce2d44167f547533340704644ed2fdf9d861b3798ffd19a3b51062a366a8bc39f84b9a8bb3dd04e9e33da742192359be00b051 - languageName: node - linkType: hard - -"@walletconnect/modal@npm:2.7.0": - version: 2.7.0 - resolution: "@walletconnect/modal@npm:2.7.0" - dependencies: - "@walletconnect/modal-core": "npm:2.7.0" - "@walletconnect/modal-ui": "npm:2.7.0" - checksum: 2f3074eebbca41a46e29680dc2565bc762133508774f05db0075a82b0b66ecc8defca40a94ad63669676090a7e3ef671804592b10e91636ab1cdeac014a1eb11 - languageName: node - linkType: hard - -"@walletconnect/react-native-compat@npm:2.19.1": - version: 2.19.1 - resolution: "@walletconnect/react-native-compat@npm:2.19.1" +"@walletconnect/react-native-compat@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/react-native-compat@npm:2.20.2" dependencies: events: "npm:3.3.0" fast-text-encoding: "npm:1.0.6" @@ -9688,7 +9459,7 @@ __metadata: peerDependenciesMeta: expo-application: optional: true - checksum: d8ae5291c47d277efd725d08b359a634ca5abd13929632edcda15a0e5185873d2d48935f624744703eebe78b39140795a2befc6cfd541e40bba8f5be5b23c915 + checksum: 09eb1ee3861b639ad2a5c5064d35eb19398855a29625f8f2ed60dbfd2eda2d0d663044e7fbe15c351839b9ed188b89488d29de54a484b2b79e4c74307125e739 languageName: node linkType: hard @@ -9701,20 +9472,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/relay-auth@npm:1.0.4": - version: 1.0.4 - resolution: "@walletconnect/relay-auth@npm:1.0.4" - dependencies: - "@stablelib/ed25519": "npm:^1.0.2" - "@stablelib/random": "npm:^1.0.1" - "@walletconnect/safe-json": "npm:^1.0.1" - "@walletconnect/time": "npm:^1.0.2" - tslib: "npm:1.14.1" - uint8arrays: "npm:^3.0.0" - checksum: e90294ff718c5c1e49751a28916aaac45dd07d694f117052506309eb05b68cc2c72d9b302366e40d79ef952c22bd0bbea731d09633a6663b0ab8e18b4804a832 - languageName: node - linkType: hard - "@walletconnect/relay-auth@npm:1.1.0": version: 1.1.0 resolution: "@walletconnect/relay-auth@npm:1.1.0" @@ -9737,54 +9494,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/sign-client@npm:2.17.3" - dependencies: - "@walletconnect/core": "npm:2.17.3" - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" - events: "npm:3.3.0" - checksum: 454afa3c933ec11f651c4cd275af88eef7da65b5d4bcf8987f768f340557492cf436d662ca42baa54ad8136e4b16f5269e0bc3e212580df09e0ee49873718b96 - languageName: node - linkType: hard - -"@walletconnect/sign-client@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/sign-client@npm:2.19.0" +"@walletconnect/sign-client@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/sign-client@npm:2.20.2" dependencies: - "@walletconnect/core": "npm:2.19.0" + "@walletconnect/core": "npm:2.20.2" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" events: "npm:3.3.0" - checksum: 0364d8f1ae4cfa08a598623f4b5a9c70c6c4b10ba8266eb57f272a90ed590f3fb5a6feeba4082461c1e9e0fe38652a51e8078f7691b70267683bb2299e901ae9 - languageName: node - linkType: hard - -"@walletconnect/sign-client@npm:2.19.2": - version: 2.19.2 - resolution: "@walletconnect/sign-client@npm:2.19.2" - dependencies: - "@walletconnect/core": "npm:2.19.2" - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.2" - "@walletconnect/utils": "npm:2.19.2" - events: "npm:3.3.0" - checksum: 9d26928d3f52b068362e271ea4ffafb23bb077e265a792e420c1045bb38137a53681b82003e6a04108b4ba1a2ac183b759d42deaf9f4e0f3c9aabb1b0b632567 + checksum: 12c27037591179553b9d5fc374dfe9980c10a04ec6d5d8418ebfd7e72c466577e88295b209da9fd429c648a7ee8a8cc64a9e1a2c320e8aa55f21e65b85714e31 languageName: node linkType: hard @@ -9797,34 +9520,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/types@npm:2.17.3" - dependencies: - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - events: "npm:3.3.0" - checksum: 6e50f1f3d64f32d0fa697bb61340191b153aa0a77b8a483cacaeb62aefa190524e10f78188260b591eaae877d6bfa5ea9ffab5ed905c286151300577f2e0101f - languageName: node - linkType: hard - -"@walletconnect/types@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/types@npm:2.19.0" - dependencies: - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/heartbeat": "npm:1.2.2" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - events: "npm:3.3.0" - checksum: 16e6006ba27a75b0e7d1cd120a275eb10c3493bacf8205808462dfb369b4b97b652f776bff35bf6da7087fd0ef67af401e40aedd4c986ee4b17864e85fba2ee6 - languageName: node - linkType: hard - "@walletconnect/types@npm:2.19.2": version: 2.19.2 resolution: "@walletconnect/types@npm:2.19.2" @@ -9839,49 +9534,23 @@ __metadata: languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/universal-provider@npm:2.17.3" +"@walletconnect/types@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/types@npm:2.20.2" dependencies: "@walletconnect/events": "npm:1.0.1" - "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" - "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.17.3" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/utils": "npm:2.17.3" - events: "npm:3.3.0" - lodash: "npm:4.17.21" - checksum: a577099e5b40fc254df56f9fa3335ff064af24804ec7db9e213ef74261076b2e92194251f56f44de3a7d980deb7cef14f76ca961399e6f6671d1a7dccbdea8d9 - languageName: node - linkType: hard - -"@walletconnect/universal-provider@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/universal-provider@npm:2.19.0" - dependencies: - "@walletconnect/events": "npm:1.0.1" - "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" - "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-types": "npm:1.0.4" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.19.0" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/utils": "npm:2.19.0" events: "npm:3.3.0" - lodash: "npm:4.17.21" - checksum: 9c473c925cfe172397f85995c895d058e07ea6eb41060f7aafb0d2c6eac22afcccf726f5ee2267bec04dc7dc98758a2e2ddf2870c1767603b3d6d1136655589f + checksum: 6695cc03a68aa66692000373f44a2844cb6b782748524c5ea6ac7c64a2a133558579a7f0d4300b2d0b1210ada40119d328f7734a9da163f65356148313f3ae18 languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.19.2": - version: 2.19.2 - resolution: "@walletconnect/universal-provider@npm:2.19.2" +"@walletconnect/universal-provider@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/universal-provider@npm:2.20.2" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" @@ -9890,46 +9559,18 @@ __metadata: "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.19.2" - "@walletconnect/types": "npm:2.19.2" - "@walletconnect/utils": "npm:2.19.2" + "@walletconnect/sign-client": "npm:2.20.2" + "@walletconnect/types": "npm:2.20.2" + "@walletconnect/utils": "npm:2.20.2" es-toolkit: "npm:1.33.0" events: "npm:3.3.0" - checksum: e4d64e5e95ee56a0babe62242c636d1bc691ee9acd2d46c1632117bf88ec0f48387224493387c3d397f2e0c030d2c64385f592ad0e92d8f4a50406058697ddb5 + checksum: d5876a490bfc207f00a0573aea129c153a959dbea33243efc71180129ba6f4d9bd103773f37759b72d8a27d30053fdfda7f4b58254ed1b663555809fc86dc685 languageName: node linkType: hard -"@walletconnect/utils@npm:2.17.3": - version: 2.17.3 - resolution: "@walletconnect/utils@npm:2.17.3" - dependencies: - "@ethersproject/hash": "npm:5.7.0" - "@ethersproject/transactions": "npm:5.7.0" - "@stablelib/chacha20poly1305": "npm:1.0.1" - "@stablelib/hkdf": "npm:1.0.1" - "@stablelib/random": "npm:1.0.2" - "@stablelib/sha256": "npm:1.0.1" - "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/relay-api": "npm:1.0.11" - "@walletconnect/relay-auth": "npm:1.0.4" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.3" - "@walletconnect/window-getters": "npm:1.0.1" - "@walletconnect/window-metadata": "npm:1.0.1" - detect-browser: "npm:5.3.0" - elliptic: "npm:6.6.1" - query-string: "npm:7.1.3" - uint8arrays: "npm:3.1.0" - checksum: ab08f625786eb55e0ae41075a3ccee9804750b1f20745f2d7a81569a6741d022463b250958124925e6b5f51d3a5b3ec783a23233391d8d937c4bcd76e7a8cc8c - languageName: node - linkType: hard - -"@walletconnect/utils@npm:2.19.0": - version: 2.19.0 - resolution: "@walletconnect/utils@npm:2.19.0" +"@walletconnect/utils@npm:2.20.2": + version: 2.20.2 + resolution: "@walletconnect/utils@npm:2.20.2" dependencies: "@noble/ciphers": "npm:1.2.1" "@noble/curves": "npm:1.8.1" @@ -9940,32 +9581,7 @@ __metadata: "@walletconnect/relay-auth": "npm:1.1.0" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.0" - "@walletconnect/window-getters": "npm:1.0.1" - "@walletconnect/window-metadata": "npm:1.0.1" - detect-browser: "npm:5.3.0" - elliptic: "npm:6.6.1" - query-string: "npm:7.1.3" - uint8arrays: "npm:3.1.0" - viem: "npm:2.23.2" - checksum: 80b2b8ff925670764561f1b4cc006915bf173237058488e0a94c62a4f3ab9071a118f699ee3016e56d22ed7dee5f84cd625e0b183330123f8b65e1a30f0a9571 - languageName: node - linkType: hard - -"@walletconnect/utils@npm:2.19.2": - version: 2.19.2 - resolution: "@walletconnect/utils@npm:2.19.2" - dependencies: - "@noble/ciphers": "npm:1.2.1" - "@noble/curves": "npm:1.8.1" - "@noble/hashes": "npm:1.7.1" - "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/keyvaluestorage": "npm:1.1.1" - "@walletconnect/relay-api": "npm:1.0.11" - "@walletconnect/relay-auth": "npm:1.1.0" - "@walletconnect/safe-json": "npm:1.0.2" - "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.19.2" + "@walletconnect/types": "npm:2.20.2" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" bs58: "npm:6.0.0" @@ -9973,7 +9589,7 @@ __metadata: query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" viem: "npm:2.23.2" - checksum: 21eca1f5b94bfe90d329285388b9676de6f4f0a60dbf12b68d76448df24ef707b5ee0000a4aa38843baee14d79e2f6a7e15aa371d50eadf96f925ffdd1c36ac1 + checksum: 114e9da7a5b477bc845aea4c18a4b7ad4cac45e89bf3cf6a35eba38f4435303ceb7e024b7d283fc67ff24a2b5b9fe7a3f2ac537c05c5a53e249dee611168a2b1 languageName: node linkType: hard @@ -10547,7 +10163,7 @@ __metadata: "@types/jest": "npm:29.5.7" "@types/qrcode": "npm:1.5.5" "@types/react": "npm:18.2.79" - "@walletconnect/react-native-compat": "npm:2.19.1" + "@walletconnect/react-native-compat": "npm:2.20.2" babel-jest: "npm:^29.7.0" eslint: "npm:^8.46.0" eslint-plugin-ft-flow: "npm:2.0.3" @@ -10568,8 +10184,8 @@ __metadata: tsconfig: "npm:*" turbo: "npm:2.1.1" typescript: "npm:5.2.2" - viem: "npm:2.23.10" - wagmi: "npm:2.14.13" + viem: "npm:2.28.3" + wagmi: "npm:2.15.1" languageName: unknown linkType: soft @@ -11195,6 +10811,13 @@ __metadata: languageName: node linkType: hard +"big.js@npm:6.2.2": + version: 6.2.2 + resolution: "big.js@npm:6.2.2" + checksum: 58d204f6a1a92508dc2eb98d964e2cc6dabb37a3d9fc8a1f0b77a34dead7c11e17b173d9a6df2d5a7a0f78d5c80853a9ce6df29852da59ab10b088e981195165 + languageName: node + linkType: hard + "bignumber.js@npm:9.1.2": version: 9.1.2 resolution: "bignumber.js@npm:9.1.2" @@ -12615,6 +12238,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:1.11.13": + version: 1.11.13 + resolution: "dayjs@npm:1.11.13" + checksum: a3caf6ac8363c7dade9d1ee797848ddcf25c1ace68d9fe8678ecf8ba0675825430de5d793672ec87b24a69bf04a1544b176547b2539982275d5542a7955f35b7 + languageName: node + linkType: hard + "dayjs@npm:^1.8.15": version: 1.11.9 resolution: "dayjs@npm:1.11.9" @@ -15725,13 +15355,6 @@ __metadata: languageName: node linkType: hard -"hey-listen@npm:^1.0.8": - version: 1.0.8 - resolution: "hey-listen@npm:1.0.8" - checksum: 38db3028b4756f3d536c0f6a92da53bad577ab649b06dddfd0a4d953f9a46bbc6a7f693c8c5b466a538d6d23dbc469260c848427f0de14198a2bbecbac37b39e - languageName: node - linkType: hard - "hmac-drbg@npm:^1.0.1": version: 1.0.1 resolution: "hmac-drbg@npm:1.0.1" @@ -17733,34 +17356,34 @@ __metadata: languageName: node linkType: hard -"lit-element@npm:^3.3.0": - version: 3.3.3 - resolution: "lit-element@npm:3.3.3" +"lit-element@npm:^4.0.0": + version: 4.2.0 + resolution: "lit-element@npm:4.2.0" dependencies: - "@lit-labs/ssr-dom-shim": "npm:^1.1.0" - "@lit/reactive-element": "npm:^1.3.0" - lit-html: "npm:^2.8.0" - checksum: f44c12fa3423a4e9ca5b84651410687e14646bb270ac258325e6905affac64a575f041f8440377e7ebaefa3910b6f0d6b8b1e902cb1aa5d0849b3fdfbf4fb3b6 + "@lit-labs/ssr-dom-shim": "npm:^1.2.0" + "@lit/reactive-element": "npm:^2.1.0" + lit-html: "npm:^3.3.0" + checksum: 20577f2092ac1e1bd82fba2bbc9ce0122b35dc2495906d3fbcb437c3727b9c8ed1c0691b8b859f65a51e910db1341d95233c117e1e1c88c450b30e2d3b62fdb8 languageName: node linkType: hard -"lit-html@npm:^2.8.0": - version: 2.8.0 - resolution: "lit-html@npm:2.8.0" +"lit-html@npm:^3.1.0, lit-html@npm:^3.3.0": + version: 3.3.0 + resolution: "lit-html@npm:3.3.0" dependencies: "@types/trusted-types": "npm:^2.0.2" - checksum: 90057dee050803823ac884c1355b0213ab8c05fbe2ec63943c694b61aade5d36272068f3925f45a312835e504f9c9784738ef797009f0a756a750351eafb52d5 + checksum: c1065048d89d93df6a46cdeed9abd637ae9bcc0847ee108dccbb2e1627a4074074e1d3ac9360e08a736d76f8c76b2c88166dbe465406da123b9137e29c2e0034 languageName: node linkType: hard -"lit@npm:2.8.0": - version: 2.8.0 - resolution: "lit@npm:2.8.0" +"lit@npm:3.1.0": + version: 3.1.0 + resolution: "lit@npm:3.1.0" dependencies: - "@lit/reactive-element": "npm:^1.6.0" - lit-element: "npm:^3.3.0" - lit-html: "npm:^2.8.0" - checksum: bf33c26b1937ee204aed1adbfa4b3d43a284e85aad8ea9763c7865365917426eded4e5888158b4136095ea42054812561fe272862b61775f1198fad3588b071f + "@lit/reactive-element": "npm:^2.0.0" + lit-element: "npm:^4.0.0" + lit-html: "npm:^3.1.0" + checksum: 7ca12c1b1593373d16b51b2220677d8936b4061de4f278ef2a85f15726bb4365a8eed89a0294816a10d6124dca81f02e83b5dfed9a6031e135a7bc68924eea6b languageName: node linkType: hard @@ -17841,13 +17464,6 @@ __metadata: languageName: node linkType: hard -"lodash.isequal@npm:4.5.0": - version: 4.5.0 - resolution: "lodash.isequal@npm:4.5.0" - checksum: dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f - languageName: node - linkType: hard - "lodash.memoize@npm:4.x": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -17876,7 +17492,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.20, lodash@npm:^4.17.21": +"lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -18970,20 +18586,6 @@ __metadata: languageName: node linkType: hard -"motion@npm:10.16.2": - version: 10.16.2 - resolution: "motion@npm:10.16.2" - dependencies: - "@motionone/animation": "npm:^10.15.1" - "@motionone/dom": "npm:^10.16.2" - "@motionone/svelte": "npm:^10.16.2" - "@motionone/types": "npm:^10.15.1" - "@motionone/utils": "npm:^10.15.1" - "@motionone/vue": "npm:^10.16.2" - checksum: ea3fa2c7ce881824bcefa39b96b5e2b802d4b664b8a64644cded11197c9262e2a5b14b2e9516940e06cec37d3c39e4c79b26825c447f71ba1cfd7e3370efbe61 - languageName: node - linkType: hard - "mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" @@ -23220,13 +22822,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.3.1": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb - languageName: node - linkType: hard - "tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" @@ -24000,7 +23595,7 @@ __metadata: languageName: node linkType: hard -"valtio@npm:^1.13.2": +"valtio@npm:1.13.2, valtio@npm:^1.13.2": version: 1.13.2 resolution: "valtio@npm:1.13.2" dependencies: @@ -24026,9 +23621,9 @@ __metadata: languageName: node linkType: hard -"viem@npm:2.23.10": - version: 2.23.10 - resolution: "viem@npm:2.23.10" +"viem@npm:2.23.2": + version: 2.23.2 + resolution: "viem@npm:2.23.2" dependencies: "@noble/curves": "npm:1.8.1" "@noble/hashes": "npm:1.7.1" @@ -24036,35 +23631,35 @@ __metadata: "@scure/bip39": "npm:1.5.4" abitype: "npm:1.0.8" isows: "npm:1.0.6" - ox: "npm:0.6.9" - ws: "npm:8.18.1" + ox: "npm:0.6.7" + ws: "npm:8.18.0" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 85f2f36ca586c5f6a0c50a052170b5c7a1a07fa7336fd0a1daa1fc627d9724202c66b3ddb7ccc76d6eb0f8402210ed154a74db942122cee44b1da778b366e07c + checksum: 39332d008d2ab0700aa57f541bb199350daecdfb722ae1b262404b02944e11205368fcc696cc0ab8327b9f90bf7172014687ae3e5d9091978e9d174885ccff2d languageName: node linkType: hard -"viem@npm:2.23.2": - version: 2.23.2 - resolution: "viem@npm:2.23.2" +"viem@npm:2.28.3, viem@npm:>=2.23.11": + version: 2.28.3 + resolution: "viem@npm:2.28.3" dependencies: - "@noble/curves": "npm:1.8.1" - "@noble/hashes": "npm:1.7.1" + "@noble/curves": "npm:1.8.2" + "@noble/hashes": "npm:1.7.2" "@scure/bip32": "npm:1.6.2" "@scure/bip39": "npm:1.5.4" abitype: "npm:1.0.8" isows: "npm:1.0.6" - ox: "npm:0.6.7" - ws: "npm:8.18.0" + ox: "npm:0.6.9" + ws: "npm:8.18.1" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 39332d008d2ab0700aa57f541bb199350daecdfb722ae1b262404b02944e11205368fcc696cc0ab8327b9f90bf7172014687ae3e5d9091978e9d174885ccff2d + checksum: d403b03d464ddae8a121c092e96371ede39ce393caefe1b89b2f34d39c1e7751ab56e20b89da9598d9ac52ee337d340e2f41344ae74f77444978f8ed24a75775 languageName: node linkType: hard @@ -24097,12 +23692,12 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:2.14.13": - version: 2.14.13 - resolution: "wagmi@npm:2.14.13" +"wagmi@npm:2.15.1": + version: 2.15.1 + resolution: "wagmi@npm:2.15.1" dependencies: - "@wagmi/connectors": "npm:5.7.9" - "@wagmi/core": "npm:2.16.5" + "@wagmi/connectors": "npm:5.8.0" + "@wagmi/core": "npm:2.17.0" use-sync-external-store: "npm:1.4.0" peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -24112,7 +23707,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 0f833c13020641a57b8a8f96bdf8f7ed44f9985e1263f59b82f8b85aa4ee9c8d10950dbc1426ab7fb5d2860f31dde1604ab38d40225bfc6b9d2d003b6294fc06 + checksum: 117a66fc132b68f25cc936058f419eb3d153d91424403e1db6622fa56b01370bb51f7b742f3e5f66466b78f26008198752d759ebc2005462604daaa31c88a39c languageName: node linkType: hard From 112d66544ac106546a579815075ba9b7318bd178 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 2 May 2025 17:01:46 -0300 Subject: [PATCH 12/91] chore: solved issue with balance --- apps/native/src/views/ActionsView.tsx | 6 +++++- packages/appkit/src/AppKit.ts | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index 1b8af8ce..04d04e93 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -11,7 +11,11 @@ export function ActionsView() { return isConnected ? ( - {chainId?.startsWith('eip155') ? : } + {chainId?.startsWith('eip155') ? ( + + ) : chainId?.startsWith('solana') ? ( + + ) : null} ) : null; } diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index fa7bb9e0..4fabca54 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -188,13 +188,15 @@ export class AppKit { } private async syncAccounts(adapters: BlockchainAdapter[]) { - // Get account balance + // Get account balances adapters.map(adapter => { const namespace = adapter.getSupportedNamespace(); const connection = ConnectionsController.state.connections[namespace]; + const network = this.networks.find( - n => n.id === Number(connection?.activeChain?.split(':')[1]) + n => n.id?.toString() === connection?.activeChain?.split(':')[1] ); + adapter.getBalance({ address: adapter.getAccounts()?.[0], network }); }); } From 11f1071728f99d7e79ee3d5efd4e3ca946a342f8 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 5 May 2025 14:34:43 -0300 Subject: [PATCH 13/91] chore: improvements and added bitcoin basic adapter --- apps/native/App.tsx | 20 +- apps/native/package.json | 4 + apps/native/src/utils/BitcoinUtil.ts | 206 ++++++++++++++++++ apps/native/src/utils/ChainUtils.ts | 16 -- apps/native/src/views/ActionsView.tsx | 4 +- apps/native/src/views/BitcoinActionsView.tsx | 128 +++++++++++ apps/native/src/views/EthersActionsView.tsx | 3 +- apps/native/src/views/SolanaActionsView.tsx | 4 +- package.json | 1 + packages/appkit/src/AppKit.ts | 3 +- .../src/connectors/WalletConnectConnector.ts | 11 +- packages/appkit/src/index.ts | 1 + packages/appkit/src/networks/bitcoin.ts | 32 +++ packages/appkit/src/networks/index.ts | 6 + packages/appkit/src/networks/solana.ts | 44 ++++ packages/appkit/src/utils/HelpersUtil.ts | 1 + packages/appkit/src/utils/NetworkUtil.ts | 39 ++++ .../views/w3m-account-default-view/index.tsx | 2 +- packages/bitcoin/.eslintignore | 2 + packages/bitcoin/.eslintrc.json | 3 + packages/bitcoin/.npmignore | 10 + packages/bitcoin/bob.config.js | 14 ++ packages/bitcoin/package.json | 54 +++++ packages/bitcoin/readme.md | 9 + packages/bitcoin/src/adapter.ts | 144 ++++++++++++ packages/bitcoin/src/index.tsx | 2 + packages/bitcoin/src/utils/BitcoinApi.ts | 41 ++++ packages/bitcoin/src/utils/UnitsUtil.ts | 11 + packages/bitcoin/tsconfig.json | 5 + packages/common/src/utils/TypeUtil.ts | 8 +- packages/ethers/src/adapter.ts | 4 +- packages/solana/package.json | 7 +- packages/solana/src/adapter.ts | 7 +- yarn.lock | 112 +++++++++- 34 files changed, 915 insertions(+), 43 deletions(-) create mode 100644 apps/native/src/utils/BitcoinUtil.ts delete mode 100644 apps/native/src/utils/ChainUtils.ts create mode 100644 apps/native/src/views/BitcoinActionsView.tsx create mode 100644 packages/appkit/src/networks/bitcoin.ts create mode 100644 packages/appkit/src/networks/index.ts create mode 100644 packages/appkit/src/networks/solana.ts create mode 100644 packages/appkit/src/utils/NetworkUtil.ts create mode 100644 packages/bitcoin/.eslintignore create mode 100644 packages/bitcoin/.eslintrc.json create mode 100644 packages/bitcoin/.npmignore create mode 100644 packages/bitcoin/bob.config.js create mode 100644 packages/bitcoin/package.json create mode 100644 packages/bitcoin/readme.md create mode 100644 packages/bitcoin/src/adapter.ts create mode 100644 packages/bitcoin/src/index.tsx create mode 100644 packages/bitcoin/src/utils/BitcoinApi.ts create mode 100644 packages/bitcoin/src/utils/UnitsUtil.ts create mode 100644 packages/bitcoin/tsconfig.json diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 9cd7d43f..dbfe8097 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -15,7 +15,15 @@ import Toast from 'react-native-toast-message'; // defaultWagmiConfig // } from '@reown/appkit-wagmi-react-native'; -import { AppKitProvider, createAppKit, AppKit, AppKitButton } from '@reown/appkit-react-native'; +import { + AppKitProvider, + createAppKit, + AppKit, + AppKitButton, + solana, + bitcoin, + bitcoinTestnet +} from '@reown/appkit-react-native'; // import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; import { Button, Text } from '@reown/appkit-ui-react-native'; @@ -29,8 +37,8 @@ import { Button, Text } from '@reown/appkit-ui-react-native'; // import { DisconnectButton } from './src/components/DisconnectButton'; import { EthersAdapter } from '@reown/appkit-ethers-react-native'; import { SolanaAdapter } from '@reown/appkit-solana-react-native'; +import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; import { mainnet, polygon, avalanche } from 'viem/chains'; -import { solana } from './src/utils/ChainUtils'; import { ActionsView } from './src/views/ActionsView'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -84,6 +92,10 @@ const solanaAdapter = new SolanaAdapter({ projectId }); +const bitcoinAdapter = new BitcoinAdapter({ + projectId +}); + // createAppKit({ // projectId, // wagmiConfig, @@ -104,9 +116,9 @@ const solanaAdapter = new SolanaAdapter({ const appKit = createAppKit({ projectId, - adapters: [ethersAdapter, solanaAdapter], + adapters: [ethersAdapter, solanaAdapter, bitcoinAdapter], metadata, - networks: [mainnet, polygon, avalanche, solana] + networks: [mainnet, polygon, avalanche, solana, bitcoin, bitcoinTestnet] }); export default function Native() { diff --git a/apps/native/package.json b/apps/native/package.json index 1ceb3978..cb883cb5 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -19,17 +19,21 @@ "build:web": "expo export -p web" }, "dependencies": { + "@bitcoinerlab/secp256k1": "1.2.0", "@expo/metro-runtime": "~4.0.1", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", "@reown/appkit-auth-wagmi-react-native": "1.2.3", + "@reown/appkit-bitcoin-react-native": "workspace:*", "@reown/appkit-ethers-react-native": "workspace:*", "@reown/appkit-react-native": "workspace:*", + "@reown/appkit-solana-react-native": "workspace:*", "@reown/appkit-wagmi-react-native": "1.2.3", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-query-persist-client": "5.56.2", "@walletconnect/react-native-compat": "2.20.2", + "bitcoinjs-lib": "7.0.0-rc.0", "ethers": "6.13.5", "expo": "^52.0.38", "expo-application": "~6.0.2", diff --git a/apps/native/src/utils/BitcoinUtil.ts b/apps/native/src/utils/BitcoinUtil.ts new file mode 100644 index 00000000..7a3718bf --- /dev/null +++ b/apps/native/src/utils/BitcoinUtil.ts @@ -0,0 +1,206 @@ +import ecc from '@bitcoinerlab/secp256k1'; +import * as bitcoin from 'bitcoinjs-lib'; + +import * as bitcoinPSBTUtils from 'bitcoinjs-lib/src/cjs/psbt/psbtutils'; + +import type { CaipNetworkId } from '@reown/appkit'; +import { bitcoinTestnet as bitcoinTestnetNetwork } from '@reown/appkit-react-native'; + +bitcoin.initEccLib(ecc); + +export type SignPSBTResponse = { + /** + * The signed PSBT, string base64 encoded + */ + psbt: string; + /** + * The `string` transaction id of the broadcasted transaction or `undefined` if not broadcasted + */ + txid?: string; +}; + +type SignPSBTParams = { + /** + * The PSBT to be signed, string base64 encoded + */ + psbt: string; + signInputs: { + /** + * The address whose private key to use for signing. + */ + address: string; + /** + * Specifies which input to sign + */ + index: number; + /** + * Specifies which part(s) of the transaction the signature commits to + */ + sighashTypes: number[]; + }[]; + /** + * If `true`, the PSBT will be broadcasted after signing. Default is `false`. + */ + broadcast?: boolean; +}; + +export const BitcoinUtil = { + createSignPSBTParams(params: BitcoinUtil.CreateSignPSBTParams): SignPSBTParams { + const network = this.getBitcoinNetwork(params.caipNetworkId); + const payment = this.getPaymentByAddress(params.senderAddress, network); + const psbt = new bitcoin.Psbt({ network }); + + if (!payment.output) { + throw new Error('Invalid payment output'); + } + + const change = this.calculateChange(params.utxos, params.amount, params.feeRate); + + if (change < 0) { + throw new Error('Insufficient funds'); + } else if (change > 0) { + psbt.addOutput({ + address: params.senderAddress, + value: BigInt(change) + }); + } + + for (const utxo of params.utxos) { + psbt.addInput({ + hash: utxo.txid, + index: utxo.vout, + witnessUtxo: { + script: payment.output, + value: BigInt(utxo.value) + } + }); + } + + psbt.addOutput({ + address: params.recipientAddress, + value: BigInt(params.amount) + }); + + if (params.memo) { + const data = Buffer.from(params.memo, 'utf8'); + const embed = bitcoin.payments.embed({ data: [data] }); + + if (!embed.output) { + throw new Error('Invalid embed output'); + } + + psbt.addOutput({ + script: embed.output, + value: BigInt(0) + }); + } + + return { + psbt: psbt.toBase64(), + signInputs: [], + broadcast: false + }; + }, + + async getUTXOs(address: string, networkId: CaipNetworkId): Promise { + const isTestnet = this.isTestnet(networkId); + // Make chain dynamic + + const response = await fetch( + `https://mempool.space${isTestnet ? '/testnet' : ''}/api/address/${address}/utxo` + ); + + return await response.json(); + }, + + async getFeeRate() { + const defaultFeeRate = 2; + try { + const response = await fetch('https://mempool.space/api/v1/fees/recommended'); + if (response.ok) { + const data = await response.json(); + + if (data?.fastestFee) { + return parseInt(data.fastestFee, 10); + } + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('Error fetching fee rate', e); + } + + return defaultFeeRate; + }, + + calculateChange(utxos: BitcoinUtil.UTXO[], amount: number, feeRate: number): number { + const inputSum = utxos.reduce((sum, utxo) => sum + utxo.value, 0); + /** + * 10 bytes: This is an estimated fixed overhead for the transaction. + * 148 bytes: This is the average size of each input (UTXO). + * 34 bytes: This is the size of each output. + * The multiplication by 2 indicates that there are usually two outputs in a typical transaction (one for the recipient and one for change) + */ + const estimatedSize = 10 + 148 * utxos.length + 34 * 2; + const fee = estimatedSize * feeRate; + const change = inputSum - amount - fee; + + return change; + }, + + isTestnet(networkId: CaipNetworkId): boolean { + return networkId === bitcoinTestnetNetwork.caipNetworkId; + }, + + getBitcoinNetwork(networkId: CaipNetworkId): bitcoin.Network { + return this.isTestnet(networkId) ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; + }, + + getPaymentByAddress( + address: string, + network: bitcoin.networks.Network + ): bitcoin.payments.Payment { + const output = bitcoin.address.toOutputScript(address, network); + + if (bitcoinPSBTUtils.isP2MS(output)) { + return bitcoin.payments.p2ms({ output, network }); + } else if (bitcoinPSBTUtils.isP2PK(output)) { + return bitcoin.payments.p2pk({ output, network }); + } else if (bitcoinPSBTUtils.isP2PKH(output)) { + return bitcoin.payments.p2pkh({ output, network }); + } else if (bitcoinPSBTUtils.isP2WPKH(output)) { + return bitcoin.payments.p2wpkh({ output, network }); + } else if (bitcoinPSBTUtils.isP2WSHScript(output)) { + return bitcoin.payments.p2wsh({ output, network }); + } else if (bitcoinPSBTUtils.isP2SHScript(output)) { + return bitcoin.payments.p2sh({ output, network }); + } else if (bitcoinPSBTUtils.isP2TR(output)) { + return bitcoin.payments.p2tr({ output, network }); + } + + throw new Error('Unsupported payment type'); + } +}; + +export namespace BitcoinUtil { + export type CreateSignPSBTParams = { + senderAddress: string; + recipientAddress: string; + caipNetworkId: CaipNetworkId; + amount: number; + utxos: UTXO[]; + feeRate: number; + memo?: string; + }; + + export type UTXO = { + txid: string; + vout: number; + value: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + }; +} diff --git a/apps/native/src/utils/ChainUtils.ts b/apps/native/src/utils/ChainUtils.ts deleted file mode 100644 index a5afe037..00000000 --- a/apps/native/src/utils/ChainUtils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CaipNetworkId, ChainNamespace } from '@reown/appkit-common-react-native'; - -export const solana = { - id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', - name: 'Solana', - network: 'solana-mainnet', - nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, - rpcUrls: { - default: { http: ['https://api.mainnet-beta.solana.com'] } - }, - blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, - testnet: false, - chainNamespace: 'solana' as ChainNamespace, - caipNetworkId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' as CaipNetworkId, - deprecatedCaipNetworkId: 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ' as CaipNetworkId -}; diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index 04d04e93..7f4da3d8 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -4,7 +4,7 @@ import { useAppKitAccount } from '@reown/appkit-react-native'; import { EthersActionsView } from './EthersActionsView'; import { SolanaActionsView } from './SolanaActionsView'; - +import { BitcoinActionsView } from './BitcoinActionsView'; export function ActionsView() { const isConnected = true; const { chainId } = useAppKitAccount(); @@ -15,6 +15,8 @@ export function ActionsView() { ) : chainId?.startsWith('solana') ? ( + ) : chainId?.startsWith('bip122') ? ( + ) : null} ) : null; diff --git a/apps/native/src/views/BitcoinActionsView.tsx b/apps/native/src/views/BitcoinActionsView.tsx new file mode 100644 index 00000000..6a0dd10e --- /dev/null +++ b/apps/native/src/views/BitcoinActionsView.tsx @@ -0,0 +1,128 @@ +import { StyleSheet } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { useAppKit, useAppKitAccount } from '@reown/appkit-react-native'; + +import { ToastUtils } from '../utils/ToastUtils'; +import { BitcoinUtil, SignPSBTResponse } from '../utils/BitcoinUtil'; + +export function BitcoinActionsView() { + const isConnected = true; + const { appKit } = useAppKit(); + const { address, chainId } = useAppKitAccount(); + + const provider = appKit?.getProvider('bip122'); + + const onSignSuccess = (data: string) => { + ToastUtils.showSuccessToast('Sign successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Sign failed', error.message); + }; + + const signMessage = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + + const message = 'Hello from AppKit Bitcoin'; + + const { signature } = (await provider.request( + { + method: 'signMessage', + params: { message, account: address, address, protocol: 'ecdsa' } + }, + chainId + )) as { address: string; signature: string }; + + const formattedSignature = Buffer.from(signature, 'hex').toString('base64'); + + onSignSuccess(formattedSignature); + } catch (error) { + onSignError(error as Error); + } + }; + + const signPsbt = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign failed', 'No provider found'); + + return; + } + + if (!address) { + ToastUtils.showErrorToast('Sign failed', 'No address found'); + + return; + } + + if (chainId?.split(':')[0] !== 'bip122') { + ToastUtils.showErrorToast('Sign failed', 'The selected chain is not bip122'); + + return; + } + + const utxos = await BitcoinUtil.getUTXOs(address, chainId as `bip122:${string}`); + const feeRate = await BitcoinUtil.getFeeRate(); + + const params = BitcoinUtil.createSignPSBTParams({ + amount: 1500, + feeRate, + caipNetworkId: chainId as `bip122:${string}`, + recipientAddress: address, + senderAddress: address, + utxos + }); + + params.broadcast = false; + + const response = (await provider.request( + { + method: 'signPsbt', + params: { + account: address, + psbt: params.psbt, + signInputs: params.signInputs, + broadcast: params.broadcast + } + }, + chainId + )) as SignPSBTResponse; + + onSignSuccess(`${response.psbt}-${response.txid}`); + } catch (error) { + // eslint-disable-next-line no-console + console.log('error', error); + onSignError(error as Error); + } + }; + + return isConnected ? ( + + Bitcoin Actions + + + + ) : null; +} + +const styles = StyleSheet.create({ + container: { + marginVertical: 16, + gap: 8 + } +}); diff --git a/apps/native/src/views/EthersActionsView.tsx b/apps/native/src/views/EthersActionsView.tsx index 1370b307..0d925cc5 100644 --- a/apps/native/src/views/EthersActionsView.tsx +++ b/apps/native/src/views/EthersActionsView.tsx @@ -33,7 +33,7 @@ export function EthersActionsView() { return; } - const message = 'hello appkit + ethers'; + const message = 'Hello from AppKit Ethers'; const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); const signature = await provider.request( @@ -46,6 +46,7 @@ export function EthersActionsView() { onSignSuccess(signature); } catch (error) { + // eslint-disable-next-line no-console console.log('error', error); onSignError(error as Error); } diff --git a/apps/native/src/views/SolanaActionsView.tsx b/apps/native/src/views/SolanaActionsView.tsx index 7082aec6..cd66e59a 100644 --- a/apps/native/src/views/SolanaActionsView.tsx +++ b/apps/native/src/views/SolanaActionsView.tsx @@ -33,7 +33,7 @@ export function SolanaActionsView() { return; } - const encodedMessage = new TextEncoder().encode('Hello from AppKit'); + const encodedMessage = new TextEncoder().encode('Hello from AppKit Solana'); const params = { message: base58.encode(encodedMessage), @@ -46,7 +46,7 @@ export function SolanaActionsView() { params }, chainId - )) as any; //TODO: check type + )) as { address: string; signature: string }; onSignSuccess(signature); } catch (error) { diff --git a/package.json b/package.json index 9a594214..d3b580fa 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "packages/ethers5", "packages/ethers", "packages/solana", + "packages/bitcoin", "apps/*" ], "scripts": { diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 4fabca54..80ee2513 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -23,6 +23,7 @@ import type { import { WalletConnectConnector } from './connectors/WalletConnectConnector'; import { WcHelpersUtil } from './utils/HelpersUtil'; +import { NetworkUtil } from './utils/NetworkUtil'; interface AppKitConfig { projectId: string; @@ -44,7 +45,7 @@ export class AppKit { this.projectId = config.projectId; this.metadata = config.metadata; this.adapters = config.adapters; - this.networks = config.networks; + this.networks = NetworkUtil.formatNetworks(config.networks, this.projectId); //TODO: check this this.namespaces = WcHelpersUtil.createNamespaces(config.networks) as ProposalNamespaces; this.extraConnectors = config.extraConnectors || []; diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 5888bdd1..2b5ac8a8 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -2,6 +2,7 @@ import { type Metadata, ConnectionController } from '@reown/appkit-core-react-na import { UniversalProvider, type IUniversalProvider } from '@walletconnect/universal-provider'; import { WalletConnector, + type AppKitNetwork, type Namespaces, type ProposalNamespaces, type Provider @@ -35,7 +36,7 @@ export class WalletConnectConnector extends WalletConnector { return this.provider.disconnect(); } - override async connect(namespaces?: ProposalNamespaces) { + override async connect(namespaces: ProposalNamespaces) { function onUri(uri: string) { ConnectionController.setWcUri(uri); } @@ -60,4 +61,12 @@ export class WalletConnectConnector extends WalletConnector { override getNamespaces(): Namespaces { return this.namespaces ?? {}; } + + override switchNetwork(network: AppKitNetwork): Promise { + if (!network.caipNetworkId) throw new Error('No network provided'); + + (this.provider as IUniversalProvider).setDefaultChain(network.caipNetworkId); + + return Promise.resolve(); + } } diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 79240dba..5c209b5c 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -21,6 +21,7 @@ export type * from '@reown/appkit-core-react-native'; export { CoreHelperUtil } from '@reown/appkit-core-react-native'; export * from './AppKit'; +export * from './networks'; export { AppKitProvider, useAppKit } from './AppKitContext'; export { useProvider } from './hooks/useProvider'; export { useAppKitAccount } from './hooks/useAppKitAccount'; diff --git a/packages/appkit/src/networks/bitcoin.ts b/packages/appkit/src/networks/bitcoin.ts new file mode 100644 index 00000000..327bb85f --- /dev/null +++ b/packages/appkit/src/networks/bitcoin.ts @@ -0,0 +1,32 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const bitcoin: AppKitNetwork = { + id: '000000000019d6689c085ae165831e93', + caipNetworkId: 'bip122:000000000019d6689c085ae165831e93', + chainNamespace: 'bip122', + name: 'Bitcoin', + nativeCurrency: { + name: 'Bitcoin', + symbol: 'BTC', + decimals: 8 + }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + } +}; + +export const bitcoinTestnet: AppKitNetwork = { + id: '000000000933ea01ad0ee984209779ba', + caipNetworkId: 'bip122:000000000933ea01ad0ee984209779ba', + chainNamespace: 'bip122', + name: 'Bitcoin Testnet', + nativeCurrency: { + name: 'Bitcoin', + symbol: 'BTC', + decimals: 8 + }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + }, + testnet: true +}; diff --git a/packages/appkit/src/networks/index.ts b/packages/appkit/src/networks/index.ts new file mode 100644 index 00000000..5f2e141a --- /dev/null +++ b/packages/appkit/src/networks/index.ts @@ -0,0 +1,6 @@ +// -- Networks --------------------------------------------------------------- +export * from './solana'; +export * from './bitcoin'; + +// -- Types --------------------------------------------------------------- +export type { AppKitNetwork } from '@reown/appkit-common-react-native'; diff --git a/packages/appkit/src/networks/solana.ts b/packages/appkit/src/networks/solana.ts new file mode 100644 index 00000000..39a2e32f --- /dev/null +++ b/packages/appkit/src/networks/solana.ts @@ -0,0 +1,44 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const solana: AppKitNetwork = { + id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana', + nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, + rpcUrls: { + default: { + http: ['https://rpc.walletconnect.org/v1'] + } + }, + blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, + chainNamespace: 'solana', + caipNetworkId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + deprecatedCaipNetworkId: 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', + testnet: false +}; + +export const solanaDevnet: AppKitNetwork = { + id: 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + name: 'Solana Devnet', + nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + }, + blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, + chainNamespace: 'solana', + caipNetworkId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', + deprecatedCaipNetworkId: 'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K', + testnet: true +}; + +export const solanaTestnet = { + id: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', + name: 'Solana Testnet', + nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, + rpcUrls: { + default: { http: ['https://rpc.walletconnect.org/v1'] } + }, + blockExplorers: { default: { name: 'Solscan', url: 'https://solscan.io' } }, + chainNamespace: 'solana', + caipNetworkId: 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', + testnet: true +}; diff --git a/packages/appkit/src/utils/HelpersUtil.ts b/packages/appkit/src/utils/HelpersUtil.ts index 88c46d9d..3fe3489c 100644 --- a/packages/appkit/src/utils/HelpersUtil.ts +++ b/packages/appkit/src/utils/HelpersUtil.ts @@ -50,6 +50,7 @@ export const WcHelpersUtil = { getMethodsByChainNamespace(chainNamespace: ChainNamespace): string[] { return DEFAULT_METHODS[chainNamespace as keyof typeof DEFAULT_METHODS] || []; }, + createDefaultNamespace(chainNamespace: ChainNamespace): Namespace { return { methods: this.getMethodsByChainNamespace(chainNamespace), diff --git a/packages/appkit/src/utils/NetworkUtil.ts b/packages/appkit/src/utils/NetworkUtil.ts new file mode 100644 index 00000000..331761b4 --- /dev/null +++ b/packages/appkit/src/utils/NetworkUtil.ts @@ -0,0 +1,39 @@ +import { ConstantsUtil } from '@reown/appkit-common-react-native'; +import type { AppKitNetwork, CaipNetworkId } from '@reown/appkit-common-react-native'; + +export const NetworkUtil = { + //TODO: check this function + formatNetworks(networks: AppKitNetwork[], projectId: string): AppKitNetwork[] { + return networks.map(network => { + const formattedNetwork = { + ...network, + rpcUrls: { ...network.rpcUrls } + }; + + Object.keys(formattedNetwork.rpcUrls).forEach(key => { + const rpcConfig = formattedNetwork.rpcUrls[key]; + if (rpcConfig?.http?.some(url => url.includes(ConstantsUtil.BLOCKCHAIN_API_RPC_URL))) { + formattedNetwork.rpcUrls[key] = { + ...rpcConfig, + http: [ + this.getBlockchainApiRpcUrl( + network.caipNetworkId ?? `${network.chainNamespace ?? 'eip155'}:${network.id}`, + projectId + ) + ] + }; + } + }); + + return formattedNetwork; + }); + }, + + getBlockchainApiRpcUrl(caipNetworkId: CaipNetworkId, projectId: string) { + const url = new URL(`${ConstantsUtil.BLOCKCHAIN_API_RPC_URL}/v1/`); + url.searchParams.set('chainId', caipNetworkId); + url.searchParams.set('projectId', projectId); + + return url.toString(); + } +}; diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 30561c96..8262b4c4 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -219,7 +219,7 @@ export function AccountDefaultView() { {showBalance && ( - {CoreHelperUtil.formatBalance(balance.amount, balance.symbol)} + {CoreHelperUtil.formatBalance(balance.amount, balance.symbol, 6)} )} {showExplorer && ( diff --git a/packages/bitcoin/.eslintignore b/packages/bitcoin/.eslintignore new file mode 100644 index 00000000..c18ed016 --- /dev/null +++ b/packages/bitcoin/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +lib/ \ No newline at end of file diff --git a/packages/bitcoin/.eslintrc.json b/packages/bitcoin/.eslintrc.json new file mode 100644 index 00000000..b9233ee4 --- /dev/null +++ b/packages/bitcoin/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../.eslintrc"] +} diff --git a/packages/bitcoin/.npmignore b/packages/bitcoin/.npmignore new file mode 100644 index 00000000..e203f76a --- /dev/null +++ b/packages/bitcoin/.npmignore @@ -0,0 +1,10 @@ +*.log +*.env +npm-debug.log* +node_modules +package-lock.json +src +tests +index.ts +.eslintrc.json +.turbo diff --git a/packages/bitcoin/bob.config.js b/packages/bitcoin/bob.config.js new file mode 100644 index 00000000..b7ca0ad6 --- /dev/null +++ b/packages/bitcoin/bob.config.js @@ -0,0 +1,14 @@ +module.exports = { + source: 'src', + output: 'lib', + targets: [ + 'commonjs', + 'module', + [ + 'typescript', + { + tsc: '../../node_modules/.bin/tsc' + } + ] + ] +}; diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json new file mode 100644 index 00000000..162e8b01 --- /dev/null +++ b/packages/bitcoin/package.json @@ -0,0 +1,54 @@ +{ + "name": "@reown/appkit-bitcoin-react-native", + "version": "1.2.3", + "main": "lib/commonjs/index.js", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index.js", + "source": "src/index.tsx", + "scripts": { + "build": "bob build", + "clean": "rm -rf lib", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" + }, + "files": [ + "src", + "lib", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": [ + "web3", + "crypto", + "bitcoin", + "appkit", + "reown", + "walletconnect", + "react-native" + ], + "repository": "https://github.com/reown-com/appkit-react-native", + "author": "Reown (https://reown.com)", + "homepage": "https://reown.com/appkit", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/reown-com/appkit-react-native/issues" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "dependencies": { + "@reown/appkit-common-react-native": "1.2.3", + "@solana/web3.js": "1.98.2", + "bs58": "6.0.0" + }, + "peerDependencies": { + "@solana/web3.js": ">=1.90.0", + "bs58": ">=6.0.0" + }, + "devDependencies": { + "@solana/web3.js": "1.98.2", + "bs58": "6.0.0" + }, + "react-native": "src/index.tsx" +} diff --git a/packages/bitcoin/readme.md b/packages/bitcoin/readme.md new file mode 100644 index 00000000..60524ccd --- /dev/null +++ b/packages/bitcoin/readme.md @@ -0,0 +1,9 @@ +#### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) + +#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) + +#### 🔗 [Website](https://reown.com/appkit) + +# AppKit + +Your on-ramp to web3 multichain. AppKit is a versatile library that makes it super easy to connect users with your Dapp and start interacting with the blockchain. diff --git a/packages/bitcoin/src/adapter.ts b/packages/bitcoin/src/adapter.ts new file mode 100644 index 00000000..09bacdb2 --- /dev/null +++ b/packages/bitcoin/src/adapter.ts @@ -0,0 +1,144 @@ +import { + BlockchainAdapter, + WalletConnector, + type AppKitNetwork, + type CaipAddress, + type GetBalanceParams, + type GetBalanceResponse +} from '@reown/appkit-common-react-native'; +import { BitcoinApi } from './utils/BitcoinApi'; +import { UnitsUtil } from './utils/UnitsUtil'; + +export class BitcoinAdapter extends BlockchainAdapter { + private static supportedNamespace: string = 'bip122'; + private static api = BitcoinApi; + + constructor(configParams: { projectId: string }) { + super({ + projectId: configParams.projectId, + supportedNamespace: BitcoinAdapter.supportedNamespace + }); + } + + async getBalance(params: GetBalanceParams): Promise { + const { network, address } = params; + + if (!this.connector) throw new Error('No active connector'); + if (!network) throw new Error('No network provided'); + + const balanceCaipAddress = + address || this.getAccounts()?.find(account => account.includes(network.id.toString())); + + const balanceAddress = balanceCaipAddress?.split(':')[2]; + + if (!balanceCaipAddress || !balanceAddress) { + return Promise.resolve({ amount: '0.00', symbol: 'BTC' }); + } + + try { + const utxos = await BitcoinAdapter.api.getUTXOs({ + network, + address: balanceAddress + }); + + const balance = utxos.reduce((acc, utxo) => acc + utxo.value, 0); + const formattedBalance = UnitsUtil.parseSatoshis(balance.toString(), network); + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address: balanceCaipAddress, + balance: { + amount: formattedBalance, + symbol: network.nativeCurrency.symbol + } + }); + + return { amount: formattedBalance, symbol: network.nativeCurrency.symbol }; + } catch (error) { + return { amount: '0.00', symbol: 'BTC' }; + } + } + + override async switchNetwork(network: AppKitNetwork): Promise { + if (!this.connector) throw new Error('No active connector'); + + const provider = this.connector.getProvider(); + if (!provider) throw new Error('No active provider'); + + try { + await this.connector.switchNetwork(network); + + return; + } catch (switchError: any) { + throw switchError; + } + } + + getAccounts(): CaipAddress[] | undefined { + if (!this.connector) throw new Error('No active connector'); + const namespaces = this.connector.getNamespaces(); + + return namespaces[this.getSupportedNamespace()]?.accounts; + } + + disconnect(): Promise { + if (!this.connector) throw new Error('SolanaAdapter:disconnect - No active connector'); + + return this.connector.disconnect(); + } + + async request(method: string, params?: any[]) { + if (!this.connector) throw new Error('No active connector'); + const provider = this.connector.getProvider(); + + return provider.request({ method, params }); + } + + getSupportedNamespace(): string { + return BitcoinAdapter.supportedNamespace; + } + + onChainChanged(chainId: string): void { + this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + } + + onAccountsChanged(accounts: string[]): void { + const _accounts = this.getAccounts(); + const shouldEmit = _accounts?.some(account => { + const accountAddress = account.split(':')[2]; + + return accountAddress !== undefined && accounts.includes(accountAddress); + }); + + if (shouldEmit) { + this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + } + } + + onDisconnect(): void { + this.emit('disconnect', { namespace: this.getSupportedNamespace() }); + + const provider = this.connector?.getProvider(); + if (provider) { + provider.off('chainChanged', this.onChainChanged.bind(this)); + provider.off('accountsChanged', this.onAccountsChanged.bind(this)); + provider.off('disconnect', this.onDisconnect.bind(this)); + } + + this.connector = undefined; + } + + override setConnector(connector: WalletConnector): void { + super.setConnector(connector); + this.subscribeToEvents(); + } + + subscribeToEvents(): void { + const provider = this.connector?.getProvider(); + if (!provider) return; + + provider.on('chainChanged', this.onChainChanged.bind(this)); + provider.on('accountsChanged', this.onAccountsChanged.bind(this)); + provider.on('disconnect', this.onDisconnect.bind(this)); + } +} diff --git a/packages/bitcoin/src/index.tsx b/packages/bitcoin/src/index.tsx new file mode 100644 index 00000000..d1cec69d --- /dev/null +++ b/packages/bitcoin/src/index.tsx @@ -0,0 +1,2 @@ +import { BitcoinAdapter } from './adapter'; +export { BitcoinAdapter }; diff --git a/packages/bitcoin/src/utils/BitcoinApi.ts b/packages/bitcoin/src/utils/BitcoinApi.ts new file mode 100644 index 00000000..a7521245 --- /dev/null +++ b/packages/bitcoin/src/utils/BitcoinApi.ts @@ -0,0 +1,41 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const BitcoinApi: BitcoinApi.Interface = { + getUTXOs: async ({ network, address }: BitcoinApi.GetUTXOsParams): Promise => { + const isTestnet = network.caipNetworkId === 'bip122:000000000933ea01ad0ee984209779ba'; + // Make chain dynamic + + //TODO: Call rpc to get balance + const url = `https://mempool.space${isTestnet ? '/testnet' : ''}/api/address/${address}/utxo`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Failed to fetch UTXOs: ${await response.text()}`); + } + + return (await response.json()) as BitcoinApi.UTXO[]; + } +}; + +export namespace BitcoinApi { + export type Interface = { + getUTXOs: (params: GetUTXOsParams) => Promise; + }; + + export type GetUTXOsParams = { + network: AppKitNetwork; + address: string; + }; + + export type UTXO = { + txid: string; + vout: number; + value: number; + status: { + confirmed: boolean; + block_height: number; + block_hash: string; + block_time: number; + }; + }; +} diff --git a/packages/bitcoin/src/utils/UnitsUtil.ts b/packages/bitcoin/src/utils/UnitsUtil.ts new file mode 100644 index 00000000..66bd85f3 --- /dev/null +++ b/packages/bitcoin/src/utils/UnitsUtil.ts @@ -0,0 +1,11 @@ +import type { AppKitNetwork } from '@reown/appkit-common-react-native'; + +export const UnitsUtil = { + parseSatoshis(amount: string, network: AppKitNetwork): string { + const value = parseFloat(amount) / 10 ** network.nativeCurrency.decimals; + + return Intl.NumberFormat('en-US', { + maximumFractionDigits: network.nativeCurrency.decimals + }).format(value); + } +}; diff --git a/packages/bitcoin/tsconfig.json b/packages/bitcoin/tsconfig.json new file mode 100644 index 00000000..512da539 --- /dev/null +++ b/packages/bitcoin/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "src/index.tsx"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index c85308c9..da21e1a2 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -23,6 +23,8 @@ export type AppKitNetwork = { // AppKit specific / CAIP properties (Optional in type, but often needed in practice) chainNamespace?: ChainNamespace; // e.g., 'eip155' caipNetworkId?: CaipNetworkId; // e.g., 'eip155:1' + testnet?: boolean; + deprecatedCaipNetworkId?: CaipNetworkId; // for Solana }; export interface CaipNetwork { @@ -191,7 +193,10 @@ type Namespace = BaseNamespace; export type Namespaces = Record; -export type ProposalNamespaces = Record>; +export type ProposalNamespaces = Record< + string, + Omit & Required> +>; export abstract class WalletConnector extends EventEmitter { public type: New_ConnectorType; @@ -208,6 +213,7 @@ export abstract class WalletConnector extends EventEmitter { abstract disconnect(): Promise; abstract getProvider(): Provider; abstract getNamespaces(): Namespaces; + abstract switchNetwork(network: AppKitNetwork): Promise; } //********** Provider Types **********// diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 5fdf59a5..5d6757b9 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -109,11 +109,11 @@ export class EthersAdapter extends EVMAdapter { return EthersAdapter.supportedNamespace; } - override onChainChanged(chainId: string): void { + onChainChanged(chainId: string): void { this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); } - override onAccountsChanged(accounts: string[]): void { + onAccountsChanged(accounts: string[]): void { const _accounts = this.getAccounts(); const shouldEmit = _accounts?.some(account => { const accountAddress = account.split(':')[2]; diff --git a/packages/solana/package.json b/packages/solana/package.json index f18fbd1e..bef2c33b 100644 --- a/packages/solana/package.json +++ b/packages/solana/package.json @@ -22,6 +22,7 @@ "crypto", "solana", "appkit", + "reown", "walletconnect", "react-native" ], @@ -38,16 +39,14 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", - "@solana/web3.js": "1.98.2", - "bs58": "6.0.0" + "@solana/web3.js": "1.98.2" }, "peerDependencies": { "@solana/web3.js": ">=1.90.0", "bs58": ">=6.0.0" }, "devDependencies": { - "@solana/web3.js": "1.98.2", - "bs58": "6.0.0" + "@solana/web3.js": "1.98.2" }, "react-native": "src/index.tsx" } diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 03a6da27..a88bf33a 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -62,8 +62,7 @@ export class SolanaAdapter extends SolanaBaseAdapter { if (!provider) throw new Error('No active provider'); try { - //@ts-ignore //TODO: check this - await provider?.setDefaultChain(network.caipNetworkId); + await this.connector.switchNetwork(network); return; } catch (switchError: any) { @@ -95,11 +94,11 @@ export class SolanaAdapter extends SolanaBaseAdapter { return SolanaAdapter.supportedNamespace; } - override onChainChanged(chainId: string): void { + onChainChanged(chainId: string): void { this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); } - override onAccountsChanged(accounts: string[]): void { + onAccountsChanged(accounts: string[]): void { const _accounts = this.getAccounts(); const shouldEmit = _accounts?.some(account => { const accountAddress = account.split(':')[2]; diff --git a/yarn.lock b/yarn.lock index 7ec4d59e..57f3b451 100644 --- a/yarn.lock +++ b/yarn.lock @@ -102,13 +102,16 @@ __metadata: resolution: "@apps/native@workspace:apps/native" dependencies: "@babel/core": "npm:^7.24.0" + "@bitcoinerlab/secp256k1": "npm:1.2.0" "@expo/metro-runtime": "npm:~4.0.1" "@playwright/test": "npm:^1.49.1" "@react-native-async-storage/async-storage": "npm:2.1.2" "@react-native-community/netinfo": "npm:11.4.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.2.3" + "@reown/appkit-bitcoin-react-native": "workspace:*" "@reown/appkit-ethers-react-native": "workspace:*" "@reown/appkit-react-native": "workspace:*" + "@reown/appkit-solana-react-native": "workspace:*" "@reown/appkit-wagmi-react-native": "npm:1.2.3" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" @@ -118,6 +121,7 @@ __metadata: "@types/react": "npm:~18.2.79" "@walletconnect/react-native-compat": "npm:2.20.2" babel-plugin-module-resolver: "npm:^5.0.0" + bitcoinjs-lib: "npm:7.0.0-rc.0" ethers: "npm:6.13.5" expo: "npm:^52.0.38" expo-application: "npm:~6.0.2" @@ -3899,6 +3903,15 @@ __metadata: languageName: node linkType: hard +"@bitcoinerlab/secp256k1@npm:1.2.0": + version: 1.2.0 + resolution: "@bitcoinerlab/secp256k1@npm:1.2.0" + dependencies: + "@noble/curves": "npm:^1.7.0" + checksum: ab5196e6052b60cbfee347434105dee59ecd93cb73473706252d35581b63dec2f4241b9e5ce7d5bbb062fb3fa9898a78660c886be8ae2d375480080c30a3a4b3 + languageName: node + linkType: hard + "@changesets/apply-release-plan@npm:^7.0.4": version: 7.0.4 resolution: "@changesets/apply-release-plan@npm:7.0.4" @@ -6127,7 +6140,7 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:^1.4.2": +"@noble/curves@npm:^1.4.2, @noble/curves@npm:^1.7.0": version: 1.9.0 resolution: "@noble/curves@npm:1.9.0" dependencies: @@ -6194,7 +6207,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.8.0": +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.2.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" checksum: 06a0b52c81a6fa7f04d67762e08b2c476a00285858150caeaaff4037356dd5e119f45b2a530f638b77a5eeca013168ec1b655db41bae3236cb2e9d511484fc77 @@ -7109,6 +7122,19 @@ __metadata: languageName: unknown linkType: soft +"@reown/appkit-bitcoin-react-native@workspace:*, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": + version: 0.0.0-use.local + resolution: "@reown/appkit-bitcoin-react-native@workspace:packages/bitcoin" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" + "@solana/web3.js": "npm:1.98.2" + bs58: "npm:6.0.0" + peerDependencies: + "@solana/web3.js": ">=1.90.0" + bs58: ">=6.0.0" + languageName: unknown + linkType: soft + "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers": version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers" @@ -7301,13 +7327,12 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-solana-react-native@workspace:packages/solana": +"@reown/appkit-solana-react-native@workspace:*, @reown/appkit-solana-react-native@workspace:packages/solana": version: 0.0.0-use.local resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" "@solana/web3.js": "npm:1.98.2" - bs58: "npm:6.0.0" peerDependencies: "@solana/web3.js": ">=1.90.0" bs58: ">=6.0.0" @@ -10786,6 +10811,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:^2.0.0": + version: 2.0.0 + resolution: "bech32@npm:2.0.0" + checksum: 45e7cc62758c9b26c05161b4483f40ea534437cf68ef785abadc5b62a2611319b878fef4f86ddc14854f183b645917a19addebc9573ab890e19194bc8f521942 + languageName: node + linkType: hard + "better-opn@npm:~3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -10832,6 +10864,31 @@ __metadata: languageName: node linkType: hard +"bip174@npm:^3.0.0-rc.0": + version: 3.0.0-rc.1 + resolution: "bip174@npm:3.0.0-rc.1" + dependencies: + uint8array-tools: "npm:^0.0.9" + varuint-bitcoin: "npm:^2.0.0" + checksum: d4fc26a4ec3dc6f4ce5b5cf38acc0825570c96a2bea536bf857a743ebfca5061ea9bc5c0cb21466a47c82e757190e7f00149eb6c6ccbba3238a48e853341b945 + languageName: node + linkType: hard + +"bitcoinjs-lib@npm:7.0.0-rc.0": + version: 7.0.0-rc.0 + resolution: "bitcoinjs-lib@npm:7.0.0-rc.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bech32: "npm:^2.0.0" + bip174: "npm:^3.0.0-rc.0" + bs58check: "npm:^4.0.0" + uint8array-tools: "npm:^0.0.9" + valibot: "npm:^0.38.0" + varuint-bitcoin: "npm:^2.0.0" + checksum: 9185d2b59a3a75d34a715dd0f654019cba4a274042c376c6ff130469fb5577de6dbe224c0c1a482fa842dfd35d3b0d25de263a5c1a79762cf46ed325194b6614 + languageName: node + linkType: hard + "bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -11089,7 +11146,7 @@ __metadata: languageName: node linkType: hard -"bs58@npm:6.0.0": +"bs58@npm:6.0.0, bs58@npm:^6.0.0": version: 6.0.0 resolution: "bs58@npm:6.0.0" dependencies: @@ -11107,6 +11164,16 @@ __metadata: languageName: node linkType: hard +"bs58check@npm:^4.0.0": + version: 4.0.0 + resolution: "bs58check@npm:4.0.0" + dependencies: + "@noble/hashes": "npm:^1.2.0" + bs58: "npm:^6.0.0" + checksum: a4e695202711daffa157ada2044bb55ff21adcfe22c92ede12111d55570e170dd4cb8cd058db12980dca6bd51733f17f7534cddc19ea1f7dfa9852583f888eea + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -23080,6 +23147,20 @@ __metadata: languageName: node linkType: hard +"uint8array-tools@npm:^0.0.8": + version: 0.0.8 + resolution: "uint8array-tools@npm:0.0.8" + checksum: ffc01a50aaed4ce7d9c30260b23465c79ffe6e4d0fe1ba4605611e59feabbaff81b42ddf7896a747f07aafcbb5a4252d1b39f2325bacb21454212c42c954d74d + languageName: node + linkType: hard + +"uint8array-tools@npm:^0.0.9": + version: 0.0.9 + resolution: "uint8array-tools@npm:0.0.9" + checksum: 1f3692aa60f87b84ebd3254bea2024ee9b8c1dc226ac906a879190298c736b3c942a7a12d20996d179d3918a65d4613fc2494837e8959329ac0747e12a18f90c + languageName: node + linkType: hard + "uint8arrays@npm:3.1.0": version: 3.1.0 resolution: "uint8arrays@npm:3.1.0" @@ -23570,6 +23651,18 @@ __metadata: languageName: node linkType: hard +"valibot@npm:^0.38.0": + version: 0.38.0 + resolution: "valibot@npm:0.38.0" + peerDependencies: + typescript: ">=5" + peerDependenciesMeta: + typescript: + optional: true + checksum: dd61a2299879fa644e6192ec5c67fd036b27c023b77146369ca2d720368f096ca6c9a8711f2e4b7cbac1716df5fe0e2d3eeee5028f233f87de04c08b93e98d81 + languageName: node + linkType: hard + "validate-npm-package-name@npm:^5.0.0": version: 5.0.1 resolution: "validate-npm-package-name@npm:5.0.1" @@ -23614,6 +23707,15 @@ __metadata: languageName: node linkType: hard +"varuint-bitcoin@npm:^2.0.0": + version: 2.0.0 + resolution: "varuint-bitcoin@npm:2.0.0" + dependencies: + uint8array-tools: "npm:^0.0.8" + checksum: 63048ddcf85ef728ec610d234a1de010ce81204751d7d1a54eca9f140a86c30bb187cd4871ee042ce9e656d76ee50093a7370c56114ae6716297ef32de4a8b26 + languageName: node + linkType: hard + "vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" From 8da083a2aad1724125bb560699c7cdb5aa29e48a Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 5 May 2025 15:12:14 -0300 Subject: [PATCH 14/91] chore: added clipboard client option --- apps/native/App.tsx | 15 ++++++++------- packages/appkit/src/AppKit.ts | 9 ++++++++- packages/appkit/src/client.ts | 2 +- .../src/views/w3m-account-default-view/index.tsx | 15 +++++++-------- .../core/src/controllers/OptionsController.ts | 8 ++++---- packages/scaffold-utils/package.json | 3 +-- packages/scaffold-utils/src/utils/HelpersUtil.ts | 2 +- yarn.lock | 1 - 8 files changed, 30 insertions(+), 25 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index dbfe8097..9356745d 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -1,6 +1,6 @@ import { SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; import { StatusBar } from 'expo-status-bar'; -// import * as Clipboard from 'expo-clipboard'; +import * as Clipboard from 'expo-clipboard'; import '@walletconnect/react-native-compat'; // import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -53,11 +53,11 @@ const metadata = { } }; -// const clipboardClient = { -// setString: async (value: string) => { -// await Clipboard.setStringAsync(value); -// } -// }; +const clipboardClient = { + setString: async (value: string) => { + await Clipboard.setStringAsync(value); + } +}; // const auth = authConnector({ projectId, metadata }); @@ -118,7 +118,8 @@ const appKit = createAppKit({ projectId, adapters: [ethersAdapter, solanaAdapter, bitcoinAdapter], metadata, - networks: [mainnet, polygon, avalanche, solana, bitcoin, bitcoinTestnet] + networks: [mainnet, polygon, avalanche, solana, bitcoin, bitcoinTestnet], + clipboardClient }); export default function Native() { diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 80ee2513..a9ca8c5e 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -7,7 +7,8 @@ import { RouterController, TransactionsController, type Metadata, - StorageUtil + StorageUtil, + type OptionsControllerState } from '@reown/appkit-core-react-native'; import type { @@ -31,6 +32,7 @@ interface AppKitConfig { adapters: BlockchainAdapter[]; networks: AppKitNetwork[]; extraConnectors?: WalletConnector[]; + clipboardClient?: OptionsControllerState['clipboardClient']; } export class AppKit { @@ -260,6 +262,11 @@ export class AppKit { OptionsController.setMetadata(options.metadata); } + if (options.clipboardClient) { + console.log('setting clipboard client', options.clipboardClient); + OptionsController.setClipboardClient(options.clipboardClient); + } + ConnectionsController.setNetworks(options.networks); } diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index 3cd6b06b..420f1cac 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -52,7 +52,7 @@ export interface LibraryOptions { customWallets?: OptionsControllerState['customWallets']; defaultChain?: NetworkControllerState['caipNetwork']; tokens?: OptionsControllerState['tokens']; - clipboardClient?: OptionsControllerState['_clipboardClient']; + clipboardClient?: OptionsControllerState['clipboardClient']; enableAnalytics?: OptionsControllerState['enableAnalytics']; _sdkVersion: OptionsControllerState['sdkVersion']; debug?: OptionsControllerState['debug']; diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 8262b4c4..84651909 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -113,14 +113,13 @@ export function AccountDefaultView() { }; const onCopyAddress = () => { - if (AccountController.state.profileName) { - OptionsController.copyToClipboard(AccountController.state.profileName); - SnackController.showSuccess('Name copied'); - } else if (AccountController.state.address) { - OptionsController.copyToClipboard( - AccountController.state.profileName ?? AccountController.state.address - ); - SnackController.showSuccess('Address copied'); + //TODO: Check ENS name + if (OptionsController.isClipboardAvailable() && ConnectionsController.state.activeAddress) { + const _address = ConnectionsController.state.activeAddress.split(':')[2]; + if (_address) { + OptionsController.copyToClipboard(_address); + SnackController.showSuccess('Address copied'); + } } }; diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index 8ecc2e94..10f5ab57 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -17,7 +17,7 @@ export interface ClipboardClient { export interface OptionsControllerState { projectId: ProjectId; - _clipboardClient?: ClipboardClient; + clipboardClient?: ClipboardClient; includeWalletIds?: string[]; excludeWalletIds?: string[]; featuredWalletIds?: string[]; @@ -47,7 +47,7 @@ export const OptionsController = { state, setClipboardClient(client: ClipboardClient) { - state._clipboardClient = ref(client); + state.clipboardClient = ref(client); }, setProjectId(projectId: OptionsControllerState['projectId']) { @@ -103,11 +103,11 @@ export const OptionsController = { }, isClipboardAvailable() { - return !!state._clipboardClient; + return !!state.clipboardClient; }, copyToClipboard(value: string) { - const client = state._clipboardClient; + const client = state.clipboardClient; if (client) { client?.setString(value); } diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index a34194ae..3f3a1a94 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -36,8 +36,7 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-core-react-native": "1.2.3", - "@reown/appkit-scaffold-react-native": "1.2.3" + "@reown/appkit-core-react-native": "1.2.3" }, "react-native": "src/index.ts", "react-native-builder-bob": { diff --git a/packages/scaffold-utils/src/utils/HelpersUtil.ts b/packages/scaffold-utils/src/utils/HelpersUtil.ts index 354d3e99..d1d033ed 100644 --- a/packages/scaffold-utils/src/utils/HelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/HelpersUtil.ts @@ -1,4 +1,4 @@ -import type { Tokens } from '@reown/appkit-scaffold-react-native'; +import type { Tokens } from '@reown/appkit-core-react-native'; import { ConstantsUtil } from '@reown/appkit-common-react-native'; export const HelpersUtil = { diff --git a/yarn.lock b/yarn.lock index 57f3b451..ce5bc009 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7310,7 +7310,6 @@ __metadata: dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" "@reown/appkit-core-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-react-native": "npm:1.2.3" languageName: unknown linkType: soft From f7fb6c1a32fb36a079551daba1be381c8b40b2c9 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 6 May 2025 11:44:23 -0300 Subject: [PATCH 15/91] chore: code improvements --- apps/native/App.tsx | 17 +- packages/appkit/src/AppKit.ts | 53 +++++- .../partials/w3m-account-activity/index.tsx | 11 +- packages/bitcoin/src/adapter.ts | 5 +- packages/common/src/utils/TypeUtil.ts | 16 +- .../controllers/BlockchainApiController.ts | 166 +++++++++++++++--- .../src/controllers/ConnectionsController.ts | 39 ++-- .../core/src/controllers/SwapController.ts | 19 +- .../core/src/controllers/ThemeController.ts | 13 +- .../src/controllers/TransactionsController.ts | 4 +- packages/core/src/utils/ConstantsUtil.ts | 8 +- packages/core/src/utils/NetworkUtil.ts | 2 +- packages/core/src/utils/SwapApiUtil.ts | 14 +- packages/core/src/utils/TypeUtil.ts | 1 + packages/ethers/src/adapter.ts | 5 +- packages/solana/src/adapter.ts | 5 +- packages/wagmi/src/adapter.ts | 5 +- 17 files changed, 293 insertions(+), 90 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 9356745d..53239f1f 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -29,9 +29,7 @@ import { import { Button, Text } from '@reown/appkit-ui-react-native'; // import { siweConfig } from './src/utils/SiweUtils'; - // import { AccountView } from './src/views/AccountView'; -// import { getCustomWallets } from './src/utils/misc'; // import { chains } from './src/utils/WagmiUtils'; // import { OpenButton } from './src/components/OpenButton'; // import { DisconnectButton } from './src/components/DisconnectButton'; @@ -40,6 +38,7 @@ import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; import { mainnet, polygon, avalanche } from 'viem/chains'; import { ActionsView } from './src/views/ActionsView'; + const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; const metadata = { @@ -76,8 +75,6 @@ const clipboardClient = { const queryClient = new QueryClient(); -// const customWallets = getCustomWallets(); - // const wagmiAdapter = new WagmiAdapter({ // wagmiConfig, // projectId, @@ -119,7 +116,17 @@ const appKit = createAppKit({ adapters: [ethersAdapter, solanaAdapter, bitcoinAdapter], metadata, networks: [mainnet, polygon, avalanche, solana, bitcoin, bitcoinTestnet], - clipboardClient + clipboardClient, + debug: true, + enableAnalytics: true + // siweConfig, + // features: { + // email: true, + // socials: ['x', 'discord', 'apple'], + // emailShowWallets: true, + // swaps: true, + // onramp: true + // } }); export default function Native() { diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index a9ca8c5e..7f9b15b0 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -8,7 +8,9 @@ import { TransactionsController, type Metadata, StorageUtil, - type OptionsControllerState + type OptionsControllerState, + ThemeController, + type Features } from '@reown/appkit-core-react-native'; import type { @@ -19,12 +21,15 @@ import type { Namespaces, CaipNetworkId, AppKitNetwork, - Provider + Provider, + ThemeVariables, + ThemeMode } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; import { WcHelpersUtil } from './utils/HelpersUtil'; import { NetworkUtil } from './utils/NetworkUtil'; +import { SIWEController, type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; interface AppKitConfig { projectId: string; @@ -33,6 +38,19 @@ interface AppKitConfig { networks: AppKitNetwork[]; extraConnectors?: WalletConnector[]; clipboardClient?: OptionsControllerState['clipboardClient']; + includeWalletIds?: OptionsControllerState['includeWalletIds']; + excludeWalletIds?: OptionsControllerState['excludeWalletIds']; + featuredWalletIds?: OptionsControllerState['featuredWalletIds']; + customWallets?: OptionsControllerState['customWallets']; + tokens?: OptionsControllerState['tokens']; + enableAnalytics?: OptionsControllerState['enableAnalytics']; + debug?: OptionsControllerState['debug']; + themeMode?: ThemeMode; + themeVariables?: ThemeVariables; + features?: Features; + siweConfig?: AppKitSIWEClient; + // defaultChain?: NetworkControllerState['caipNetwork']; + // chainImages?: Record; } export class AppKit { @@ -257,17 +275,38 @@ export class AppKit { private async initControllers(options: AppKitConfig) { OptionsController.setProjectId(options.projectId); - - if (options.metadata) { - OptionsController.setMetadata(options.metadata); - } + OptionsController.setMetadata(options.metadata); + OptionsController.setIncludeWalletIds(options.includeWalletIds); + OptionsController.setExcludeWalletIds(options.excludeWalletIds); + OptionsController.setFeaturedWalletIds(options.featuredWalletIds); + OptionsController.setTokens(options.tokens); + OptionsController.setCustomWallets(options.customWallets); + OptionsController.setEnableAnalytics(options.enableAnalytics); + OptionsController.setDebug(options.debug); + OptionsController.setFeatures(options.features); + + ThemeController.setThemeMode(options.themeMode); + ThemeController.setThemeVariables(options.themeVariables); + + //TODO: function to get sdk version based on adapters + // OptionsController.setSdkVersion(options._sdkVersion); if (options.clipboardClient) { - console.log('setting clipboard client', options.clipboardClient); OptionsController.setClipboardClient(options.clipboardClient); } ConnectionsController.setNetworks(options.networks); + + if (options.siweConfig) { + SIWEController.setSIWEClient(options.siweConfig); + } + + if ( + (options.features?.onramp === true || options.features?.onramp === undefined) && + (options.metadata?.redirect?.universal || options.metadata?.redirect?.native) + ) { + OptionsController.setIsOnRampEnabled(true); + } } async disconnect(namespace?: string): Promise { diff --git a/packages/appkit/src/partials/w3m-account-activity/index.tsx b/packages/appkit/src/partials/w3m-account-activity/index.tsx index dc70315d..7bae4d4c 100644 --- a/packages/appkit/src/partials/w3m-account-activity/index.tsx +++ b/packages/appkit/src/partials/w3m-account-activity/index.tsx @@ -36,12 +36,13 @@ export function AccountActivity({ style }: Props) { const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); const handleLoadMore = () => { - TransactionsController.fetchTransactions(AccountController.state.address); + const address = ConnectionsController.state.activeAddress?.split(':')[2]; + TransactionsController.fetchTransactions(address); EventsController.sendEvent({ type: 'track', event: 'LOAD_MORE_TRANSACTIONS', properties: { - address: AccountController.state.address, + address, projectId: OptionsController.state.projectId, cursor: TransactionsController.state.next, isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' @@ -51,7 +52,8 @@ export function AccountActivity({ style }: Props) { const onRefresh = useCallback(async () => { setRefreshing(true); - await TransactionsController.fetchTransactions(AccountController.state.address, true); + const address = ConnectionsController.state.activeAddress?.split(':')[2]; + await TransactionsController.fetchTransactions(address, true); setRefreshing(false); }, []); @@ -61,7 +63,8 @@ export function AccountActivity({ style }: Props) { useEffect(() => { if (!TransactionsController.state.transactions.length) { - TransactionsController.fetchTransactions(AccountController.state.address, true); + const address = ConnectionsController.state.activeAddress?.split(':')[2]; + TransactionsController.fetchTransactions(address, true); } // Set initial load to false after first fetch const timer = setTimeout(() => setInitialLoad(false), 100); diff --git a/packages/bitcoin/src/adapter.ts b/packages/bitcoin/src/adapter.ts index 09bacdb2..66770cdc 100644 --- a/packages/bitcoin/src/adapter.ts +++ b/packages/bitcoin/src/adapter.ts @@ -3,6 +3,7 @@ import { WalletConnector, type AppKitNetwork, type CaipAddress, + type ChainNamespace, type GetBalanceParams, type GetBalanceResponse } from '@reown/appkit-common-react-native'; @@ -10,7 +11,7 @@ import { BitcoinApi } from './utils/BitcoinApi'; import { UnitsUtil } from './utils/UnitsUtil'; export class BitcoinAdapter extends BlockchainAdapter { - private static supportedNamespace: string = 'bip122'; + private static supportedNamespace: ChainNamespace = 'bip122'; private static api = BitcoinApi; constructor(configParams: { projectId: string }) { @@ -94,7 +95,7 @@ export class BitcoinAdapter extends BlockchainAdapter { return provider.request({ method, params }); } - getSupportedNamespace(): string { + getSupportedNamespace(): ChainNamespace { return BitcoinAdapter.supportedNamespace; } diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index da21e1a2..d269accc 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -4,7 +4,7 @@ export type CaipAddress = `${string}:${string}:${string}`; export type CaipNetworkId = `${string}:${string}`; -export type ChainNamespace = 'eip155' | 'solana' | 'polkadot' | 'bip122' | string; +export type ChainNamespace = 'eip155' | 'solana' | 'polkadot' | 'bip122'; export type AppKitNetwork = { // Core viem/chain properties @@ -133,18 +133,17 @@ export interface ThemeVariables { export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; //********** Adapter Types **********// - export abstract class BlockchainAdapter extends EventEmitter { public projectId: string; public connector?: WalletConnector; - public supportedNamespace: string; + public supportedNamespace: ChainNamespace; constructor({ projectId, supportedNamespace }: { projectId: string; - supportedNamespace: string; + supportedNamespace: ChainNamespace; }) { super(); this.projectId = projectId; @@ -161,13 +160,15 @@ export abstract class BlockchainAdapter extends EventEmitter { abstract disconnect(): Promise; abstract request(method: string, params?: any[]): Promise; - abstract getSupportedNamespace(): string; + abstract getSupportedNamespace(): ChainNamespace; abstract getBalance(params: GetBalanceParams): Promise; abstract getAccounts(): CaipAddress[] | undefined; abstract switchNetwork(network: AppKitNetwork): Promise; } -export abstract class EVMAdapter extends BlockchainAdapter {} +export abstract class EVMAdapter extends BlockchainAdapter { + // ens logic +} export abstract class SolanaBaseAdapter extends BlockchainAdapter {} @@ -176,9 +177,12 @@ export interface GetBalanceParams { network?: AppKitNetwork; } +type ContractAddress = CaipAddress; + export interface GetBalanceResponse { amount: string; symbol: string; + contractAddress?: ContractAddress; } //********** Connector Types **********// diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 5a7f7f26..17e3d9a6 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -37,10 +37,12 @@ import type { import { OptionsController } from './OptionsController'; import { ConstantsUtil } from '../utils/ConstantsUtil'; import { ApiUtil } from '../utils/ApiUtil'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; +import { ConnectionsController } from './ConnectionsController'; +import { SnackController } from './SnackController'; // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getBlockchainApiUrl(); -const stagingUrl = CoreHelperUtil.getBlockchainStagingApiUrl(); const getHeaders = () => { const { sdkType, sdkVersion } = OptionsController.state; @@ -58,22 +60,54 @@ const getHeaders = () => { export interface BlockchainApiControllerState { clientId: string | null; api: FetchUtil; - stageApi: FetchUtil; + supportedChains: { http: CaipNetworkId[]; ws: CaipNetworkId[] }; } // -- State --------------------------------------------- // const state = proxy({ clientId: null, api: new FetchUtil({ baseUrl }), - //TODO: remove this before release - stageApi: new FetchUtil({ baseUrl: stagingUrl }) + supportedChains: { http: [], ws: [] } }); // -- Controller ---------------------------------------- // export const BlockchainApiController = { state, - fetchIdentity({ address }: BlockchainApiIdentityRequest) { + async isNetworkSupported(networkId?: CaipNetworkId) { + if (!networkId) { + return false; + } + try { + if (!state.supportedChains.http.length) { + await BlockchainApiController.getSupportedNetworks(); + } + } catch (e) { + return false; + } + + return state.supportedChains.http.includes(networkId); + }, + + async getSupportedNetworks() { + const supportedChains = await state.api.get({ + path: 'v1/supported-chains' + }); + + state.supportedChains = supportedChains!; + + return supportedChains; + }, + + async fetchIdentity({ address }: BlockchainApiIdentityRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { avatar: '', name: '' }; + } + return state.api.get({ path: `/v1/identity/${address}`, params: { @@ -83,29 +117,48 @@ export const BlockchainApiController = { }); }, - fetchTransactions({ + async fetchTransactions({ account, projectId, cursor, onramp, signal, - cache + cache, + chainId }: BlockchainApiTransactionsRequest) { - return state.api.get({ + const _chainId = chainId ?? ConnectionsController.state.activeCaipNetworkId; + const isSupported = await BlockchainApiController.isNetworkSupported(_chainId); + + if (!isSupported) { + return { data: [], next: undefined }; + } + + const response = await state.api.get({ path: `/v1/account/${account}/history`, headers: getHeaders(), params: { projectId, cursor, - onramp + onramp, + chainId: _chainId }, signal, cache }); + + return response; }, - fetchTokenPrice({ projectId, addresses }: BlockchainApiTokenPriceRequest) { - return state.api.post({ + async fetchTokenPrice({ projectId, addresses }: BlockchainApiTokenPriceRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { fungibles: [] }; + } + + const response = await state.api.post({ path: '/v1/fungible/price', body: { projectId, @@ -114,9 +167,23 @@ export const BlockchainApiController = { }, headers: getHeaders() }); + + return response; }, - fetchSwapAllowance({ projectId, tokenAddress, userAddress }: BlockchainApiSwapAllowanceRequest) { + async fetchSwapAllowance({ + projectId, + tokenAddress, + userAddress + }: BlockchainApiSwapAllowanceRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { allowance: '0' }; + } + return state.api.get({ path: `/v1/convert/allowance`, params: { @@ -128,7 +195,15 @@ export const BlockchainApiController = { }); }, - fetchGasPrice({ projectId, chainId }: BlockchainApiGasPriceRequest) { + async fetchGasPrice({ projectId, chainId }: BlockchainApiGasPriceRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + throw new Error('Network not supported for Gas Price'); + } + return state.api.get({ path: `/v1/convert/gas-price`, headers: getHeaders(), @@ -139,7 +214,7 @@ export const BlockchainApiController = { }); }, - fetchSwapQuote({ + async fetchSwapQuote({ projectId, amount, userAddress, @@ -147,6 +222,14 @@ export const BlockchainApiController = { to, gasPrice }: BlockchainApiSwapQuoteRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { quotes: [] }; + } + return state.api.get({ path: `/v1/convert/quotes`, headers: getHeaders(), @@ -161,7 +244,15 @@ export const BlockchainApiController = { }); }, - fetchSwapTokens({ projectId, chainId }: BlockchainApiSwapTokensRequest) { + async fetchSwapTokens({ projectId, chainId }: BlockchainApiSwapTokensRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + return { tokens: [] }; + } + return state.api.get({ path: `/v1/convert/tokens`, headers: getHeaders(), @@ -172,13 +263,21 @@ export const BlockchainApiController = { }); }, - generateSwapCalldata({ + async generateSwapCalldata({ amount, from, projectId, to, userAddress }: BlockchainApiGenerateSwapCalldataRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + throw new Error('Network not supported for Swaps'); + } + return state.api.post({ path: '/v1/convert/build-transaction', headers: getHeaders(), @@ -195,12 +294,20 @@ export const BlockchainApiController = { }); }, - generateApproveCalldata({ + async generateApproveCalldata({ from, projectId, to, userAddress }: BlockchainApiGenerateApproveCalldataRequest) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + + if (!isSupported) { + throw new Error('Network not supported for Swaps'); + } + return state.api.get({ path: `/v1/convert/build-approve`, headers: getHeaders(), @@ -214,6 +321,15 @@ export const BlockchainApiController = { }, async getBalance(address: string, chainId?: string, forceUpdate?: string) { + const isSupported = await BlockchainApiController.isNetworkSupported( + ConnectionsController.state.activeCaipNetworkId + ); + if (!isSupported) { + SnackController.showError('Token Balance Unavailable'); + + return { balances: [] }; + } + return state.api.get({ path: `/v1/account/${address}/balance`, headers: getHeaders(), @@ -238,7 +354,7 @@ export const BlockchainApiController = { }, async fetchOnRampCountries() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -249,7 +365,7 @@ export const BlockchainApiController = { }, async fetchOnRampServiceProviders() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers', headers: getHeaders(), params: { @@ -259,7 +375,7 @@ export const BlockchainApiController = { }, async fetchOnRampPaymentMethods(params: { countries?: string }) { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -271,7 +387,7 @@ export const BlockchainApiController = { }, async fetchOnRampCryptoCurrencies(params: { countries?: string }) { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -283,7 +399,7 @@ export const BlockchainApiController = { }, async fetchOnRampFiatCurrencies() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -294,7 +410,7 @@ export const BlockchainApiController = { }, async fetchOnRampFiatLimits() { - return await state.stageApi.get({ + return await state.api.get({ path: '/v1/onramp/providers/properties', headers: getHeaders(), params: { @@ -305,7 +421,7 @@ export const BlockchainApiController = { }, async getOnRampQuotes(body: BlockchainApiOnRampQuotesRequest, signal?: AbortSignal) { - return await state.stageApi.post({ + return await state.api.post({ path: '/v1/onramp/multi/quotes', headers: getHeaders(), body: { @@ -317,7 +433,7 @@ export const BlockchainApiController = { }, async getOnRampWidget(body: BlockchainApiOnRampWidgetRequest, signal?: AbortSignal) { - return await state.stageApi.post({ + return await state.api.post({ path: '/v1/onramp/widget', headers: getHeaders(), body: { diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 7aca2af8..1913f99f 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -4,26 +4,25 @@ import type { AppKitNetwork, BlockchainAdapter, CaipAddress, - CaipNetworkId + CaipNetworkId, + ChainNamespace, + GetBalanceResponse } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // +type Balance = GetBalanceResponse; -interface Balance { - amount: string; - symbol: string; -} - +//TODO: balance could be elsewhere interface Connection { accounts: CaipAddress[]; - balances: Record; + balances: Record; //TODO: make this an array of balances adapter: BlockchainAdapter; chains: CaipNetworkId[]; activeChain: CaipNetworkId; } export interface ConnectionsControllerState { - activeNamespace: string; + activeNamespace: ChainNamespace; connections: Record; networks: AppKitNetwork[]; } @@ -93,6 +92,17 @@ const derivedState = derive( (network.chainNamespace ?? 'eip155') === snap.activeNamespace && network.id?.toString() === connection.activeChain?.split(':')[1] ); + }, + activeCaipNetworkId: (get): CaipNetworkId | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + const connection = snap.connections[snap.activeNamespace]; + + if (!connection) return undefined; + + return connection.activeChain; } }, { @@ -104,7 +114,7 @@ const derivedState = derive( export const ConnectionsController = { state: derivedState, - setActiveNamespace(namespace: string) { + setActiveNamespace(namespace: ChainNamespace) { baseState.activeNamespace = namespace; }, @@ -168,24 +178,15 @@ export const ConnectionsController = { const connection = baseState.connections[namespace]; if (!connection) return; - // console.log('ConnectionController:disconnect - connection', connection); - // Get the current connector from the adapter const connector = connection.adapter.connector; if (!connector) return; - // console.log('ConnectionController:disconnect - connector', connector); - // Find all namespaces that use the same connector const namespacesUsingConnector = Object.keys(baseState.connections).filter( ns => baseState.connections[ns]?.adapter.connector === connector ); - // console.log( - // 'ConnectionController:disconnect - namespacesUsingConnector', - // namespacesUsingConnector - // ); - // Unsubscribe all event listeners from the adapter namespacesUsingConnector.forEach(ns => { const _connection = baseState.connections[ns]; @@ -201,7 +202,5 @@ export const ConnectionsController = { namespacesUsingConnector.forEach(ns => { delete baseState.connections[ns]; }); - - // console.log('ConnectionController:disconnect - baseState.connections', baseState.connections); } }; diff --git a/packages/core/src/controllers/SwapController.ts b/packages/core/src/controllers/SwapController.ts index ce2601af..6a4db8c7 100644 --- a/packages/core/src/controllers/SwapController.ts +++ b/packages/core/src/controllers/SwapController.ts @@ -17,6 +17,7 @@ import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { ConnectionController } from './ConnectionController'; import { TransactionsController } from './TransactionsController'; import { EventsController } from './EventsController'; +import { ConnectionsController } from './ConnectionsController'; // -- Constants ---------------------------------------- // export const INITIAL_GAS_LIMIT = 150000; @@ -158,9 +159,17 @@ export const SwapController = { }, getParams() { - const caipAddress = AccountController.state.caipAddress; - const address = CoreHelperUtil.getPlainAddress(caipAddress); - const networkAddress = NetworkController.getActiveNetworkTokenAddress(); + const { activeAddress, activeNamespace, activeNetwork } = ConnectionsController.state; + const address = CoreHelperUtil.getPlainAddress(activeAddress); + + if (!activeNamespace || !activeNetwork) { + throw new Error('No active namespace or network found to swap the tokens from.'); + } + + const networkAddress = `${activeNetwork.caipNetworkId ?? 'eip155:1'}:${ + ConstantsUtil.NATIVE_TOKEN_ADDRESS[activeNamespace] + }`; + const type = ConnectorController.state.connectedConnector; if (!address) { @@ -178,7 +187,7 @@ export const SwapController = { return { networkAddress, fromAddress: address, - fromCaipAddress: caipAddress, + fromCaipAddress: activeAddress, sourceTokenAddress: state.sourceToken?.address, toTokenAddress: state.toToken?.address, toTokenAmount: state.toTokenAmount, @@ -189,7 +198,7 @@ export const SwapController = { invalidSourceToken, invalidSourceTokenAmount, availableToSwap: - caipAddress && !invalidToToken && !invalidSourceToken && !invalidSourceTokenAmount, + activeAddress && !invalidToToken && !invalidSourceToken && !invalidSourceTokenAmount, isAuthConnector: type === 'AUTH' }; }, diff --git a/packages/core/src/controllers/ThemeController.ts b/packages/core/src/controllers/ThemeController.ts index f3453b00..29514468 100644 --- a/packages/core/src/controllers/ThemeController.ts +++ b/packages/core/src/controllers/ThemeController.ts @@ -1,10 +1,11 @@ +import { Appearance } from 'react-native'; import { proxy, subscribe as sub } from 'valtio'; import type { ThemeMode, ThemeVariables } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface ThemeControllerState { themeMode?: ThemeMode; - themeVariables: ThemeVariables; + themeVariables?: ThemeVariables; } // -- State --------------------------------------------- // @@ -22,10 +23,18 @@ export const ThemeController = { }, setThemeMode(themeMode: ThemeControllerState['themeMode']) { - state.themeMode = themeMode; + if (!themeMode) { + state.themeMode = Appearance.getColorScheme() as ThemeMode; + } else { + state.themeMode = themeMode; + } }, setThemeVariables(themeVariables: ThemeControllerState['themeVariables']) { + if (!themeVariables) { + state.themeVariables = {}; + } + state.themeVariables = { ...state.themeVariables, ...themeVariables }; } }; diff --git a/packages/core/src/controllers/TransactionsController.ts b/packages/core/src/controllers/TransactionsController.ts index 333f1d5c..23679623 100644 --- a/packages/core/src/controllers/TransactionsController.ts +++ b/packages/core/src/controllers/TransactionsController.ts @@ -3,9 +3,9 @@ import { proxy, subscribe as sub } from 'valtio/vanilla'; import { OptionsController } from './OptionsController'; import { EventsController } from './EventsController'; import { SnackController } from './SnackController'; -import { NetworkController } from './NetworkController'; import { BlockchainApiController } from './BlockchainApiController'; import { AccountController } from './AccountController'; +import { ConnectionsController } from './ConnectionsController'; // -- Types --------------------------------------------- // type TransactionByMonthMap = Record; @@ -121,7 +121,7 @@ export const TransactionsController = { }, filterByConnectedChain(transactions: Transaction[]) { - const chainId = NetworkController.state.caipNetwork?.id; + const chainId = ConnectionsController.state.activeCaipNetworkId; const filteredTransactions = transactions.filter( transaction => transaction.metadata.chain === chainId ); diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 9e10b999..47402841 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -1,3 +1,4 @@ +import type { ChainNamespace } from '@reown/appkit-common-react-native'; import type { Features } from './TypeUtil'; const defaultFeatures: Features = { @@ -34,7 +35,12 @@ export const ConstantsUtil = { LINKING_ERROR: 'LINKING_ERROR', - NATIVE_TOKEN_ADDRESS: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + NATIVE_TOKEN_ADDRESS: { + eip155: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + solana: 'So11111111111111111111111111111111111111111', + polkadot: '0x', + bip122: '0x' + } as const satisfies Record, ONRAMP_ERROR_TYPES: OnRampErrorType, diff --git a/packages/core/src/utils/NetworkUtil.ts b/packages/core/src/utils/NetworkUtil.ts index 4795b60f..fcdc446e 100644 --- a/packages/core/src/utils/NetworkUtil.ts +++ b/packages/core/src/utils/NetworkUtil.ts @@ -4,7 +4,7 @@ import { NetworkController } from '../controllers/NetworkController'; import { AccountController } from '../controllers/AccountController'; import { ConnectorController } from '../controllers/ConnectorController'; import { SwapController } from '../controllers/SwapController'; -import type { CaipNetwork } from '@reown/appkit-common-react-native'; +import { type CaipNetwork } from '@reown/appkit-common-react-native'; export const NetworkUtil = { async handleNetworkSwitch(network: CaipNetwork) { diff --git a/packages/core/src/utils/SwapApiUtil.ts b/packages/core/src/utils/SwapApiUtil.ts index be33994b..9156e14c 100644 --- a/packages/core/src/utils/SwapApiUtil.ts +++ b/packages/core/src/utils/SwapApiUtil.ts @@ -8,12 +8,14 @@ import type { } from './TypeUtil'; import { AccountController } from '../controllers/AccountController'; import { ConnectionController } from '../controllers/ConnectionController'; +import { ConnectionsController } from '../controllers/ConnectionsController'; export const SwapApiUtil = { async getTokenList() { + const chainId = ConnectionsController.state.activeNetwork?.caipNetworkId ?? 'eip155:1'; const response = await BlockchainApiController.fetchSwapTokens({ projectId: OptionsController.state.projectId, - chainId: NetworkController.state.caipNetwork?.id + chainId }); const tokens = response?.tokens?.map( @@ -62,14 +64,18 @@ export const SwapApiUtil = { }, async getMyTokensWithBalance(forceUpdate?: string) { - const address = AccountController.state.address; - const chainId = NetworkController.state.caipNetwork?.id; + const { activeAddress, activeNetwork: network } = ConnectionsController.state; + const address = activeAddress?.split(':')[2]; if (!address) { return []; } - const response = await BlockchainApiController.getBalance(address, chainId, forceUpdate); + const response = await BlockchainApiController.getBalance( + address, + network?.caipNetworkId, + forceUpdate + ); const balances = response?.balances.filter(balance => balance.quantity.decimals !== '0'); AccountController.setTokenBalance(balances); diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index d7dc742b..ad5e6f0a 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -171,6 +171,7 @@ export interface BlockchainApiTransactionsRequest { onramp?: 'coinbase'; signal?: AbortSignal; cache?: RequestCache; + chainId?: CaipNetworkId; } export interface BlockchainApiTransactionsResponse { diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 5d6757b9..71882857 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -4,13 +4,14 @@ import { WalletConnector, type AppKitNetwork, type CaipAddress, + type ChainNamespace, type GetBalanceParams, type GetBalanceResponse } from '@reown/appkit-common-react-native'; import { EthersHelpersUtil } from '@reown/appkit-scaffold-utils-react-native'; export class EthersAdapter extends EVMAdapter { - private static supportedNamespace: string = 'eip155'; + private static supportedNamespace: ChainNamespace = 'eip155'; constructor(configParams: { projectId: string }) { super({ @@ -105,7 +106,7 @@ export class EthersAdapter extends EVMAdapter { return provider.request({ method, params }); } - getSupportedNamespace(): string { + getSupportedNamespace(): ChainNamespace { return EthersAdapter.supportedNamespace; } diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index a88bf33a..6096dd11 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -3,13 +3,14 @@ import { WalletConnector, type AppKitNetwork, type CaipAddress, + type ChainNamespace, type GetBalanceParams, type GetBalanceResponse } from '@reown/appkit-common-react-native'; import { Connection, PublicKey } from '@solana/web3.js'; export class SolanaAdapter extends SolanaBaseAdapter { - private static supportedNamespace: string = 'solana'; + private static supportedNamespace: ChainNamespace = 'solana'; constructor(configParams: { projectId: string }) { super({ @@ -90,7 +91,7 @@ export class SolanaAdapter extends SolanaBaseAdapter { return provider.request({ method, params }); } - getSupportedNamespace(): string { + getSupportedNamespace(): ChainNamespace { return SolanaAdapter.supportedNamespace; } diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index 86bae9fa..9da99703 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -3,6 +3,7 @@ import { WalletConnector, type AppKitNetwork, type CaipAddress, + type ChainNamespace, type GetBalanceParams, type GetBalanceResponse } from '@reown/appkit-common-react-native'; @@ -21,7 +22,7 @@ type ConfigParams = Partial & { }; export class WagmiAdapter extends EVMAdapter { - private static supportedNamespace: string = 'eip155'; + private static supportedNamespace: ChainNamespace = 'eip155'; public wagmiChains: readonly [Chain, ...Chain[]] | undefined; public wagmiConfig!: Config; @@ -88,7 +89,7 @@ export class WagmiAdapter extends EVMAdapter { return provider.request({ method, params }); } - getSupportedNamespace(): string { + getSupportedNamespace(): ChainNamespace { return WagmiAdapter.supportedNamespace; } From d9d690f05bf5a0ff168639e9b22f0fa18574be4a Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 6 May 2025 15:26:22 -0300 Subject: [PATCH 16/91] chore: added events and walletinfo hooks --- apps/native/App.tsx | 14 +++++- apps/native/src/views/EventsView.tsx | 33 ++++++++++++ apps/native/src/views/WalletInfoView.tsx | 37 ++++++++++++++ package.json | 1 - packages/appkit/src/AppKit.ts | 50 +++++++++++-------- .../src/connectors/WalletConnectConnector.ts | 18 ++++++- packages/appkit/src/hooks/useAppKitEvents.ts | 47 +++++++++++++++++ packages/appkit/src/hooks/useWalletInfo.ts | 13 +++++ packages/appkit/src/index.ts | 6 ++- packages/common/src/utils/TypeUtil.ts | 24 ++++++++- .../src/controllers/ConnectionsController.ts | 24 +++++++-- packages/core/src/utils/ConstantsUtil.ts | 9 ++-- packages/solana/src/adapter.ts | 2 - 13 files changed, 240 insertions(+), 38 deletions(-) create mode 100644 apps/native/src/views/EventsView.tsx create mode 100644 apps/native/src/views/WalletInfoView.tsx create mode 100644 packages/appkit/src/hooks/useAppKitEvents.ts create mode 100644 packages/appkit/src/hooks/useWalletInfo.ts diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 53239f1f..36634954 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -38,6 +38,8 @@ import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; import { mainnet, polygon, avalanche } from 'viem/chains'; import { ActionsView } from './src/views/ActionsView'; +import { WalletInfoView } from './src/views/WalletInfoView'; +import { EventsView } from './src/views/EventsView'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -141,6 +143,7 @@ export default function Native() { AppKit for React Native + appKit.disconnect()}> Disconnect + @@ -179,9 +183,15 @@ const styles = StyleSheet.create({ marginBottom: 20 }, title: { - marginBottom: 30 + marginBottom: 10 }, button: { - marginVertical: 6 + marginVertical: 16 + }, + walletInfo: { + marginBottom: 10 + }, + events: { + marginTop: 30 } }); diff --git a/apps/native/src/views/EventsView.tsx b/apps/native/src/views/EventsView.tsx new file mode 100644 index 00000000..4733074e --- /dev/null +++ b/apps/native/src/views/EventsView.tsx @@ -0,0 +1,33 @@ +import { useAppKitEvents, useAppKitEventSubscription } from '@reown/appkit-react-native'; +import { FlexView, Text } from '@reown/appkit-ui-react-native'; +import { useState } from 'react'; +import { type ViewStyle, type StyleProp, StyleSheet } from 'react-native'; + +interface Props { + style?: StyleProp; +} + +export function EventsView({ style }: Props) { + const { data } = useAppKitEvents(); + const [eventCount, setEventCount] = useState(0); + + useAppKitEventSubscription('MODAL_OPEN', () => { + setEventCount(prev => prev + 1); + }); + + return data ? ( + + + Events + + Last event: {data?.event} + Modal open count: {eventCount} + + ) : null; +} + +const styles = StyleSheet.create({ + title: { + marginBottom: 6 + } +}); diff --git a/apps/native/src/views/WalletInfoView.tsx b/apps/native/src/views/WalletInfoView.tsx new file mode 100644 index 00000000..a59c28e3 --- /dev/null +++ b/apps/native/src/views/WalletInfoView.tsx @@ -0,0 +1,37 @@ +import { Image, StyleSheet, StyleProp, ViewStyle } from 'react-native'; +import { useWalletInfo } from '@reown/appkit-react-native'; +import { FlexView, Text } from '@reown/appkit-ui-react-native'; + +interface Props { + style?: StyleProp; +} + +export function WalletInfoView({ style }: Props) { + const { walletInfo } = useWalletInfo(); + + return walletInfo ? ( + + + Connected to + + + {walletInfo?.icons?.[0] && ( + + )} + {walletInfo?.name && {walletInfo?.name}} + + + ) : null; +} + +const styles = StyleSheet.create({ + label: { + marginBottom: 2 + }, + logo: { + width: 20, + height: 20, + borderRadius: 5, + marginRight: 4 + } +}); diff --git a/package.json b/package.json index d3b580fa..e2a1873b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ "packages/wallet", "packages/scaffold-utils", "packages/siwe", - "packages/wagmi", "packages/coinbase-wagmi", "packages/auth-wagmi", "packages/auth-ethers", diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 7f9b15b0..00561b8e 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -9,8 +9,7 @@ import { type Metadata, StorageUtil, type OptionsControllerState, - ThemeController, - type Features + ThemeController } from '@reown/appkit-core-react-native'; import type { @@ -23,7 +22,8 @@ import type { AppKitNetwork, Provider, ThemeVariables, - ThemeMode + ThemeMode, + WalletInfo } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; @@ -47,7 +47,7 @@ interface AppKitConfig { debug?: OptionsControllerState['debug']; themeMode?: ThemeMode; themeVariables?: ThemeVariables; - features?: Features; + // features?: Features; siweConfig?: AppKitSIWEClient; // defaultChain?: NetworkControllerState['caipNetwork']; // chainImages?: Record; @@ -102,6 +102,8 @@ export class AppKit { const connector = await this.createConnector(connected.type); const namespaces = connector.getNamespaces(); + const walletInfo = connector.getWalletInfo(); + if (namespaces && Object.keys(namespaces).length > 0) { // Ensure namespaces is not empty // Setup adapters and subscribe to events @@ -112,7 +114,7 @@ export class AppKit { // If adapters were successfully initialized, store the connection details if (initializedAdapters.length > 0) { - this._storeConnectionDetails(initializedAdapters, namespaces); + this._storeConnectionDetails(initializedAdapters, namespaces, walletInfo); } this.syncAccounts(initializedAdapters); @@ -169,6 +171,8 @@ export class AppKit { const connector = await this.createConnector(type); const approvedNamespaces = await connector.connect(requestedNamespaces ?? this.namespaces); + const walletInfo = connector.getWalletInfo(); + if (!approvedNamespaces || Object.keys(approvedNamespaces).length === 0) { throw new Error('Connection cancelled or failed: No approved namespaces returned.'); } @@ -186,7 +190,7 @@ export class AppKit { } // Store the connection details for the successfully connected adapters - this._storeConnectionDetails(approvedAdapters, approvedNamespaces); + this._storeConnectionDetails(approvedAdapters, approvedNamespaces, walletInfo); // Store connector type and namespaces in storage await StorageUtil.setConnectedConnectors({ @@ -227,7 +231,11 @@ export class AppKit { * @param adapters - The adapters for which to store the connection. * @param approvedNamespaces - The map of approved namespaces and their details. */ - private _storeConnectionDetails(adapters: BlockchainAdapter[], approvedNamespaces: Namespaces) { + private _storeConnectionDetails( + adapters: BlockchainAdapter[], + approvedNamespaces: Namespaces, + wallet?: WalletInfo + ) { adapters.forEach(async adapter => { const namespace = adapter.getSupportedNamespace(); const namespaceDetails = approvedNamespaces[namespace]; @@ -236,11 +244,13 @@ export class AppKit { const accounts = namespaceDetails.accounts ?? []; const chains = namespaceDetails.chains ?? []; + console.log('walletInfo', walletInfo); ConnectionsController.storeConnection({ namespace, adapter, accounts, - chains + chains, + wallet }); }); @@ -262,13 +272,10 @@ export class AppKit { }); adapter.on('disconnect', ({ namespace }) => { - ConnectionsController.disconnect(namespace); - // Potentially remove from storage on disconnect event as well - // StorageUtil.removeConnectedConnectors(connectorType); // Need connectorType here + this.disconnect(namespace, false); }); adapter.on('balanceChanged', ({ namespace, address, balance }) => { - // console.log('balanceChanged', namespace, address, balance); ConnectionsController.updateBalance(namespace, address, balance); }); } @@ -283,7 +290,7 @@ export class AppKit { OptionsController.setCustomWallets(options.customWallets); OptionsController.setEnableAnalytics(options.enableAnalytics); OptionsController.setDebug(options.debug); - OptionsController.setFeatures(options.features); + // OptionsController.setFeatures(options.features); ThemeController.setThemeMode(options.themeMode); ThemeController.setThemeVariables(options.themeVariables); @@ -301,15 +308,15 @@ export class AppKit { SIWEController.setSIWEClient(options.siweConfig); } - if ( - (options.features?.onramp === true || options.features?.onramp === undefined) && - (options.metadata?.redirect?.universal || options.metadata?.redirect?.native) - ) { - OptionsController.setIsOnRampEnabled(true); - } + // if ( + // (options.features?.onramp === true || options.features?.onramp === undefined) && + // (options.metadata?.redirect?.universal || options.metadata?.redirect?.native) + // ) { + // OptionsController.setIsOnRampEnabled(true); + // } } - async disconnect(namespace?: string): Promise { + async disconnect(namespace?: string, isInternal?: boolean): Promise { try { const connection = ConnectionsController.state.connections[ @@ -318,7 +325,8 @@ export class AppKit { const connectorType = connection?.adapter?.connector?.type; await ConnectionsController.disconnect( - namespace ?? ConnectionsController.state.activeNamespace + namespace ?? ConnectionsController.state.activeNamespace, + isInternal ); if (connectorType) { diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 2b5ac8a8..921795f2 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -5,7 +5,8 @@ import { type AppKitNetwork, type Namespaces, type ProposalNamespaces, - type Provider + type Provider, + type WalletInfo } from '@reown/appkit-common-react-native'; export class WalletConnectConnector extends WalletConnector { @@ -15,6 +16,17 @@ export class WalletConnectConnector extends WalletConnector { if (provider.session?.namespaces) { this.namespaces = provider.session.namespaces as Namespaces; } + + if (provider.session?.peer?.metadata) { + const metadata = provider.session?.peer.metadata; + if (metadata) { + this.wallet = { + ...metadata, + name: metadata.name, + icon: metadata.icons?.[0] + }; + } + } } public static async create({ @@ -69,4 +81,8 @@ export class WalletConnectConnector extends WalletConnector { return Promise.resolve(); } + + override getWalletInfo(): WalletInfo | undefined { + return this.wallet; + } } diff --git a/packages/appkit/src/hooks/useAppKitEvents.ts b/packages/appkit/src/hooks/useAppKitEvents.ts new file mode 100644 index 00000000..d5f510bd --- /dev/null +++ b/packages/appkit/src/hooks/useAppKitEvents.ts @@ -0,0 +1,47 @@ +import { useEffect } from 'react'; +import { useSnapshot } from 'valtio'; +import { + EventsController, + OptionsController, + type EventName, + type EventsControllerState +} from '@reown/appkit-core-react-native'; + +export function useAppKitEvents(callback?: (newEvent: EventsControllerState) => void) { + const { projectId } = useSnapshot(OptionsController.state); + const { data, timestamp } = useSnapshot(EventsController.state); + + if (!projectId) { + throw new Error('Please call "createAppKit" before using "useAppKitEvents" hook'); + } + + useEffect(() => { + const unsubscribe = EventsController.subscribe(newEvent => { + callback?.(newEvent); + }); + + return () => { + unsubscribe?.(); + }; + }, [callback]); + + return { data, timestamp }; +} + +export function useAppKitEventSubscription( + event: EventName, + callback: (newEvent: EventsControllerState) => void +) { + const { projectId } = useSnapshot(OptionsController.state); + if (!projectId) { + throw new Error('Please call "createAppKit" before using "useAppKitEventSubscription" hook'); + } + + useEffect(() => { + const unsubscribe = EventsController?.subscribeEvent(event, callback); + + return () => { + unsubscribe?.(); + }; + }, [callback, event]); +} diff --git a/packages/appkit/src/hooks/useWalletInfo.ts b/packages/appkit/src/hooks/useWalletInfo.ts new file mode 100644 index 00000000..faba92ee --- /dev/null +++ b/packages/appkit/src/hooks/useWalletInfo.ts @@ -0,0 +1,13 @@ +import { useSnapshot } from 'valtio'; +import { ConnectionsController, OptionsController } from '@reown/appkit-core-react-native'; + +export function useWalletInfo() { + const { projectId } = useSnapshot(OptionsController.state); + const { walletInfo } = useSnapshot(ConnectionsController.state); + + if (!projectId) { + throw new Error('Please call "createAppKit" before using "useWalletInfo" hook'); + } + + return { walletInfo }; +} diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 5c209b5c..aa225183 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -23,6 +23,10 @@ export { CoreHelperUtil } from '@reown/appkit-core-react-native'; export * from './AppKit'; export * from './networks'; export { AppKitProvider, useAppKit } from './AppKitContext'; +export { WalletConnectConnector } from './connectors/WalletConnectConnector'; + +/****** Hooks *******/ export { useProvider } from './hooks/useProvider'; export { useAppKitAccount } from './hooks/useAppKitAccount'; -export { WalletConnectConnector } from './connectors/WalletConnectConnector'; +export { useWalletInfo } from './hooks/useWalletInfo'; +export { useAppKitEvents, useAppKitEventSubscription } from './hooks/useAppKitEvents'; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index d269accc..2670f347 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -158,6 +158,12 @@ export abstract class BlockchainAdapter extends EventEmitter { this.connector = undefined; } + getProvider(): Provider { + if (!this.connector) throw new Error('No active connector'); + + return this.connector.getProvider(); + } + abstract disconnect(): Promise; abstract request(method: string, params?: any[]): Promise; abstract getSupportedNamespace(): ChainNamespace; @@ -206,6 +212,7 @@ export abstract class WalletConnector extends EventEmitter { public type: New_ConnectorType; protected provider: Provider; protected namespaces?: Namespaces; + protected wallet?: WalletInfo; constructor({ type, provider }: { type: New_ConnectorType; provider: Provider }) { super(); @@ -217,6 +224,7 @@ export abstract class WalletConnector extends EventEmitter { abstract disconnect(): Promise; abstract getProvider(): Provider; abstract getNamespaces(): Namespaces; + abstract getWalletInfo(): WalletInfo | undefined; abstract switchNetwork(network: AppKitNetwork): Promise; } @@ -239,7 +247,7 @@ export interface RequestArguments { params?: unknown[] | Record | object | undefined; } -//TODO: rename this and remove the old one +//TODO: rename this and remove the old one ConnectorType export type New_ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; //********** Others **********// @@ -249,3 +257,17 @@ export interface ConnectionResponse { chainId: string; [key: string]: any; } + +export interface WalletInfo { + name?: string; + icon?: string; + description?: string; + url?: string; + icons?: string[]; + redirect?: { + native?: string; + universal?: string; + linkMode?: boolean; + }; + [key: string]: unknown; +} diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 1913f99f..fa1fef46 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -6,7 +6,8 @@ import type { CaipAddress, CaipNetworkId, ChainNamespace, - GetBalanceResponse + GetBalanceResponse, + WalletInfo } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // @@ -19,6 +20,7 @@ interface Connection { adapter: BlockchainAdapter; chains: CaipNetworkId[]; activeChain: CaipNetworkId; + wallet?: WalletInfo; } export interface ConnectionsControllerState { @@ -103,6 +105,13 @@ const derivedState = derive( if (!connection) return undefined; return connection.activeChain; + }, + walletInfo: (get): WalletInfo | undefined => { + const snap = get(baseState); + + if (!snap.activeNamespace) return undefined; + + return snap.connections[snap.activeNamespace]?.wallet; } }, { @@ -122,19 +131,22 @@ export const ConnectionsController = { namespace, adapter, accounts, - chains + chains, + wallet }: { namespace: string; adapter: BlockchainAdapter; accounts: CaipAddress[]; chains: CaipNetworkId[]; + wallet?: WalletInfo; }) { baseState.connections[namespace] = { balances: {}, activeChain: chains[0]!, adapter: ref(adapter), accounts, - chains + chains, + wallet }; }, @@ -174,7 +186,7 @@ export const ConnectionsController = { ); }, - async disconnect(namespace: string) { + async disconnect(namespace: string, isInternal = true) { const connection = baseState.connections[namespace]; if (!connection) return; @@ -196,7 +208,9 @@ export const ConnectionsController = { }); // Disconnect the adapter - await connection.adapter.disconnect(); + if (isInternal) { + await connection.adapter.disconnect(); + } // Remove all namespaces that used this connector namespacesUsingConnector.forEach(ns => { diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 47402841..840e195d 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -1,11 +1,12 @@ import type { ChainNamespace } from '@reown/appkit-common-react-native'; import type { Features } from './TypeUtil'; +//TODO: enable this again after implemented const defaultFeatures: Features = { - swaps: true, - onramp: true, - email: true, - emailShowWallets: true, + swaps: false, + onramp: false, + email: false, + emailShowWallets: false, socials: ['x', 'discord', 'apple'] }; diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 6096dd11..0ffff3f2 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -113,7 +113,6 @@ export class SolanaAdapter extends SolanaBaseAdapter { } onDisconnect(): void { - // console.log('SolanaAdapter - onDisconnect'); this.emit('disconnect', { namespace: this.getSupportedNamespace() }); //the connector might be shared between adapters. Validate this @@ -136,7 +135,6 @@ export class SolanaAdapter extends SolanaBaseAdapter { const provider = this.connector?.getProvider(); if (!provider) return; - // console.log('SolanaAdapter - subscribing to events'); provider.on('chainChanged', this.onChainChanged.bind(this)); provider.on('accountsChanged', this.onAccountsChanged.bind(this)); provider.on('disconnect', this.onDisconnect.bind(this)); From 384abc997d03ec44738031c9341f7a5ac6145508 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 6 May 2025 16:30:51 -0300 Subject: [PATCH 17/91] chore: improved hooks --- apps/native/src/views/ActionsView.tsx | 4 +-- apps/native/src/views/BitcoinActionsView.tsx | 8 ++--- apps/native/src/views/EthersActionsView.tsx | 7 ++--- apps/native/src/views/SolanaActionsView.tsx | 8 ++--- packages/appkit/src/AppKit.ts | 21 +++++++------- packages/appkit/src/AppKitContext.tsx | 14 +++++++-- .../{useAppKitAccount.ts => useAccount.ts} | 4 +-- packages/appkit/src/hooks/useAppKit.ts | 29 +++++++++++++++++++ packages/appkit/src/hooks/useProvider.ts | 6 ++-- packages/appkit/src/index.ts | 5 ++-- .../views/w3m-account-default-view/index.tsx | 4 +-- .../src/views/w3m-connecting-view/index.tsx | 4 +-- .../src/views/w3m-networks-view/index.tsx | 5 ++-- .../w3m-unsupported-chain-view/index.tsx | 4 +-- 14 files changed, 78 insertions(+), 45 deletions(-) rename packages/appkit/src/hooks/{useAppKitAccount.ts => useAccount.ts} (91%) create mode 100644 packages/appkit/src/hooks/useAppKit.ts diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index 7f4da3d8..8fb4e34a 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -1,13 +1,13 @@ import { StyleSheet } from 'react-native'; import { FlexView } from '@reown/appkit-ui-react-native'; -import { useAppKitAccount } from '@reown/appkit-react-native'; +import { useAccount } from '@reown/appkit-react-native'; import { EthersActionsView } from './EthersActionsView'; import { SolanaActionsView } from './SolanaActionsView'; import { BitcoinActionsView } from './BitcoinActionsView'; export function ActionsView() { const isConnected = true; - const { chainId } = useAppKitAccount(); + const { chainId } = useAccount(); return isConnected ? ( diff --git a/apps/native/src/views/BitcoinActionsView.tsx b/apps/native/src/views/BitcoinActionsView.tsx index 6a0dd10e..104917a2 100644 --- a/apps/native/src/views/BitcoinActionsView.tsx +++ b/apps/native/src/views/BitcoinActionsView.tsx @@ -1,16 +1,14 @@ import { StyleSheet } from 'react-native'; import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; -import { useAppKit, useAppKitAccount } from '@reown/appkit-react-native'; +import { useAccount, useProvider } from '@reown/appkit-react-native'; import { ToastUtils } from '../utils/ToastUtils'; import { BitcoinUtil, SignPSBTResponse } from '../utils/BitcoinUtil'; export function BitcoinActionsView() { const isConnected = true; - const { appKit } = useAppKit(); - const { address, chainId } = useAppKitAccount(); - - const provider = appKit?.getProvider('bip122'); + const { address, chainId } = useAccount(); + const provider = useProvider('bip122'); const onSignSuccess = (data: string) => { ToastUtils.showSuccessToast('Sign successful', data); diff --git a/apps/native/src/views/EthersActionsView.tsx b/apps/native/src/views/EthersActionsView.tsx index 0d925cc5..c0e60963 100644 --- a/apps/native/src/views/EthersActionsView.tsx +++ b/apps/native/src/views/EthersActionsView.tsx @@ -1,15 +1,14 @@ import { StyleSheet } from 'react-native'; import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; -import { useAppKit, useAppKitAccount } from '@reown/appkit-react-native'; +import { useAccount, useProvider } from '@reown/appkit-react-native'; import { hexlify, isHexString, toUtf8Bytes } from 'ethers'; import { ToastUtils } from '../utils/ToastUtils'; export function EthersActionsView() { const isConnected = true; - const { appKit } = useAppKit(); - const { address, chainId } = useAppKitAccount(); - const provider = appKit?.getProvider('eip155'); + const { address, chainId } = useAccount(); + const provider = useProvider('eip155'); const onSignSuccess = (data: any) => { ToastUtils.showSuccessToast('Sign successful', data); diff --git a/apps/native/src/views/SolanaActionsView.tsx b/apps/native/src/views/SolanaActionsView.tsx index cd66e59a..5258fb03 100644 --- a/apps/native/src/views/SolanaActionsView.tsx +++ b/apps/native/src/views/SolanaActionsView.tsx @@ -1,16 +1,14 @@ import { StyleSheet } from 'react-native'; import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; -import { useAppKit, useAppKitAccount } from '@reown/appkit-react-native'; +import { useAccount, useProvider } from '@reown/appkit-react-native'; import base58 from 'bs58'; import { ToastUtils } from '../utils/ToastUtils'; export function SolanaActionsView() { const isConnected = true; - const { appKit } = useAppKit(); - const { address, chainId } = useAppKitAccount(); - - const provider = appKit?.getProvider('solana'); + const { address, chainId } = useAccount(); + const provider = useProvider('solana'); const onSignSuccess = (data: any) => { ToastUtils.showSuccessToast('Sign successful', data); diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 00561b8e..a878e8aa 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -30,6 +30,7 @@ import { WalletConnectConnector } from './connectors/WalletConnectConnector'; import { WcHelpersUtil } from './utils/HelpersUtil'; import { NetworkUtil } from './utils/NetworkUtil'; import { SIWEController, type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; +import type { OpenOptions } from './client'; interface AppKitConfig { projectId: string; @@ -244,7 +245,6 @@ export class AppKit { const accounts = namespaceDetails.accounts ?? []; const chains = namespaceDetails.chains ?? []; - console.log('walletInfo', walletInfo); ConnectionsController.storeConnection({ namespace, adapter, @@ -363,16 +363,7 @@ export class AppKit { return connection.adapter.connector.getProvider() as T; } - getActiveAdapter(): BlockchainAdapter | null { - const activeNamespace = ConnectionsController.state.activeNamespace; - if (!activeNamespace) return null; - - const connection = ConnectionsController.state.connections[activeNamespace]; - - return connection?.adapter ?? null; - } - - getAdapterByNamespace(namespace: string = 'eip155'): BlockchainAdapter | null { + private getAdapterByNamespace(namespace: string = 'eip155'): BlockchainAdapter | null { const namespaceConnection = ConnectionsController.state.connections[namespace]; return namespaceConnection?.adapter ?? null; @@ -403,6 +394,14 @@ export class AppKit { adapter.getBalance({ network }); } + + open(options?: OpenOptions) { + ModalController.open(options); + } + + close() { + ModalController.close(); + } } export function createAppKit(config: AppKitConfig): AppKit { diff --git a/packages/appkit/src/AppKitContext.tsx b/packages/appkit/src/AppKitContext.tsx index 5974007e..21f8358b 100644 --- a/packages/appkit/src/AppKitContext.tsx +++ b/packages/appkit/src/AppKitContext.tsx @@ -5,7 +5,7 @@ interface AppKitContextType { appKit: AppKit | null; } -const AppKitContext = createContext({ appKit: null }); +export const AppKitContext = createContext({ appKit: null }); interface AppKitProviderProps { children: ReactNode; @@ -16,7 +16,8 @@ export const AppKitProvider: React.FC = ({ children, instan return {children}; }; -export const useAppKit = (): { appKit: AppKit } => { +//TODO: rename this so it doesn't conflict with the useAppKit hook in the hooks folder +export const useAppKit = () => { const context = useContext(AppKitContext); if (context === undefined) { throw new Error('useAppKit must be used within an AppKitProvider'); @@ -26,5 +27,12 @@ export const useAppKit = (): { appKit: AppKit } => { throw new Error('AppKit instance is not yet available in context.'); } - return { appKit: context.appKit }; + return { + connect: context.appKit.connect.bind(context.appKit), + disconnect: context.appKit.disconnect.bind(context.appKit), + open: context.appKit.open.bind(context.appKit), + close: context.appKit.close.bind(context.appKit), + switchNetwork: context.appKit.switchNetwork.bind(context.appKit), + getProvider: context.appKit.getProvider.bind(context.appKit) + }; }; diff --git a/packages/appkit/src/hooks/useAppKitAccount.ts b/packages/appkit/src/hooks/useAccount.ts similarity index 91% rename from packages/appkit/src/hooks/useAppKitAccount.ts rename to packages/appkit/src/hooks/useAccount.ts index dc7ab5e6..eb447c4b 100644 --- a/packages/appkit/src/hooks/useAppKitAccount.ts +++ b/packages/appkit/src/hooks/useAccount.ts @@ -1,7 +1,7 @@ -import { ConnectionsController } from '@reown/appkit-core-react-native'; import { useSnapshot } from 'valtio'; +import { ConnectionsController } from '@reown/appkit-core-react-native'; -export function useAppKitAccount() { +export function useAccount() { const { activeAddress: address, activeNamespace, diff --git a/packages/appkit/src/hooks/useAppKit.ts b/packages/appkit/src/hooks/useAppKit.ts new file mode 100644 index 00000000..cdd4bc73 --- /dev/null +++ b/packages/appkit/src/hooks/useAppKit.ts @@ -0,0 +1,29 @@ +import { useContext } from 'react'; +import type { AppKit } from '../AppKit'; +import { AppKitContext } from '../AppKitContext'; + +interface UseAppKitReturn { + open: AppKit['open']; + close: AppKit['close']; + disconnect: (namespace?: string) => void; + switchNetwork: AppKit['switchNetwork']; +} + +export const useAppKit = (): UseAppKitReturn => { + const context = useContext(AppKitContext); + + if (context === undefined) { + throw new Error('useAppKit must be used within an AppKitProvider'); + } + if (!context.appKit) { + // This might happen if the provider is rendered before AppKit is initialized + throw new Error('AppKit instance is not yet available in context.'); + } + + return { + open: context.appKit.open.bind(context.appKit), + close: context.appKit.close.bind(context.appKit), + disconnect: (namespace?: string) => context.appKit?.disconnect.bind(context.appKit)(namespace), + switchNetwork: context.appKit.switchNetwork.bind(context.appKit) + }; +}; diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts index f38d75ae..7d05c682 100644 --- a/packages/appkit/src/hooks/useProvider.ts +++ b/packages/appkit/src/hooks/useProvider.ts @@ -1,11 +1,11 @@ import { useSnapshot } from 'valtio'; import { ConnectionsController } from '@reown/appkit-core-react-native'; -export function useProvider(namespace?: string): T | null { +export function useProvider(namespace?: string) { const { connections, activeNamespace } = useSnapshot(ConnectionsController.state); const connection = connections[namespace ?? activeNamespace]; - if (!connection) return null; + if (!connection) return undefined; - return connection.adapter.connector?.getProvider() as T; + return connection.adapter.connector?.getProvider(); } diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index aa225183..8cce9b03 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -22,11 +22,12 @@ export { CoreHelperUtil } from '@reown/appkit-core-react-native'; export * from './AppKit'; export * from './networks'; -export { AppKitProvider, useAppKit } from './AppKitContext'; +export { AppKitProvider } from './AppKitContext'; export { WalletConnectConnector } from './connectors/WalletConnectConnector'; /****** Hooks *******/ +export { useAppKit } from './hooks/useAppKit'; export { useProvider } from './hooks/useProvider'; -export { useAppKitAccount } from './hooks/useAppKitAccount'; +export { useAccount } from './hooks/useAccount'; export { useWalletInfo } from './hooks/useWalletInfo'; export { useAppKitEvents, useAppKitEventSubscription } from './hooks/useAppKitEvents'; diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 84651909..a27f5ef2 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -61,11 +61,11 @@ export function AccountDefaultView() { const showBack = history.length > 1; const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); const { padding } = useCustomDimensions(); - const { appKit } = useAppKit(); + const { disconnect } = useAppKit(); async function onDisconnect() { setDisconnecting(true); - await appKit?.disconnect(ConnectionsController.state.activeNamespace); + await disconnect(ConnectionsController.state.activeNamespace); setDisconnecting(false); } diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 4b8b3347..8bd17423 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -22,7 +22,7 @@ import { ConnectingHeader } from '../../partials/w3m-connecting-header'; import { UiUtil } from '../../utils/UiUtil'; export function ConnectingView() { - const { appKit } = useAppKit(); + const { connect } = useAppKit(); const { installed } = useSnapshot(ApiController.state); const { data } = RouterController.state; const [lastRetry, setLastRetry] = useState(Date.now()); @@ -51,7 +51,7 @@ export function ConnectingView() { // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); //TODO: check linkmode - const wcPromise = appKit?.connect('walletconnect'); + const wcPromise = connect('walletconnect'); ConnectionController.setWcPromise(wcPromise); await wcPromise; // await ConnectionController.state.wcPromise; diff --git a/packages/appkit/src/views/w3m-networks-view/index.tsx b/packages/appkit/src/views/w3m-networks-view/index.tsx index c1cd35c3..4fff357d 100644 --- a/packages/appkit/src/views/w3m-networks-view/index.tsx +++ b/packages/appkit/src/views/w3m-networks-view/index.tsx @@ -20,6 +20,7 @@ import type { AppKitNetwork } from '@reown/appkit-common-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; import { useAppKit } from '../../AppKitContext'; + export function NetworksView() { const { caipNetwork } = NetworkController.state; const imageHeaders = ApiController._getApiHeaders(); @@ -30,7 +31,7 @@ export function NetworksView() { const itemGap = Math.abs( Math.trunc((usableWidth - numColumns * CardSelectWidth) / numColumns) / 2 ); - const { appKit } = useAppKit(); + const { switchNetwork } = useAppKit(); const onHelpPress = () => { RouterController.push('WhatIsANetwork'); @@ -43,7 +44,7 @@ export function NetworksView() { const networks = ConnectionsController.getConnectedNetworks(); const onNetworkPress = async (network: AppKitNetwork) => { - await appKit.switchNetwork(network); + await switchNetwork(network); RouterController.goBack(); }; diff --git a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx index 3b01b6aa..1c200c17 100644 --- a/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx +++ b/packages/appkit/src/views/w3m-unsupported-chain-view/index.tsx @@ -25,7 +25,7 @@ export function UnsupportedChainView() { const [disconnecting, setDisconnecting] = useState(false); const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); const imageHeaders = ApiController._getApiHeaders(); - const { appKit } = useAppKit(); + const { disconnect } = useAppKit(); const onNetworkPress = async (network: CaipNetwork) => { //TODO: change to appkit switchNetwork @@ -43,7 +43,7 @@ export function UnsupportedChainView() { const onDisconnect = async () => { setDisconnecting(true); - await appKit?.disconnect(ConnectionsController.state.activeNamespace); + await disconnect(ConnectionsController.state.activeNamespace); setDisconnecting(false); }; From 886a216403229623892fe0dba432351f3ec397a3 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 7 May 2025 12:25:23 -0300 Subject: [PATCH 18/91] chore: added default chain prop --- apps/native/App.tsx | 1 + packages/appkit/src/AppKit.ts | 337 ++++++++++-------- .../src/connectors/WalletConnectConnector.ts | 31 +- packages/appkit/src/hooks/useAccount.ts | 3 +- packages/appkit/src/hooks/useProvider.ts | 6 +- packages/appkit/src/utils/NetworkUtil.ts | 14 + packages/common/src/utils/TypeUtil.ts | 6 +- .../src/controllers/ConnectionController.ts | 2 +- .../src/controllers/ConnectionsController.ts | 24 +- packages/core/src/utils/StorageUtil.ts | 39 +- 10 files changed, 295 insertions(+), 168 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 36634954..cdcf7fd1 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -118,6 +118,7 @@ const appKit = createAppKit({ adapters: [ethersAdapter, solanaAdapter, bitcoinAdapter], metadata, networks: [mainnet, polygon, avalanche, solana, bitcoin, bitcoinTestnet], + defaultChain: polygon, clipboardClient, debug: true, enableAnalytics: true diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index a878e8aa..464fa98d 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -9,7 +9,8 @@ import { type Metadata, StorageUtil, type OptionsControllerState, - ThemeController + ThemeController, + ConnectionController } from '@reown/appkit-core-react-native'; import type { @@ -50,7 +51,7 @@ interface AppKitConfig { themeVariables?: ThemeVariables; // features?: Features; siweConfig?: AppKitSIWEClient; - // defaultChain?: NetworkControllerState['caipNetwork']; + defaultChain?: AppKitNetwork; // chainImages?: Record; } @@ -60,6 +61,7 @@ export class AppKit { private adapters: BlockchainAdapter[]; private networks: AppKitNetwork[]; private namespaces: ProposalNamespaces; //TODO: check if its ok to use universal provider NamespaceConfig here + private config: AppKitConfig; private extraConnectors: WalletConnector[]; constructor(config: AppKitConfig) { @@ -68,12 +70,159 @@ export class AppKit { this.adapters = config.adapters; this.networks = NetworkUtil.formatNetworks(config.networks, this.projectId); //TODO: check this this.namespaces = WcHelpersUtil.createNamespaces(config.networks) as ProposalNamespaces; + this.config = config; this.extraConnectors = config.extraConnectors || []; this.initControllers(config); this.initConnectors(); } + /** + * Handles the full connection flow for a given connector type. + * @param type - The type of connector to use. + * @param requestedNamespaces - Optional specific namespaces to request. + */ + async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { + try { + const connector = await this.createConnector(type); + const defaultChain = NetworkUtil.getDefaultChainId(this.config.defaultChain); + + const approvedNamespaces = await connector.connect({ + namespaces: requestedNamespaces ?? this.namespaces, + defaultChain + }); + + const walletInfo = connector.getWalletInfo(); + + if (!approvedNamespaces || Object.keys(approvedNamespaces).length === 0) { + throw new Error('Connection cancelled or failed: No approved namespaces returned.'); + } + + // Setup adapters and subscribe to adapter events + const approvedAdapters = this.setupAdaptersAndSubscribe( + connector, + Object.keys(approvedNamespaces) + ); + + // Check if any compatible adapters were found for the *approved* namespaces + if (approvedAdapters.length === 0) { + //TODO: handle case where devs want to connect to a namespace that has no adapters. Could use the provider directly. + throw new Error('No compatible adapters found for the approved namespaces'); + } + + // Store the connection details for the successfully connected adapters + this.storeConnectionDetails(approvedAdapters, approvedNamespaces, walletInfo); + + // Store connector type and namespaces in storage + await StorageUtil.setConnectedConnectors({ + type: connector.type, + namespaces: Object.keys(approvedNamespaces) + }); + + this.syncAccounts(approvedAdapters); + + //TODO: Replace this + AccountController.setIsConnected(true); + } catch (error) { + console.warn('Connection failed:', error); + throw error; + } + } + + /** + * Disconnects from a given namespace. + * @param namespace - The namespace to disconnect from. + * @param isInternal - Whether the disconnect is internal (i.e. from the AppKit) or external (i.e. from wallet side). + */ + async disconnect(namespace?: string, isInternal?: boolean): Promise { + try { + if (!namespace || !ConnectionsController.state.activeNamespace) { + return; + } + + const connection = + ConnectionsController.state.connections[ + namespace ?? ConnectionsController.state.activeNamespace + ]; + const connectorType = connection?.adapter?.connector?.type; + + await ConnectionsController.disconnect( + namespace ?? ConnectionsController.state.activeNamespace, + isInternal + ); + + if (connectorType) { + await StorageUtil.removeConnectedConnectors(connectorType); + } + + ModalController.close(); + + AccountController.setIsConnected(false); // Might need adjustment based on multi-connection logic + RouterController.reset('Connect'); + TransactionsController.resetTransactions(); + ConnectionController.disconnect(); + + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }); + } catch (error) { + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_ERROR' + }); + } + } + + /** + * Returns the provider for a given namespace. + * @param namespace - The namespace to get the provider for. + * @returns The provider for the given namespace. + */ + getProvider(namespace?: string): T | null { + const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; + if (!activeNamespace) return null; + + const connection = ConnectionsController.state.connections[activeNamespace]; + if (!connection || !connection.adapter || !connection.adapter.connector) return null; + + return connection.adapter.connector.getProvider() as T; + } + + async switchNetwork(network: AppKitNetwork): Promise { + const adapter = this.getAdapterByNamespace(network.chainNamespace); + if (!adapter) throw new Error('No active adapter'); + + await adapter.switchNetwork(network); + + EventsController.sendEvent({ + type: 'track', + event: 'SWITCH_NETWORK', + properties: { + network: network.id + } + }); + + ConnectionsController.setActiveChain( + adapter.getSupportedNamespace(), + `${adapter.getSupportedNamespace()}:${network.id}` as CaipNetworkId + ); + + if (ConnectionsController.state.activeNamespace !== (network.chainNamespace ?? 'eip155')) { + ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); + } + + adapter.getBalance({ network }); + } + + open(options?: OpenOptions) { + ModalController.open(options); + } + + close() { + ModalController.close(); + } + private async createConnector(type: New_ConnectorType): Promise { // Check if an extra connector was provided by the developer const CustomConnector = this.extraConnectors.find( @@ -88,6 +237,7 @@ export class AppKit { return WalletConnectConnector.create({ projectId: this.projectId, metadata: this.metadata }); } + //TODO: reuse logic with connect method /** * Initializes connectors based on stored connection data. * This attempts to restore previous sessions. @@ -108,14 +258,14 @@ export class AppKit { if (namespaces && Object.keys(namespaces).length > 0) { // Ensure namespaces is not empty // Setup adapters and subscribe to events - const initializedAdapters = this._setupAdaptersAndSubscribe( + const initializedAdapters = this.setupAdaptersAndSubscribe( connector, Object.keys(namespaces) ); // If adapters were successfully initialized, store the connection details if (initializedAdapters.length > 0) { - this._storeConnectionDetails(initializedAdapters, namespaces, walletInfo); + this.storeConnectionDetails(initializedAdapters, namespaces, walletInfo); } this.syncAccounts(initializedAdapters); @@ -132,14 +282,7 @@ export class AppKit { } } - /** - * Sets up blockchain adapters for a given connector and namespaces, - * subscribes to adapter events. - * @param connector - The WalletConnector instance. - * @param namespaces - The namespaces to find adapters for. - * @returns The array of BlockchainAdapter instances that were set up. - */ - private _setupAdaptersAndSubscribe( + private setupAdaptersAndSubscribe( connector: WalletConnector, namespaces: string[] ): BlockchainAdapter[] { @@ -162,55 +305,10 @@ export class AppKit { return adapters; } - /** - * Handles the full connection flow for a given connector type. - * @param type - The type of connector to use. - * @param requestedNamespaces - Optional specific namespaces to request. - */ - async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { - try { - const connector = await this.createConnector(type); - - const approvedNamespaces = await connector.connect(requestedNamespaces ?? this.namespaces); - const walletInfo = connector.getWalletInfo(); - - if (!approvedNamespaces || Object.keys(approvedNamespaces).length === 0) { - throw new Error('Connection cancelled or failed: No approved namespaces returned.'); - } - - // Setup adapters and subscribe to adapter events - const approvedAdapters = this._setupAdaptersAndSubscribe( - connector, - Object.keys(approvedNamespaces) - ); - - // Check if any compatible adapters were found for the *approved* namespaces - if (approvedAdapters.length === 0) { - //TODO: handle case where devs want to connect to a namespace that has no adapters. Could use the provider directly. - throw new Error('No compatible adapters found for the approved namespaces'); - } - - // Store the connection details for the successfully connected adapters - this._storeConnectionDetails(approvedAdapters, approvedNamespaces, walletInfo); - - // Store connector type and namespaces in storage - await StorageUtil.setConnectedConnectors({ - type: connector.type, - namespaces: Object.keys(approvedNamespaces) - }); - - this.syncAccounts(approvedAdapters); - - // Set connected state (consider if this should be more nuanced for multi-connections) - AccountController.setIsConnected(true); + private getAdapterByNamespace(namespace: string = 'eip155'): BlockchainAdapter | null { + const namespaceConnection = ConnectionsController.state.connections[namespace]; - // No longer need to unsubscribe as we only subscribe to approved ones - } catch (error) { - // Log connection errors - console.warn('Connection failed:', error); // Using warn for potentially recoverable errors - // Rethrow or handle the error appropriately for the UI - throw error; - } + return namespaceConnection?.adapter ?? null; } private async syncAccounts(adapters: BlockchainAdapter[]) { @@ -219,20 +317,21 @@ export class AppKit { const namespace = adapter.getSupportedNamespace(); const connection = ConnectionsController.state.connections[namespace]; + if (!connection) return; + const network = this.networks.find( n => n.id?.toString() === connection?.activeChain?.split(':')[1] ); - adapter.getBalance({ address: adapter.getAccounts()?.[0], network }); + const address = + adapter.getAccounts()?.find(a => a.startsWith(connection?.activeChain)) ?? + adapter.getAccounts()?.[0]; + + adapter.getBalance({ address, network }); }); } - /** - * Stores connection details in the ConnectionsController. - * @param adapters - The adapters for which to store the connection. - * @param approvedNamespaces - The map of approved namespaces and their details. - */ - private _storeConnectionDetails( + private storeConnectionDetails( adapters: BlockchainAdapter[], approvedNamespaces: Namespaces, wallet?: WalletInfo @@ -244,18 +343,24 @@ export class AppKit { const accounts = namespaceDetails.accounts ?? []; const chains = namespaceDetails.chains ?? []; + const activeChain = adapter?.connector?.getChainId(namespace); ConnectionsController.storeConnection({ namespace, adapter, accounts, chains, + activeChain, wallet }); }); - // Set the first connected adapter's namespace as active - if (adapters.length > 0 && adapters[0]) { + const updateActiveNamespace = !Object.keys(approvedNamespaces).find( + n => n === ConnectionsController.state.activeNamespace + ); + + // If the active namespace is not in the approved namespaces or is undefined, set the first connected adapter's namespace as active + if (updateActiveNamespace && adapters[0]) { ConnectionsController.setActiveNamespace(adapters[0].getSupportedNamespace()); } } @@ -263,7 +368,7 @@ export class AppKit { private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { adapter.on('accountsChanged', ({ accounts, namespace }) => { console.log('accountsChanged', accounts, namespace); - //TODO: do i need this? + //TODO: check this }); adapter.on('chainChanged', ({ chainId, namespace }) => { @@ -281,6 +386,8 @@ export class AppKit { } private async initControllers(options: AppKitConfig) { + await this.initAsyncValues(options); + OptionsController.setProjectId(options.projectId); OptionsController.setMetadata(options.metadata); OptionsController.setIncludeWalletIds(options.includeWalletIds); @@ -316,91 +423,13 @@ export class AppKit { // } } - async disconnect(namespace?: string, isInternal?: boolean): Promise { - try { - const connection = - ConnectionsController.state.connections[ - namespace ?? ConnectionsController.state.activeNamespace - ]; - const connectorType = connection?.adapter?.connector?.type; - - await ConnectionsController.disconnect( - namespace ?? ConnectionsController.state.activeNamespace, - isInternal - ); - - if (connectorType) { - await StorageUtil.removeConnectedConnectors(connectorType); - } - - ModalController.close(); - // Resetting states after successful disconnect logic - AccountController.setIsConnected(false); // Might need adjustment based on multi-connection logic - RouterController.reset('Connect'); - TransactionsController.resetTransactions(); - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_SUCCESS' - }); - } catch (error) { - // Use console.warn for disconnect errors as they might not be critical app failures - console.warn('Disconnect failed:', error); // Keep error log for disconnect issues - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_ERROR' - }); - // Do not rethrow? Or handle differently? - } - } - - getProvider(namespace?: string): T | null { - const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; - if (!activeNamespace) return null; - - const connection = ConnectionsController.state.connections[activeNamespace]; - if (!connection || !connection.adapter || !connection.adapter.connector) return null; - - return connection.adapter.connector.getProvider() as T; - } - - private getAdapterByNamespace(namespace: string = 'eip155'): BlockchainAdapter | null { - const namespaceConnection = ConnectionsController.state.connections[namespace]; - - return namespaceConnection?.adapter ?? null; - } - - async switchNetwork(network: AppKitNetwork): Promise { - const adapter = this.getAdapterByNamespace(network.chainNamespace); - if (!adapter) throw new Error('No active adapter'); - - await adapter.switchNetwork(network); - - EventsController.sendEvent({ - type: 'track', - event: 'SWITCH_NETWORK', - properties: { - network: network.id - } - }); - - ConnectionsController.setActiveChain( - adapter.getSupportedNamespace(), - `${adapter.getSupportedNamespace()}:${network.id}` as CaipNetworkId - ); - - if (ConnectionsController.state.activeNamespace !== (network.chainNamespace ?? 'eip155')) { - ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); + private async initAsyncValues(options: AppKitConfig) { + const activeNamespace = await StorageUtil.getActiveNamespace(); + if (activeNamespace) { + ConnectionsController.setActiveNamespace(activeNamespace); + } else if (options.defaultChain) { + ConnectionsController.setActiveNamespace(options.defaultChain?.chainNamespace ?? 'eip155'); } - - adapter.getBalance({ network }); - } - - open(options?: OpenOptions) { - ModalController.open(options); - } - - close() { - ModalController.close(); } } diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 921795f2..486b1fda 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -6,10 +6,14 @@ import { type Namespaces, type ProposalNamespaces, type Provider, - type WalletInfo + type WalletInfo, + type ChainNamespace, + type CaipNetworkId } from '@reown/appkit-common-react-native'; export class WalletConnectConnector extends WalletConnector { + // private override provider: IUniversalProvider; + private constructor(provider: IUniversalProvider) { super({ type: 'walletconnect', provider: provider as Provider }); @@ -48,7 +52,7 @@ export class WalletConnectConnector extends WalletConnector { return this.provider.disconnect(); } - override async connect(namespaces: ProposalNamespaces) { + override async connect(opts: { namespaces: ProposalNamespaces; defaultChain?: CaipNetworkId }) { function onUri(uri: string) { ConnectionController.setWcUri(uri); } @@ -56,9 +60,13 @@ export class WalletConnectConnector extends WalletConnector { this.provider.on('display_uri', onUri); const session = await this.provider.connect({ - optionalNamespaces: namespaces + optionalNamespaces: opts.namespaces }); + if (opts.defaultChain) { + (this.provider as IUniversalProvider).setDefaultChain(opts.defaultChain); + } + this.namespaces = session?.namespaces as Namespaces; this.provider.off('display_uri', onUri); @@ -76,7 +84,6 @@ export class WalletConnectConnector extends WalletConnector { override switchNetwork(network: AppKitNetwork): Promise { if (!network.caipNetworkId) throw new Error('No network provided'); - (this.provider as IUniversalProvider).setDefaultChain(network.caipNetworkId); return Promise.resolve(); @@ -85,4 +92,20 @@ export class WalletConnectConnector extends WalletConnector { override getWalletInfo(): WalletInfo | undefined { return this.wallet; } + + override getChainId(namespace: ChainNamespace): CaipNetworkId | undefined { + if (!this.namespaces || !this.namespaces[namespace]) { + return undefined; + } + + const chainId = (this.provider as IUniversalProvider).rpcProviders[ + namespace + ]?.getDefaultChain(); + + if (!chainId) { + return undefined; + } + + return `${namespace}:${chainId}` as CaipNetworkId; + } } diff --git a/packages/appkit/src/hooks/useAccount.ts b/packages/appkit/src/hooks/useAccount.ts index eb447c4b..545e7f7b 100644 --- a/packages/appkit/src/hooks/useAccount.ts +++ b/packages/appkit/src/hooks/useAccount.ts @@ -7,7 +7,8 @@ export function useAccount() { activeNamespace, connections } = useSnapshot(ConnectionsController.state); - const connection = connections[activeNamespace]; + + const connection = connections[activeNamespace ?? '']; return { address: address?.split(':')[2], diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts index 7d05c682..6554eab4 100644 --- a/packages/appkit/src/hooks/useProvider.ts +++ b/packages/appkit/src/hooks/useProvider.ts @@ -1,8 +1,12 @@ import { useSnapshot } from 'valtio'; import { ConnectionsController } from '@reown/appkit-core-react-native'; +import type { Provider } from '@reown/appkit-common-react-native'; -export function useProvider(namespace?: string) { +export function useProvider(namespace?: string): Provider | undefined { const { connections, activeNamespace } = useSnapshot(ConnectionsController.state); + + if (!namespace || !activeNamespace) return undefined; + const connection = connections[namespace ?? activeNamespace]; if (!connection) return undefined; diff --git a/packages/appkit/src/utils/NetworkUtil.ts b/packages/appkit/src/utils/NetworkUtil.ts index 331761b4..39103ae4 100644 --- a/packages/appkit/src/utils/NetworkUtil.ts +++ b/packages/appkit/src/utils/NetworkUtil.ts @@ -35,5 +35,19 @@ export const NetworkUtil = { url.searchParams.set('projectId', projectId); return url.toString(); + }, + + getDefaultChainId(network?: AppKitNetwork): CaipNetworkId | undefined { + if (!network) return undefined; + + if (network.caipNetworkId) { + return network.caipNetworkId; + } + + if (network.chainNamespace) { + return `${network.chainNamespace}:${network.id}`; + } + + return `eip155:${network.id}`; } }; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 2670f347..08d20e3b 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -220,10 +220,14 @@ export abstract class WalletConnector extends EventEmitter { this.provider = provider; } - abstract connect(namespaces?: ProposalNamespaces): Promise; + abstract connect(opts: { + namespaces?: ProposalNamespaces; + defaultChain?: CaipNetworkId; + }): Promise; abstract disconnect(): Promise; abstract getProvider(): Provider; abstract getNamespaces(): Namespaces; + abstract getChainId(namespace: ChainNamespace): CaipNetworkId | undefined; abstract getWalletInfo(): WalletInfo | undefined; abstract switchNetwork(network: AppKitNetwork): Promise; } diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 5f1000cc..b2014a8a 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -201,7 +201,7 @@ export const ConnectionController = { }, async disconnect() { - await this._getClient().disconnect(); + await this._getClient()?.disconnect(); this.resetWcConnection(); // remove transactions // RouterController.reset('Connect'); diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index fa1fef46..f27775f7 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -9,6 +9,7 @@ import type { GetBalanceResponse, WalletInfo } from '@reown/appkit-common-react-native'; +import { StorageUtil } from '../utils/StorageUtil'; // -- Types --------------------------------------------- // type Balance = GetBalanceResponse; @@ -24,14 +25,14 @@ interface Connection { } export interface ConnectionsControllerState { - activeNamespace: ChainNamespace; + activeNamespace?: ChainNamespace; connections: Record; networks: AppKitNetwork[]; } // -- State --------------------------------------------- // const baseState = proxy({ - activeNamespace: 'eip155', + activeNamespace: undefined, connections: {}, networks: [] }); @@ -123,8 +124,9 @@ const derivedState = derive( export const ConnectionsController = { state: derivedState, - setActiveNamespace(namespace: ChainNamespace) { + setActiveNamespace(namespace?: ChainNamespace) { baseState.activeNamespace = namespace; + StorageUtil.setActiveNamespace(namespace); }, storeConnection({ @@ -132,17 +134,19 @@ export const ConnectionsController = { adapter, accounts, chains, - wallet + wallet, + activeChain }: { namespace: string; adapter: BlockchainAdapter; accounts: CaipAddress[]; chains: CaipNetworkId[]; wallet?: WalletInfo; + activeChain?: CaipNetworkId; }) { baseState.connections[namespace] = { balances: {}, - activeChain: chains[0]!, + activeChain: activeChain ?? chains[0]!, adapter: ref(adapter), accounts, chains, @@ -216,5 +220,15 @@ export const ConnectionsController = { namespacesUsingConnector.forEach(ns => { delete baseState.connections[ns]; }); + + // Remove activeNamespace if it is in the list of namespaces using the connector + if ( + baseState.activeNamespace && + (baseState.activeNamespace === namespace || + namespacesUsingConnector.includes(baseState.activeNamespace)) + ) { + baseState.activeNamespace = undefined; + StorageUtil.setActiveNamespace(undefined); + } } }; diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index 0c253503..294fe43f 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -11,7 +11,8 @@ import { DateUtil, type SocialProvider, type New_ConnectorType, - type ConnectorType + type ConnectorType, + type ChainNamespace } from '@reown/appkit-common-react-native'; // -- Helpers ----------------------------------------------------------------- @@ -27,6 +28,8 @@ const ONRAMP_SERVICE_PROVIDERS = '@appkit/onramp_service_providers'; const ONRAMP_FIAT_LIMITS = '@appkit/onramp_fiat_limits'; const ONRAMP_FIAT_CURRENCIES = '@appkit/onramp_fiat_currencies'; const ONRAMP_PREFERRED_FIAT_CURRENCY = '@appkit/onramp_preferred_fiat_currency'; +const ACTIVE_NAMESPACE = '@appkit/active_namespace'; + // -- Utility ----------------------------------------------------------------- export const StorageUtil = { setWalletConnectDeepLink({ href, name }: { href: string; name: string }) { @@ -392,5 +395,39 @@ export const StorageUtil = { } return []; + }, + + async setActiveNamespace(namespace?: ChainNamespace) { + try { + if (!namespace) { + await AsyncStorage.removeItem(ACTIVE_NAMESPACE); + + return; + } + + await AsyncStorage.setItem(ACTIVE_NAMESPACE, namespace); + } catch { + console.info('Unable to set Active Namespace'); + } + }, + + async getActiveNamespace() { + try { + const namespace = (await AsyncStorage.getItem(ACTIVE_NAMESPACE)) as ChainNamespace; + + return namespace ?? undefined; + } catch (err) { + console.info('Unable to get Active Namespace'); + } + + return undefined; + }, + + async removeActiveNamespace() { + try { + await AsyncStorage.removeItem(ACTIVE_NAMESPACE); + } catch { + console.info('Unable to remove Active Namespace'); + } } }; From fe895b2c57a27a5729beaf58325779a19e203a63 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 7 May 2025 15:23:07 -0300 Subject: [PATCH 19/91] chore: type change --- apps/native/App.tsx | 3 ++- packages/appkit/src/AppKit.ts | 16 ++++++++++------ packages/common/src/utils/TypeUtil.ts | 8 ++++++++ .../src/controllers/ConnectionsController.ts | 1 + .../core/src/controllers/OptionsController.ts | 5 +++-- packages/core/src/utils/TypeUtil.ts | 7 ------- packages/ethers/src/adapter.ts | 3 --- packages/ethers5/src/client.ts | 2 +- packages/scaffold-utils/src/utils/HelpersUtil.ts | 3 +-- packages/solana/src/adapter.ts | 1 - .../src/composites/wui-network-button/styles.ts | 3 +-- packages/wagmi/src/client.ts | 2 +- 12 files changed, 28 insertions(+), 26 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index cdcf7fd1..cc15e6d3 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -20,6 +20,7 @@ import { createAppKit, AppKit, AppKitButton, + NetworkButton, solana, bitcoin, bitcoinTestnet @@ -152,7 +153,7 @@ export default function Native() { loadingLabel="Connecting..." balance="show" /> - {/* */} + {/* */} {/* */} diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 464fa98d..52036846 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -44,15 +44,15 @@ interface AppKitConfig { excludeWalletIds?: OptionsControllerState['excludeWalletIds']; featuredWalletIds?: OptionsControllerState['featuredWalletIds']; customWallets?: OptionsControllerState['customWallets']; - tokens?: OptionsControllerState['tokens']; + tokens?: OptionsControllerState['tokens']; //TODO: check if needed in OptionsController enableAnalytics?: OptionsControllerState['enableAnalytics']; debug?: OptionsControllerState['debug']; themeMode?: ThemeMode; themeVariables?: ThemeVariables; - // features?: Features; siweConfig?: AppKitSIWEClient; defaultChain?: AppKitNetwork; - // chainImages?: Record; + // features?: Features; + // chainImages?: Record; //TODO: rename to networkImages } export class AppKit { @@ -60,7 +60,7 @@ export class AppKit { private metadata: Metadata; private adapters: BlockchainAdapter[]; private networks: AppKitNetwork[]; - private namespaces: ProposalNamespaces; //TODO: check if its ok to use universal provider NamespaceConfig here + private namespaces: ProposalNamespaces; private config: AppKitConfig; private extraConnectors: WalletConnector[]; @@ -189,6 +189,10 @@ export class AppKit { return connection.adapter.connector.getProvider() as T; } + getNetworks() { + return this.networks; + } + async switchNetwork(network: AppKitNetwork): Promise { const adapter = this.getAdapterByNamespace(network.chainNamespace); if (!adapter) throw new Error('No active adapter'); @@ -212,7 +216,7 @@ export class AppKit { ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); } - adapter.getBalance({ network }); + adapter.getBalance({ network, tokens: this.config.tokens }); } open(options?: OpenOptions) { @@ -327,7 +331,7 @@ export class AppKit { adapter.getAccounts()?.find(a => a.startsWith(connection?.activeChain)) ?? adapter.getAccounts()?.[0]; - adapter.getBalance({ address, network }); + adapter.getBalance({ address, network, tokens: this.config.tokens }); }); } diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 08d20e3b..8ec994d1 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -130,6 +130,13 @@ export interface ThemeVariables { accent?: string; } +export interface Token { + address: string; + image?: string; +} + +export type Tokens = Record; + export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; //********** Adapter Types **********// @@ -181,6 +188,7 @@ export abstract class SolanaBaseAdapter extends BlockchainAdapter {} export interface GetBalanceParams { address?: CaipAddress; network?: AppKitNetwork; + tokens?: Tokens; } type ContractAddress = CaipAddress; diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index f27775f7..d93aceb9 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -50,6 +50,7 @@ const derivedState = derive( return undefined; } + //TODO: what happens if there are several accounts on the same chain? const activeAccount = connection.accounts.find(account => account.startsWith(connection.activeChain) ); diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index 10f5ab57..753ef500 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -1,13 +1,14 @@ import { proxy, ref } from 'valtio'; +import type { Tokens } from '@reown/appkit-common-react-native'; import type { CustomWallet, Features, Metadata, ProjectId, SdkType, - SdkVersion, - Tokens + SdkVersion } from '../utils/TypeUtil'; + import { ConstantsUtil } from '../utils/ConstantsUtil'; // -- Types --------------------------------------------- // diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index ad5e6f0a..fd251133 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -339,13 +339,6 @@ export type BlockchainApiOnRampWidgetResponse = { }; // -- OptionsController Types --------------------------------------------------- -export interface Token { - address: string; - image?: string; -} - -export type Tokens = Record; - export type Metadata = { name: string; description: string; diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 71882857..827bd07f 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -128,10 +128,8 @@ export class EthersAdapter extends EVMAdapter { } onDisconnect(): void { - // console.log('EthersAdapter - onDisconnect'); this.emit('disconnect', { namespace: this.getSupportedNamespace() }); - //the connector might be shared between adapters. Validate this const provider = this.connector?.getProvider(); if (provider) { provider.off('chainChanged', this.onChainChanged.bind(this)); @@ -151,7 +149,6 @@ export class EthersAdapter extends EVMAdapter { const provider = this.connector?.getProvider(); if (!provider) return; - // console.log('EthersAdapter - subscribing to events'); provider.on('chainChanged', this.onChainChanged.bind(this)); provider.on('accountsChanged', this.onAccountsChanged.bind(this)); provider.on('disconnect', this.onDisconnect.bind(this)); diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index c369d75d..da7469f1 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -8,7 +8,6 @@ import { type NetworkControllerClient, type PublicStateControllerState, type SendTransactionArgs, - type Token, type WriteContractArgs, AppKitScaffold } from '@reown/appkit-scaffold-react-native'; @@ -37,6 +36,7 @@ import { type CaipAddress, type CaipNetwork, type CaipNetworkId, + type Token, erc20ABI, ErrorUtil, NamesUtil, diff --git a/packages/scaffold-utils/src/utils/HelpersUtil.ts b/packages/scaffold-utils/src/utils/HelpersUtil.ts index d1d033ed..e07252ec 100644 --- a/packages/scaffold-utils/src/utils/HelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/HelpersUtil.ts @@ -1,5 +1,4 @@ -import type { Tokens } from '@reown/appkit-core-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; +import { ConstantsUtil, type Tokens } from '@reown/appkit-common-react-native'; export const HelpersUtil = { getCaipTokens(tokens?: Tokens) { diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 0ffff3f2..f606aa56 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -115,7 +115,6 @@ export class SolanaAdapter extends SolanaBaseAdapter { onDisconnect(): void { this.emit('disconnect', { namespace: this.getSupportedNamespace() }); - //the connector might be shared between adapters. Validate this const provider = this.connector?.getProvider(); if (provider) { provider.off('chainChanged', this.onChainChanged.bind(this)); diff --git a/packages/ui/src/composites/wui-network-button/styles.ts b/packages/ui/src/composites/wui-network-button/styles.ts index f2166e82..77352019 100644 --- a/packages/ui/src/composites/wui-network-button/styles.ts +++ b/packages/ui/src/composites/wui-network-button/styles.ts @@ -21,8 +21,7 @@ export default StyleSheet.create({ height: 24, width: 24, borderRadius: BorderRadius.full, - borderWidth: 2, - paddingLeft: Spacing['4xs'] + borderWidth: 2 }, imageDisabled: { opacity: 0.4 diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 84cb06f2..85730a94 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -32,7 +32,6 @@ import { type NetworkControllerClient, type PublicStateControllerState, type SendTransactionArgs, - type Token, AppKitScaffold, type WriteContractArgs, type AppKitFrameProvider, @@ -49,6 +48,7 @@ import { type CaipAddress, type CaipNetwork, type CaipNetworkId + type Token, } from '@reown/appkit-common-react-native'; import { SIWEController, From c3051efdf507327e82972f0cd104953d70d1b1b2 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 8 May 2025 11:30:57 -0300 Subject: [PATCH 20/91] chore: type fix --- packages/ethers/src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 7deaafc6..1f3d54f8 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -19,7 +19,6 @@ import { type NetworkControllerClient, type PublicStateControllerState, type SendTransactionArgs, - type Token, AppKitScaffold, type WriteContractArgs, type AppKitFrameAccountType, @@ -29,6 +28,7 @@ import { type CaipAddress, type CaipNetwork, type CaipNetworkId, + type Token, erc20ABI, ErrorUtil, NamesUtil, From 0175ea0e2aa8a263e10999540831974bd4b732a1 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 8 May 2025 11:51:51 -0300 Subject: [PATCH 21/91] chore: removed ethers client --- packages/ethers/src/client.ts | 1071 -------------------- packages/ethers/src/utils/defaultConfig.ts | 19 - packages/ethers/src/utils/helpers.ts | 27 - 3 files changed, 1117 deletions(-) delete mode 100644 packages/ethers/src/client.ts delete mode 100644 packages/ethers/src/utils/defaultConfig.ts delete mode 100644 packages/ethers/src/utils/helpers.ts diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts deleted file mode 100644 index 1f3d54f8..00000000 --- a/packages/ethers/src/client.ts +++ /dev/null @@ -1,1071 +0,0 @@ -import { - BrowserProvider, - Contract, - InfuraProvider, - JsonRpcProvider, - JsonRpcSigner, - formatEther, - formatUnits, - getAddress, - hexlify, - isHexString, - parseUnits, - toUtf8Bytes -} from 'ethers'; -import { - type ConnectionControllerClient, - type Connector, - type LibraryOptions, - type NetworkControllerClient, - type PublicStateControllerState, - type SendTransactionArgs, - AppKitScaffold, - type WriteContractArgs, - type AppKitFrameAccountType, - type EstimateGasTransactionArgs -} from '@reown/appkit-scaffold-react-native'; -import { - type CaipAddress, - type CaipNetwork, - type CaipNetworkId, - type Token, - erc20ABI, - ErrorUtil, - NamesUtil, - NetworkUtil, - PresetsUtil, - ConstantsUtil -} from '@reown/appkit-common-react-native'; -import { - HelpersUtil, - StorageUtil, - EthersConstantsUtil, - EthersHelpersUtil, - EthersStoreUtil, - type Address, - type Metadata, - type ProviderType, - type Chain, - type Provider, - type EthersStoreUtilState, - type CombinedProviderType, - type AppKitFrameProvider -} from '@reown/appkit-scaffold-utils-react-native'; -import { - type AppKitSIWEClient, - SIWEController, - getDidChainId, - getDidAddress -} from '@reown/appkit-siwe-react-native'; -import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; -import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; -import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; - -import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; - -// -- Types --------------------------------------------------------------------- -export interface AppKitClientOptions extends Omit { - config: ProviderType; - siweConfig?: AppKitSIWEClient; - chains: Chain[]; - defaultChain?: Chain; - chainImages?: Record; - connectorImages?: Record; - tokens?: Record; -} - -export type AppKitOptions = Omit; - -// @ts-expect-error: Overriden state type is correct -interface AppKitState extends PublicStateControllerState { - selectedNetworkId: number | undefined; -} - -interface ExternalProvider extends EthereumProvider { - address?: string; -} - -// -- Client -------------------------------------------------------------------- -export class AppKit extends AppKitScaffold { - private hasSyncedConnectedAccount = false; - - private walletConnectProvider?: EthereumProvider; - - private walletConnectProviderInitPromise?: Promise; - - private projectId: string; - - private chains: Chain[]; - - private metadata: Metadata; - - private options: AppKitClientOptions | undefined = undefined; - - private authProvider?: AppKitFrameProvider; - - public constructor(options: AppKitClientOptions) { - const { - config, - siweConfig, - chains, - defaultChain, - tokens, - chainImages, - _sdkVersion, - ...appKitOptions - } = options; - - if (!config) { - throw new Error('appkit:constructor - config is undefined'); - } - - if (!appKitOptions.projectId) { - throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); - } - - const networkControllerClient: NetworkControllerClient = { - switchCaipNetwork: async caipNetwork => { - const chainId = NetworkUtil.caipNetworkIdToNumber(caipNetwork?.id); - if (chainId) { - try { - await this.switchNetwork(chainId); - } catch (error) { - EthersStoreUtil.setError(error); - } - } - }, - - getApprovedCaipNetworksData: async () => - new Promise(async resolve => { - const walletChoice = await StorageUtil.getConnectedConnector(); - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; - if (walletChoice?.includes(walletConnectType)) { - const provider = await this.getWalletConnectProvider(); - const result = getWalletConnectCaipNetworks(provider); - - resolve(result); - } else if (walletChoice?.includes(authType)) { - const result = getAuthCaipNetworks(); - resolve(result); - } else { - const result = { - approvedCaipNetworkIds: undefined, - supportsAllNetworks: true - }; - - resolve(result); - } - }) - }; - - const connectionControllerClient: ConnectionControllerClient = { - connectWalletConnect: async onUri => { - const WalletConnectProvider = await this.getWalletConnectProvider(); - if (!WalletConnectProvider) { - throw new Error('connectionControllerClient:getWalletConnectUri - provider is undefined'); - } - - WalletConnectProvider.on('display_uri', (uri: string) => { - onUri(uri); - }); - - // When connecting through walletconnect, we need to set the clientId in the store - const clientId = await WalletConnectProvider.signer?.client?.core?.crypto?.getClientId(); - if (clientId) { - this.setClientId(clientId); - } - - // SIWE - const params = await siweConfig?.getMessageParams?.(); - if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { - const result = await WalletConnectProvider.authenticate({ - nonce: await siweConfig.getNonce(), - methods: OPTIONAL_METHODS, - ...params - }); - // Auths is an array of signed CACAO objects https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-74.md - const signedCacao = result?.auths?.[0]; - if (signedCacao) { - const { p, s } = signedCacao; - const chainId = getDidChainId(p.iss); - const address = getDidAddress(p.iss); - - try { - // Kicks off verifyMessage and populates external states - const message = WalletConnectProvider.signer.client.formatAuthMessage({ - request: p, - iss: p.iss - }); - - await SIWEController.verifyMessage({ - message, - signature: s.s, - cacao: signedCacao - }); - - if (address && chainId) { - const session = { - address, - chainId: parseInt(chainId, 10) - }; - - SIWEController.setSession(session); - SIWEController.onSignIn?.(session); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error verifying message', error); - // eslint-disable-next-line no-console - await WalletConnectProvider.disconnect().catch(console.error); - // eslint-disable-next-line no-console - await SIWEController.signOut().catch(console.error); - throw error; - } - } - } else { - await WalletConnectProvider.connect(); - } - - await this.setWalletConnectProvider(); - }, - - // @ts-expect-error TODO expected types in arguments are incomplete - connectExternal: async ({ id }: { id: string; provider: Provider }) => { - // If connecting with something else than walletconnect, we need to clear the clientId in the store - this.setClientId(null); - - if (id === ConstantsUtil.COINBASE_CONNECTOR_ID) { - const coinbaseProvider = config.extraConnectors?.find(connector => connector.id === id); - if (!coinbaseProvider) { - throw new Error('connectionControllerClient:connectCoinbase - connector is undefined'); - } - - try { - await coinbaseProvider.request({ method: 'eth_requestAccounts' }); - await this.setCoinbaseProvider(coinbaseProvider as Provider); - } catch (error) { - EthersStoreUtil.setError(error); - } - } else if (id === ConstantsUtil.AUTH_CONNECTOR_ID) { - await this.setAuthProvider(); - } - }, - - disconnect: async () => { - const provider = EthersStoreUtil.state.provider; - const providerType = EthersStoreUtil.state.providerType; - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; - - if (siweConfig?.options?.signOutOnDisconnect) { - await SIWEController.signOut(); - } - - if (providerType === walletConnectType) { - const WalletConnectProvider = provider; - await (WalletConnectProvider as unknown as EthereumProvider).disconnect(); - } else if (providerType === authType) { - await this.authProvider?.disconnect(); - } else if (provider) { - provider.emit('disconnect'); - } - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - this.setClientId(null); - }, - - signMessage: async (message: string) => { - const provider = EthersStoreUtil.state.provider; - if (!provider) { - throw new Error('connectionControllerClient:signMessage - provider is undefined'); - } - const hexMessage = isHexString(message) ? message : hexlify(toUtf8Bytes(message)); - const signature = await provider.request({ - method: 'personal_sign', - params: [hexMessage, this.getAddress()] - }); - - return signature as `0x${string}`; - }, - - estimateGas: async ({ - address, - to, - data, - chainNamespace - }: EstimateGasTransactionArgs): Promise => { - const caipNetwork = this.getCaipNetwork(); - const provider = EthersStoreUtil.state.provider; - - if (!provider) { - throw new Error('Provider is undefined'); - } - - try { - if (!provider) { - throw new Error('estimateGas - provider is undefined'); - } - if (!address) { - throw new Error('estimateGas - address is undefined'); - } - if (chainNamespace && chainNamespace !== 'eip155') { - throw new Error('estimateGas - chainNamespace is not eip155'); - } - - const txParams = { - from: address, - to, - data, - type: 0 - }; - const browserProvider = new BrowserProvider(provider, Number(caipNetwork?.id)); - const signer = new JsonRpcSigner(browserProvider, address); - - return await signer.estimateGas(txParams); - } catch (error) { - throw new Error('Ethers: estimateGas - Estimate gas failed'); - } - }, - - parseUnits: (value: string, decimals: number) => parseUnits(value, decimals), - - formatUnits: (value: bigint, decimals: number) => formatUnits(value, decimals), - - sendTransaction: async (data: SendTransactionArgs) => { - const { chainId, provider, address } = EthersStoreUtil.state; - - if (!provider) { - throw new Error('ethersClient:sendTransaction - provider is undefined'); - } - - if (!address) { - throw new Error('ethersClient:sendTransaction - address is undefined'); - } - - const txParams = { - to: data.to, - value: data.value, - gasLimit: data.gas, - gasPrice: data.gasPrice, - data: data.data, - type: 0 - }; - - const browserProvider = new BrowserProvider(provider, chainId); - const signer = new JsonRpcSigner(browserProvider, address); - const txResponse = await signer.sendTransaction(txParams); - const txReceipt = await txResponse.wait(); - - return (txReceipt?.hash as `0x${string}`) || null; - }, - - writeContract: async (data: WriteContractArgs) => { - const { chainId, provider, address } = EthersStoreUtil.state; - - if (!provider) { - throw new Error('ethersClient:writeContract - provider is undefined'); - } - - if (!address) { - throw new Error('ethersClient:writeContract - address is undefined'); - } - - const browserProvider = new BrowserProvider(provider, chainId); - const signer = new JsonRpcSigner(browserProvider, address); - const contract = new Contract(data.tokenAddress, data.abi, signer); - - if (!contract || !data.method) { - throw new Error('Contract method is undefined'); - } - - const method = contract[data.method]; - if (method) { - const tx = await method(data.receiverAddress, data.tokenAmount); - - return tx; - } - - throw new Error('Contract method is undefined'); - }, - - getEnsAddress: async (value: string) => { - try { - const chainId = Number(this.getCaipNetwork()?.id); - let ensName: string | null = null; - let wcName: boolean | string = false; - - if (NamesUtil.isReownName(value)) { - wcName = (await this?.resolveReownName(value)) || false; - } - - // If on mainnet, fetch from ENS - if (chainId === 1) { - const ensProvider = new InfuraProvider('mainnet'); - ensName = await ensProvider.resolveName(value); - } - - return ensName || wcName || false; - } catch { - return false; - } - }, - - getEnsAvatar: async (value: string) => { - const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); - if (chainId === 1) { - const ensProvider = new InfuraProvider('mainnet'); - const avatar = await ensProvider.getAvatar(value); - - return avatar || false; - } - - return false; - } - }; - - super({ - networkControllerClient, - connectionControllerClient, - siweControllerClient: siweConfig, - defaultChain: EthersHelpersUtil.getCaipDefaultChain(defaultChain), - tokens: HelpersUtil.getCaipTokens(tokens), - _sdkVersion: _sdkVersion ?? `react-native-ethers-${ConstantsUtil.VERSION}`, - ...appKitOptions - }); - - this.options = options; - - this.metadata = config.metadata; - - this.projectId = appKitOptions.projectId; - this.chains = chains; - - this.createProvider(); - - EthersStoreUtil.subscribeKey('address', address => { - this.syncAccount({ address }); - }); - - EthersStoreUtil.subscribeKey('chainId', () => { - this.syncNetwork(chainImages); - }); - - EthersStoreUtil.subscribeKey('provider', provider => { - this.syncConnectedWalletInfo(provider); - }); - - this.syncRequestedNetworks(chains, chainImages); - this.syncConnectors(config); - this.syncAuthConnector(config); - } - - // -- Public ------------------------------------------------------------------ - - // @ts-expect-error: Overriden state type is correct - public override getState() { - const state = super.getState(); - - return { - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }; - } - - // @ts-expect-error: Overriden state type is correct - public override subscribeState(callback: (state: AppKitState) => void) { - return super.subscribeState(state => - callback({ - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }) - ); - } - - public setAddress(address?: string) { - const originalAddress = address ? (getAddress(address) as Address) : undefined; - EthersStoreUtil.setAddress(originalAddress); - } - - public getAddress() { - const { address } = EthersStoreUtil.state; - - return address ? getAddress(address) : address; - } - - public getError() { - return EthersStoreUtil.state.error; - } - - public getChainId() { - return EthersStoreUtil.state.chainId; - } - - public getIsConnected() { - return EthersStoreUtil.state.isConnected; - } - - public getWalletProvider() { - return EthersStoreUtil.state.provider; - } - - public getWalletProviderType() { - return EthersStoreUtil.state.providerType; - } - - public subscribeProvider(callback: (newState: EthersStoreUtilState) => void) { - return EthersStoreUtil.subscribe(callback); - } - - public async disconnect() { - const { provider } = EthersStoreUtil.state; - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - this.setClientId(null); - - await (provider as unknown as EthereumProvider).disconnect(); - } - - // -- Private ----------------------------------------------------------------- - private createProvider() { - if (!this.walletConnectProviderInitPromise) { - this.walletConnectProviderInitPromise = this.initWalletConnectProvider(); - } - - return this.walletConnectProviderInitPromise; - } - - private async initWalletConnectProvider() { - const rpcMap = this.chains - ? this.chains.reduce>((map, chain) => { - map[chain.chainId] = chain.rpcUrl; - - return map; - }, {}) - : ({} as Record); - - const walletConnectProviderOptions: EthereumProviderOptions = { - projectId: this.projectId, - showQrModal: false, - rpcMap, - optionalChains: [...this.chains.map(chain => chain.chainId)] as [number], - metadata: this.metadata - }; - - this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions); - this.addWalletConnectListeners(this.walletConnectProvider); - - await this.checkActiveWalletConnectProvider(); - } - - private async getWalletConnectProvider() { - if (!this.walletConnectProvider) { - try { - await this.createProvider(); - } catch (error) { - EthersStoreUtil.setError(error); - } - } - - return this.walletConnectProvider; - } - - private syncRequestedNetworks( - chains: AppKitClientOptions['chains'], - chainImages?: AppKitClientOptions['chainImages'] - ) { - const requestedCaipNetworks = chains?.map( - chain => - ({ - id: `${ConstantsUtil.EIP155}:${chain.chainId}`, - name: chain.name, - imageId: PresetsUtil.NetworkImageIds[chain.chainId], - imageUrl: chainImages?.[chain.chainId] - }) as CaipNetwork - ); - this.setRequestedCaipNetworks(requestedCaipNetworks ?? []); - } - - private async checkActiveWalletConnectProvider() { - const WalletConnectProvider = await this.getWalletConnectProvider(); - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - if (WalletConnectProvider) { - if (walletId === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID) { - await this.setWalletConnectProvider(); - } - } - } - - private async checkActiveCoinbaseProvider(provider: Provider) { - const CoinbaseProvider = provider as unknown as ExternalProvider; - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - if (CoinbaseProvider) { - if (walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { - if (CoinbaseProvider.address) { - await this.setCoinbaseProvider(provider); - await this.watchCoinbase(provider); - } else { - await StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - } - } - } - } - - private async setWalletConnectProvider() { - StorageUtil.setItem(EthersConstantsUtil.WALLET_ID, ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID); - const WalletConnectProvider = await this.getWalletConnectProvider(); - if (WalletConnectProvider) { - const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - EthersStoreUtil.setChainId(WalletConnectProvider.chainId); - EthersStoreUtil.setProviderType(providerType); - EthersStoreUtil.setProvider(WalletConnectProvider as unknown as Provider); - EthersStoreUtil.setIsConnected(true); - this.setAddress(WalletConnectProvider.accounts?.[0]); - await this.watchWalletConnect(); - } - } - - private async setCoinbaseProvider(provider: Provider) { - await StorageUtil.setItem(EthersConstantsUtil.WALLET_ID, ConstantsUtil.COINBASE_CONNECTOR_ID); - - if (provider) { - const { address, chainId } = await EthersHelpersUtil.getUserInfo(provider); - if (address && chainId) { - const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]; - EthersStoreUtil.setChainId(chainId); - EthersStoreUtil.setProviderType(providerType); - EthersStoreUtil.setProvider(provider); - EthersStoreUtil.setIsConnected(true); - this.setAddress(address); - await this.watchCoinbase(provider); - } - } - } - - private async setAuthProvider() { - StorageUtil.setItem(EthersConstantsUtil.WALLET_ID, ConstantsUtil.AUTH_CONNECTOR_ID); - - if (this.authProvider) { - const { address, chainId } = await this.authProvider.connect(); - super.setLoading(false); - if (address && chainId) { - EthersStoreUtil.setChainId(chainId); - EthersStoreUtil.setProviderType( - PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID] - ); - EthersStoreUtil.setProvider(this.authProvider as CombinedProviderType); - EthersStoreUtil.setIsConnected(true); - EthersStoreUtil.setAddress(address as Address); - } - } - } - - private async watchWalletConnect() { - const WalletConnectProvider = await this.getWalletConnectProvider(); - - function disconnectHandler() { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - - WalletConnectProvider?.removeListener('disconnect', disconnectHandler); - WalletConnectProvider?.removeListener('accountsChanged', accountsChangedHandler); - WalletConnectProvider?.removeListener('chainChanged', chainChangedHandler); - } - - function chainChangedHandler(chainId: string) { - if (chainId) { - const chain = EthersHelpersUtil.hexStringToNumber(chainId); - EthersStoreUtil.setChainId(chain); - } - } - - const accountsChangedHandler = async (accounts: string[]) => { - if (accounts.length > 0) { - await this.setWalletConnectProvider(); - } - }; - - if (WalletConnectProvider) { - WalletConnectProvider.on('disconnect', disconnectHandler); - WalletConnectProvider.on('accountsChanged', accountsChangedHandler); - WalletConnectProvider.on('chainChanged', chainChangedHandler); - } - } - - private async watchCoinbase(provider: Provider) { - const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID); - - function disconnectHandler() { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - - provider?.removeListener('disconnect', disconnectHandler); - provider?.removeListener('accountsChanged', accountsChangedHandler); - provider?.removeListener('chainChanged', chainChangedHandler); - } - - function accountsChangedHandler(accounts: string[]) { - if (accounts.length === 0) { - StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID); - EthersStoreUtil.reset(); - } else { - EthersStoreUtil.setAddress(accounts[0] as Address); - } - } - - function chainChangedHandler(chainId: string) { - if (chainId && walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) { - const chain = Number(chainId); - EthersStoreUtil.setChainId(chain); - } - } - - if (provider) { - provider.on('disconnect', disconnectHandler); - provider.on('accountsChanged', accountsChangedHandler); - provider.on('chainChanged', chainChangedHandler); - } - } - - private async syncAccount({ address }: { address?: Address }) { - const chainId = EthersStoreUtil.state.chainId; - const isConnected = EthersStoreUtil.state.isConnected; - - if (isConnected && address && chainId) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - - this.setIsConnected(isConnected); - - this.setCaipAddress(caipAddress); - - await Promise.all([ - this.syncProfile(address), - this.syncBalance(address), - this.getApprovedCaipNetworksData() - ]); - this.hasSyncedConnectedAccount = true; - } else if (!isConnected && this.hasSyncedConnectedAccount) { - this.close(); - this.resetAccount(); - this.resetWcConnection(); - this.resetNetwork(); - } - } - - private async syncNetwork(chainImages?: AppKitClientOptions['chainImages']) { - const address = EthersStoreUtil.state.address; - const chainId = EthersStoreUtil.state.chainId; - const isConnected = EthersStoreUtil.state.isConnected; - if (this.chains) { - const chain = this.chains.find(c => c.chainId === chainId); - - if (chain) { - const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`; - - this.setCaipNetwork({ - id: caipChainId, - name: chain.name, - imageId: PresetsUtil.NetworkImageIds[chain.chainId], - imageUrl: chainImages?.[chain.chainId] - }); - if (isConnected && address) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setCaipAddress(caipAddress); - if (chain.explorerUrl) { - const url = `${chain.explorerUrl}/address/${address}`; - this.setAddressExplorerUrl(url); - } else { - this.setAddressExplorerUrl(undefined); - } - - if (this.hasSyncedConnectedAccount) { - await this.syncBalance(address); - } - } - } - } - } - - private async syncProfile(address: Address) { - const chainId = EthersStoreUtil.state.chainId; - - try { - const response = await this.fetchIdentity({ address }); - - if (!response) { - throw new Error('Couldnt fetch idendity'); - } - - this.setProfileName(response.name); - this.setProfileImage(response.avatar); - } catch { - if (chainId === 1) { - const ensProvider = new InfuraProvider('mainnet'); - const name = await ensProvider.lookupAddress(address); - const avatar = await ensProvider.getAvatar(address); - - if (name) { - this.setProfileName(name); - } - if (avatar) { - this.setProfileImage(avatar); - } - } else { - this.setProfileName(undefined); - this.setProfileImage(undefined); - } - } - } - - private async syncBalance(address: Address) { - const chainId = EthersStoreUtil.state.chainId; - if (chainId && this.chains) { - const chain = this.chains.find(c => c.chainId === chainId); - const token = this.options?.tokens?.[chainId]; - - try { - if (chain) { - const jsonRpcProvider = new JsonRpcProvider(chain.rpcUrl, { - chainId, - name: chain.name - }); - - if (jsonRpcProvider) { - if (token) { - // Get balance from custom token address - const erc20 = new Contract(token.address, erc20ABI, jsonRpcProvider); - // @ts-expect-error - const decimals = await erc20.decimals(); - // @ts-expect-error - const symbol = await erc20.symbol(); - // @ts-expect-error - const balanceOf = await erc20.balanceOf(address); - this.setBalance(formatUnits(balanceOf, decimals), symbol); - } else { - const balance = await jsonRpcProvider.getBalance(address); - const formattedBalance = formatEther(balance); - this.setBalance(formattedBalance, chain.currency); - } - } - } - } catch { - this.setBalance(undefined, undefined); - } - } - } - - private async switchNetwork(chainId: number) { - const provider = EthersStoreUtil.state.provider; - const providerType = EthersStoreUtil.state.providerType; - if (this.chains) { - const chain = this.chains.find(c => c.chainId === chainId); - - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; - - const coinbaseType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; - - if (providerType === walletConnectType && chain) { - const WalletConnectProvider = provider as unknown as EthereumProvider; - - if (WalletConnectProvider) { - try { - await WalletConnectProvider.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: EthersHelpersUtil.numberToHexString(chain.chainId) }] - }); - - EthersStoreUtil.setChainId(chainId); - } catch (switchError: any) { - const message = switchError?.message as string; - if (/(?user rejected)/u.test(message?.toLowerCase())) { - throw new Error('Chain is not supported'); - } - await EthersHelpersUtil.addEthereumChain( - WalletConnectProvider as unknown as Provider, - chain - ); - } - } - } else if (providerType === coinbaseType && chain) { - const CoinbaseProvider = provider; - if (CoinbaseProvider) { - try { - await CoinbaseProvider.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: EthersHelpersUtil.numberToHexString(chain.chainId) }] - }); - EthersStoreUtil.setChainId(chain.chainId); - } catch (switchError: any) { - if ( - switchError.code === EthersConstantsUtil.ERROR_CODE_UNRECOGNIZED_CHAIN_ID || - switchError.code === EthersConstantsUtil.ERROR_CODE_DEFAULT || - switchError?.data?.originalError?.code === - EthersConstantsUtil.ERROR_CODE_UNRECOGNIZED_CHAIN_ID - ) { - await EthersHelpersUtil.addEthereumChain(CoinbaseProvider, chain); - } else { - throw new Error('Error switching network'); - } - } - } - } else if (providerType === authType) { - if (this.authProvider && chain?.chainId) { - try { - await this.authProvider?.switchNetwork(chain?.chainId); - EthersStoreUtil.setChainId(chain.chainId); - } catch { - throw new Error('Switching chain failed'); - } - } - } - } - } - - private async handleAuthSetPreferredAccount(address: string, type: AppKitFrameAccountType) { - if (!address) { - return; - } - - const chainId = this.getCaipNetwork()?.id; - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setCaipAddress(caipAddress); - this.setPreferredAccountType(type); - - await this.syncAccount({ address: address as Address }); - this.setLoading(false); - } - - private syncConnectors(config: ProviderType) { - const _connectors: Connector[] = []; - const EXCLUDED_CONNECTORS = [ConstantsUtil.AUTH_CONNECTOR_ID]; - - _connectors.push({ - id: ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID, - explorerId: PresetsUtil.ConnectorExplorerIds[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - imageId: PresetsUtil.ConnectorImageIds[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - imageUrl: this.options?.connectorImages?.[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID], - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]! - }); - - config.extraConnectors?.forEach(connector => { - if (!EXCLUDED_CONNECTORS.includes(connector.id)) { - if (connector.id === ConstantsUtil.COINBASE_CONNECTOR_ID) { - _connectors.push({ - id: ConstantsUtil.COINBASE_CONNECTOR_ID, - explorerId: PresetsUtil.ConnectorExplorerIds[ConstantsUtil.COINBASE_CONNECTOR_ID], - imageId: PresetsUtil.ConnectorImageIds[ConstantsUtil.COINBASE_CONNECTOR_ID], - imageUrl: this.options?.connectorImages?.[ConstantsUtil.COINBASE_CONNECTOR_ID], - name: - connector?.name ?? PresetsUtil.ConnectorNamesMap[ConstantsUtil.COINBASE_CONNECTOR_ID], - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]! - }); - this.checkActiveCoinbaseProvider(connector as Provider); - } else { - _connectors.push({ - id: connector.id, - name: connector.name ?? PresetsUtil.ConnectorNamesMap[connector.id], - type: 'EXTERNAL' - }); - } - } - }); - - this.setConnectors(_connectors); - } - - private async syncAuthConnector(config: ProviderType) { - const authConnector = config.extraConnectors?.find( - connector => connector.id === ConstantsUtil.AUTH_CONNECTOR_ID - ); - - if (!authConnector) { - return; - } - - this.authProvider = authConnector as AppKitFrameProvider; - - this.addConnector({ - id: ConstantsUtil.AUTH_CONNECTOR_ID, - name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.AUTH_CONNECTOR_ID], - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!, - provider: authConnector - }); - - const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); - if (connectedConnector === 'AUTH') { - // Set loader until it reconnects - this.setLoading(true); - } - - const { isConnected } = await this.authProvider.isConnected(); - if (isConnected) { - this.setAuthProvider(); - } - - this.addAuthListeners(this.authProvider); - } - - private async syncConnectedWalletInfo(provider?: Provider) { - if (!provider) { - this.setConnectedWalletInfo(undefined); - - return; - } - - if ((provider as any)?.session?.peer?.metadata) { - const metadata = (provider as unknown as EthereumProvider)?.session?.peer.metadata; - if (metadata) { - this.setConnectedWalletInfo({ - ...metadata, - name: metadata.name, - icon: metadata.icons?.[0] - }); - } - } else if (provider?.id) { - this.setConnectedWalletInfo({ - id: provider.id, - name: provider?.name ?? PresetsUtil.ConnectorNamesMap[provider.id], - icon: this.options?.connectorImages?.[provider.id] - }); - } else { - this.setConnectedWalletInfo(undefined); - } - } - - private async addAuthListeners(authProvider: AppKitFrameProvider) { - authProvider.onSetPreferredAccount(async ({ address, type }) => { - if (address) { - await this.handleAuthSetPreferredAccount(address, type); - } - this.setLoading(false); - }); - - authProvider.setOnTimeout(async () => { - this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); - this.setLoading(false); - }); - } - - private async addWalletConnectListeners(provider: EthereumProvider) { - if (provider) { - provider.signer.client.core.relayer.on('relayer_connect', () => { - provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { - if (payload?.error) { - this.handleAlertError(payload?.error.message); - } - }); - }); - } - } -} diff --git a/packages/ethers/src/utils/defaultConfig.ts b/packages/ethers/src/utils/defaultConfig.ts deleted file mode 100644 index 6ef65cee..00000000 --- a/packages/ethers/src/utils/defaultConfig.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { - Metadata, - Provider, - ProviderType, - AppKitFrameProvider -} from '@reown/appkit-scaffold-utils-react-native'; - -export interface ConfigOptions { - metadata: Metadata; - extraConnectors?: (Provider | AppKitFrameProvider)[]; -} - -export function defaultConfig(options: ConfigOptions) { - const { metadata, extraConnectors } = options; - - let providers: ProviderType = { metadata, extraConnectors }; - - return providers; -} diff --git a/packages/ethers/src/utils/helpers.ts b/packages/ethers/src/utils/helpers.ts deleted file mode 100644 index e2197eb4..00000000 --- a/packages/ethers/src/utils/helpers.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { CaipNetworkId } from '@reown/appkit-common-react-native'; -import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; -import EthereumProvider from '@walletconnect/ethereum-provider'; - -export async function getWalletConnectCaipNetworks(provider?: EthereumProvider) { - if (!provider) { - throw new Error('networkControllerClient:getApprovedCaipNetworks - provider is undefined'); - } - - const ns = provider.signer?.session?.namespaces; - const nsMethods = ns?.[ConstantsUtil.EIP155]?.methods; - const nsChains = ns?.[ConstantsUtil.EIP155]?.chains as CaipNetworkId[]; - - return { - supportsAllNetworks: Boolean(nsMethods?.includes(ConstantsUtil.ADD_CHAIN_METHOD)), - approvedCaipNetworkIds: nsChains - }; -} - -export function getAuthCaipNetworks() { - return { - supportsAllNetworks: false, - approvedCaipNetworkIds: PresetsUtil.RpcChainIds.map( - id => `${ConstantsUtil.EIP155}:${id}` - ) as CaipNetworkId[] - }; -} From 3d06ddffecd542e9b20dd13046a0323939622cb1 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 8 May 2025 12:08:25 -0300 Subject: [PATCH 22/91] chore: removed scaffold package --- packages/ethers/package.json | 1 - packages/scaffold/.eslintrc.json | 3 - packages/scaffold/.npmignore | 10 - packages/scaffold/CHANGELOG.md | 153 ------- packages/scaffold/package.json | 69 ---- packages/scaffold/readme.md | 9 - packages/scaffold/src/client.ts | 384 ------------------ packages/scaffold/src/config/animations.ts | 7 - .../scaffold/src/hooks/useCustomDimensions.ts | 21 - .../scaffold/src/hooks/useDebounceCallback.ts | 45 -- packages/scaffold/src/hooks/useKeyboard.ts | 62 --- packages/scaffold/src/hooks/useTimeout.ts | 34 -- packages/scaffold/src/index.ts | 21 - .../src/modal/w3m-account-button/index.tsx | 52 --- .../scaffold/src/modal/w3m-button/index.tsx | 45 -- .../src/modal/w3m-connect-button/index.tsx | 43 -- .../scaffold/src/modal/w3m-modal/index.tsx | 157 ------- .../scaffold/src/modal/w3m-modal/styles.ts | 13 - .../src/modal/w3m-network-button/index.tsx | 48 --- .../scaffold/src/modal/w3m-router/index.tsx | 136 ------- .../partials/w3m-account-activity/index.tsx | 172 -------- .../partials/w3m-account-activity/styles.ts | 29 -- .../partials/w3m-account-activity/utils.ts | 25 -- .../src/partials/w3m-account-tokens/index.tsx | 100 ----- .../w3m-account-wallet-features/index.tsx | 156 ------- .../w3m-account-wallet-features/styles.ts | 41 -- .../partials/w3m-all-wallets-list/index.tsx | 179 -------- .../partials/w3m-all-wallets-list/styles.ts | 26 -- .../partials/w3m-all-wallets-search/index.tsx | 147 ------- .../partials/w3m-all-wallets-search/styles.ts | 30 -- .../partials/w3m-connecting-body/index.tsx | 32 -- .../src/partials/w3m-connecting-body/utils.ts | 34 -- .../partials/w3m-connecting-header/index.tsx | 53 --- .../components/StoreLink.tsx | 38 -- .../partials/w3m-connecting-mobile/index.tsx | 158 ------- .../partials/w3m-connecting-mobile/styles.ts | 24 -- .../partials/w3m-connecting-qrcode/index.tsx | 76 ---- .../partials/w3m-connecting-qrcode/styles.ts | 8 - .../src/partials/w3m-connecting-web/index.tsx | 111 ----- .../src/partials/w3m-connecting-web/styles.ts | 20 - .../src/partials/w3m-header/index.tsx | 146 ------- .../src/partials/w3m-header/styles.ts | 8 - .../partials/w3m-information-modal/index.tsx | 65 --- .../partials/w3m-information-modal/styles.ts | 22 - .../src/partials/w3m-otp-code/index.tsx | 81 ---- .../src/partials/w3m-otp-code/styles.ts | 15 - .../src/partials/w3m-placeholder/index.tsx | 77 ---- .../src/partials/w3m-selector-modal/index.tsx | 124 ------ .../src/partials/w3m-selector-modal/styles.ts | 42 -- .../partials/w3m-send-input-address/index.tsx | 74 ---- .../partials/w3m-send-input-address/styles.ts | 14 - .../partials/w3m-send-input-token/index.tsx | 119 ------ .../partials/w3m-send-input-token/styles.ts | 20 - .../partials/w3m-send-input-token/utils.ts | 21 - .../src/partials/w3m-snackbar/index.tsx | 49 --- .../src/partials/w3m-snackbar/styles.ts | 12 - .../src/partials/w3m-swap-details/index.tsx | 160 -------- .../src/partials/w3m-swap-details/styles.ts | 26 -- .../src/partials/w3m-swap-details/utils.ts | 33 -- .../src/partials/w3m-swap-input/index.tsx | 152 ------- .../src/partials/w3m-swap-input/styles.ts | 20 - packages/scaffold/src/utils/UiUtil.ts | 42 -- .../components/auth-buttons.tsx | 68 ---- .../components/upgrade-wallet-button.tsx | 67 --- .../views/w3m-account-default-view/index.tsx | 333 --------------- .../views/w3m-account-default-view/styles.ts | 28 -- .../src/views/w3m-account-view/index.tsx | 105 ----- .../src/views/w3m-account-view/styles.ts | 32 -- .../src/views/w3m-all-wallets-view/index.tsx | 107 ----- .../src/views/w3m-all-wallets-view/styles.ts | 21 - .../views/w3m-connect-socials-view/index.tsx | 58 --- .../views/w3m-connect-socials-view/styles.ts | 12 - .../components/all-wallet-list.tsx | 60 --- .../components/all-wallets-button.tsx | 33 -- .../components/connect-email-input.tsx | 69 ---- .../components/connectors-list.tsx | 45 -- .../components/custom-wallet-list.tsx | 41 -- .../components/recent-wallet-list.tsx | 44 -- .../components/social-login-list.tsx | 95 ----- .../components/wallet-guide.tsx | 50 --- .../src/views/w3m-connect-view/index.tsx | 140 ------- .../src/views/w3m-connect-view/styles.ts | 18 - .../src/views/w3m-connect-view/utils.ts | 14 - .../w3m-connecting-external-view/index.tsx | 131 ------ .../w3m-connecting-external-view/styles.ts | 20 - .../w3m-connecting-farcaster-view/index.tsx | 140 ------- .../w3m-connecting-farcaster-view/styles.ts | 18 - .../w3m-connecting-social-view/index.tsx | 153 ------- .../w3m-connecting-social-view/styles.ts | 15 - .../src/views/w3m-connecting-view/index.tsx | 155 ------- .../src/views/w3m-create-view/index.tsx | 35 -- .../w3m-email-verify-device-view/index.tsx | 80 ---- .../w3m-email-verify-device-view/styles.ts | 19 - .../views/w3m-email-verify-otp-view/index.tsx | 75 ---- .../src/views/w3m-get-wallet-view/index.tsx | 50 --- .../src/views/w3m-get-wallet-view/styles.ts | 8 - .../views/w3m-network-switch-view/index.tsx | 138 ------- .../views/w3m-network-switch-view/styles.ts | 23 -- .../src/views/w3m-networks-view/index.tsx | 111 ----- .../src/views/w3m-networks-view/styles.ts | 12 - .../views/w3m-onramp-checkout-view/index.tsx | 266 ------------ .../views/w3m-onramp-loading-view/index.tsx | 157 ------- .../views/w3m-onramp-loading-view/styles.ts | 23 -- .../components/Country.tsx | 65 --- .../views/w3m-onramp-settings-view/index.tsx | 145 ------- .../views/w3m-onramp-settings-view/styles.ts | 25 -- .../views/w3m-onramp-settings-view/utils.ts | 90 ---- .../w3m-onramp-transaction-view/index.tsx | 120 ------ .../w3m-onramp-transaction-view/styles.ts | 18 - .../w3m-onramp-view/components/Currency.tsx | 86 ---- .../components/CurrencyInput.tsx | 169 -------- .../w3m-onramp-view/components/Header.tsx | 47 --- .../components/LoadingView.tsx | 43 -- .../components/PaymentMethod.tsx | 97 ----- .../w3m-onramp-view/components/Quote.tsx | 94 ----- .../components/SelectPaymentModal.tsx | 255 ------------ .../src/views/w3m-onramp-view/index.tsx | 293 ------------- .../src/views/w3m-onramp-view/styles.ts | 41 -- .../src/views/w3m-onramp-view/utils.ts | 124 ------ .../src/views/w3m-swap-preview-view/index.tsx | 145 ------- .../src/views/w3m-swap-preview-view/styles.ts | 18 - .../w3m-swap-select-token-view/index.tsx | 137 ------- .../w3m-swap-select-token-view/styles.ts | 30 -- .../views/w3m-swap-select-token-view/utils.ts | 33 -- .../src/views/w3m-swap-view/index.tsx | 209 ---------- .../src/views/w3m-swap-view/styles.ts | 23 -- .../src/views/w3m-transactions-view/index.tsx | 14 - .../w3m-unsupported-chain-view/index.tsx | 92 ----- .../w3m-unsupported-chain-view/styles.ts | 21 - .../index.tsx | 55 --- .../index.tsx | 56 --- .../w3m-update-email-wallet-view/index.tsx | 96 ----- .../w3m-update-email-wallet-view/styles.ts | 24 -- .../w3m-upgrade-email-wallet-view/index.tsx | 38 -- .../index.tsx | 106 ----- .../styles.ts | 29 -- .../index.tsx | 48 --- .../styles.ts | 8 - .../views/w3m-wallet-receive-view/index.tsx | 94 ----- .../views/w3m-wallet-receive-view/styles.ts | 8 - .../components/preview-send-details.tsx | 101 ----- .../components/preview-send-pill.tsx | 36 -- .../w3m-wallet-send-preview-view/index.tsx | 134 ------ .../w3m-wallet-send-preview-view/styles.ts | 35 -- .../index.tsx | 83 ---- .../styles.ts | 15 - .../src/views/w3m-wallet-send-view/index.tsx | 129 ------ .../src/views/w3m-wallet-send-view/styles.ts | 21 - .../w3m-what-is-a-network-view/index.tsx | 49 --- .../w3m-what-is-a-network-view/styles.ts | 14 - .../views/w3m-what-is-a-wallet-view/index.tsx | 68 ---- .../views/w3m-what-is-a-wallet-view/styles.ts | 15 - packages/scaffold/tsconfig.json | 5 - 153 files changed, 10864 deletions(-) delete mode 100644 packages/scaffold/.eslintrc.json delete mode 100644 packages/scaffold/.npmignore delete mode 100644 packages/scaffold/CHANGELOG.md delete mode 100644 packages/scaffold/package.json delete mode 100644 packages/scaffold/readme.md delete mode 100644 packages/scaffold/src/client.ts delete mode 100644 packages/scaffold/src/config/animations.ts delete mode 100644 packages/scaffold/src/hooks/useCustomDimensions.ts delete mode 100644 packages/scaffold/src/hooks/useDebounceCallback.ts delete mode 100644 packages/scaffold/src/hooks/useKeyboard.ts delete mode 100644 packages/scaffold/src/hooks/useTimeout.ts delete mode 100644 packages/scaffold/src/index.ts delete mode 100644 packages/scaffold/src/modal/w3m-account-button/index.tsx delete mode 100644 packages/scaffold/src/modal/w3m-button/index.tsx delete mode 100644 packages/scaffold/src/modal/w3m-connect-button/index.tsx delete mode 100644 packages/scaffold/src/modal/w3m-modal/index.tsx delete mode 100644 packages/scaffold/src/modal/w3m-modal/styles.ts delete mode 100644 packages/scaffold/src/modal/w3m-network-button/index.tsx delete mode 100644 packages/scaffold/src/modal/w3m-router/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-account-activity/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-account-activity/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-account-activity/utils.ts delete mode 100644 packages/scaffold/src/partials/w3m-account-tokens/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-all-wallets-list/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-connecting-body/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-connecting-body/utils.ts delete mode 100644 packages/scaffold/src/partials/w3m-connecting-header/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-connecting-mobile/components/StoreLink.tsx delete mode 100644 packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-connecting-mobile/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-connecting-qrcode/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-connecting-web/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-connecting-web/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-header/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-header/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-information-modal/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-information-modal/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-otp-code/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-otp-code/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-placeholder/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-selector-modal/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-selector-modal/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-send-input-address/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-send-input-address/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-send-input-token/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-send-input-token/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-send-input-token/utils.ts delete mode 100644 packages/scaffold/src/partials/w3m-snackbar/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-snackbar/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-swap-details/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-swap-details/styles.ts delete mode 100644 packages/scaffold/src/partials/w3m-swap-details/utils.ts delete mode 100644 packages/scaffold/src/partials/w3m-swap-input/index.tsx delete mode 100644 packages/scaffold/src/partials/w3m-swap-input/styles.ts delete mode 100644 packages/scaffold/src/utils/UiUtil.ts delete mode 100644 packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx delete mode 100644 packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx delete mode 100644 packages/scaffold/src/views/w3m-account-default-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-account-default-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-account-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-account-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-all-wallets-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-all-wallets-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-connect-socials-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-socials-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/custom-wallet-list.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-connect-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-connect-view/utils.ts delete mode 100644 packages/scaffold/src/views/w3m-connecting-external-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-connecting-external-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-connecting-social-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-connecting-social-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-connecting-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-create-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-email-verify-device-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-get-wallet-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-get-wallet-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-network-switch-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-network-switch-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-networks-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-networks-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-onramp-checkout-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-loading-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-loading-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-onramp-settings-view/components/Country.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-settings-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-settings-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-onramp-settings-view/utils.ts delete mode 100644 packages/scaffold/src/views/w3m-onramp-transaction-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-transaction-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/components/Currency.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/components/CurrencyInput.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/components/Header.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/components/LoadingView.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/components/PaymentMethod.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/components/Quote.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-onramp-view/utils.ts delete mode 100644 packages/scaffold/src/views/w3m-swap-preview-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-swap-preview-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-swap-select-token-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-swap-select-token-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-swap-select-token-view/utils.ts delete mode 100644 packages/scaffold/src/views/w3m-swap-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-swap-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-transactions-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-unsupported-chain-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-update-email-secondary-otp-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-update-email-wallet-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-wallet-send-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-what-is-a-network-view/styles.ts delete mode 100644 packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx delete mode 100644 packages/scaffold/src/views/w3m-what-is-a-wallet-view/styles.ts delete mode 100644 packages/scaffold/tsconfig.json diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 9384cbb6..2e10f17c 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -40,7 +40,6 @@ "dependencies": { "@reown/appkit-common-react-native": "1.2.3", "@reown/appkit-react-native": "workspace:*", - "@reown/appkit-scaffold-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", "@walletconnect/ethereum-provider": "2.20.2" diff --git a/packages/scaffold/.eslintrc.json b/packages/scaffold/.eslintrc.json deleted file mode 100644 index b9233ee4..00000000 --- a/packages/scaffold/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["../../.eslintrc"] -} diff --git a/packages/scaffold/.npmignore b/packages/scaffold/.npmignore deleted file mode 100644 index e203f76a..00000000 --- a/packages/scaffold/.npmignore +++ /dev/null @@ -1,10 +0,0 @@ -*.log -*.env -npm-debug.log* -node_modules -package-lock.json -src -tests -index.ts -.eslintrc.json -.turbo diff --git a/packages/scaffold/CHANGELOG.md b/packages/scaffold/CHANGELOG.md deleted file mode 100644 index cc85a1b2..00000000 --- a/packages/scaffold/CHANGELOG.md +++ /dev/null @@ -1,153 +0,0 @@ -# @reown/appkit-scaffold-react-native - -## 1.2.3 - -### Patch Changes - -- [#322](https://github.com/reown-com/appkit-react-native/pull/322) [`e0b59fb`](https://github.com/reown-com/appkit-react-native/commit/e0b59fb217fab570efc876265d13647844bb79d2) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: clear balance if the sdk fails to get it - -- [#327](https://github.com/reown-com/appkit-react-native/pull/327) [`bdc0388`](https://github.com/reown-com/appkit-react-native/commit/bdc038847695f45ed2e5b88f70fdec4ae17abcf7) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: deduplicate wallets in all wallet list - -- [#331](https://github.com/reown-com/appkit-react-native/pull/331) [`c75e94a`](https://github.com/reown-com/appkit-react-native/commit/c75e94ae97d2e7b5017a2fa879a58fb1d3a03821) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: solved issue with wallet page loaders - -- Updated dependencies [[`e0b59fb`](https://github.com/reown-com/appkit-react-native/commit/e0b59fb217fab570efc876265d13647844bb79d2), [`bdc0388`](https://github.com/reown-com/appkit-react-native/commit/bdc038847695f45ed2e5b88f70fdec4ae17abcf7), [`c75e94a`](https://github.com/reown-com/appkit-react-native/commit/c75e94ae97d2e7b5017a2fa879a58fb1d3a03821)]: - - @reown/appkit-common-react-native@1.2.3 - - @reown/appkit-core-react-native@1.2.3 - - @reown/appkit-siwe-react-native@1.2.3 - - @reown/appkit-ui-react-native@1.2.3 - -## 1.2.2 - -### Patch Changes - -- [#316](https://github.com/reown-com/appkit-react-native/pull/316) [`f3f4f37`](https://github.com/reown-com/appkit-react-native/commit/f3f4f3754673e0e85c110f41d9c77e27e30caa68) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: load transactions when needed - -- [#312](https://github.com/reown-com/appkit-react-native/pull/312) [`52484c4`](https://github.com/reown-com/appkit-react-native/commit/52484c4ee17d7651c966d7fe939c51a7af516c14) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show wallet info in useWalletInfo hook for ethers and ethers5 - -- [#314](https://github.com/reown-com/appkit-react-native/pull/314) [`d3a9604`](https://github.com/reown-com/appkit-react-native/commit/d3a9604b59fd177465fbf5b04ee3c26200e40384) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: improved state array updates in connection and router controllers - -- [#315](https://github.com/reown-com/appkit-react-native/pull/315) [`19e17ef`](https://github.com/reown-com/appkit-react-native/commit/19e17ef9b85e4d81f424438d83c3f3c89dd80c92) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: send expo info in useragent - -- Updated dependencies [[`f3f4f37`](https://github.com/reown-com/appkit-react-native/commit/f3f4f3754673e0e85c110f41d9c77e27e30caa68), [`52484c4`](https://github.com/reown-com/appkit-react-native/commit/52484c4ee17d7651c966d7fe939c51a7af516c14), [`d3a9604`](https://github.com/reown-com/appkit-react-native/commit/d3a9604b59fd177465fbf5b04ee3c26200e40384), [`19e17ef`](https://github.com/reown-com/appkit-react-native/commit/19e17ef9b85e4d81f424438d83c3f3c89dd80c92)]: - - @reown/appkit-common-react-native@1.2.2 - - @reown/appkit-core-react-native@1.2.2 - - @reown/appkit-siwe-react-native@1.2.2 - - @reown/appkit-ui-react-native@1.2.2 - -## 1.2.1 - -### Patch Changes - -- [#308](https://github.com/reown-com/appkit-react-native/pull/308) [`cdc1af3`](https://github.com/reown-com/appkit-react-native/commit/cdc1af3878a9193b34c530d2a867397941ed0bbf) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: added event subscription hook - -- [#307](https://github.com/reown-com/appkit-react-native/pull/307) [`bec3342`](https://github.com/reown-com/appkit-react-native/commit/bec3342e41d80a93e6eb5fca4883d97dd7dc64c2) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed condition for initial modal loading - -- Updated dependencies [[`cdc1af3`](https://github.com/reown-com/appkit-react-native/commit/cdc1af3878a9193b34c530d2a867397941ed0bbf), [`bec3342`](https://github.com/reown-com/appkit-react-native/commit/bec3342e41d80a93e6eb5fca4883d97dd7dc64c2)]: - - @reown/appkit-core-react-native@1.2.1 - - @reown/appkit-common-react-native@1.2.1 - - @reown/appkit-siwe-react-native@1.2.1 - - @reown/appkit-ui-react-native@1.2.1 - -## 1.2.0 - -### Minor Changes - -- [#299](https://github.com/reown-com/appkit-react-native/pull/299) [`278023f`](https://github.com/reown-com/appkit-react-native/commit/278023fa03c4a09be4c2b2b0d2b65e86d05e589b) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: swaps feature - -- [#300](https://github.com/reown-com/appkit-react-native/pull/300) [`db1e2a8`](https://github.com/reown-com/appkit-react-native/commit/db1e2a88d2c263fca438a99e0020aa1b2c55e360) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: added ability to change themeMode and override accent color - -### Patch Changes - -- fix: set loading when account data is being synced in appkit-wagmi - -- Updated dependencies [[`278023f`](https://github.com/reown-com/appkit-react-native/commit/278023fa03c4a09be4c2b2b0d2b65e86d05e589b), [`db1e2a8`](https://github.com/reown-com/appkit-react-native/commit/db1e2a88d2c263fca438a99e0020aa1b2c55e360)]: - - @reown/appkit-common-react-native@1.2.0 - - @reown/appkit-core-react-native@1.2.0 - - @reown/appkit-ui-react-native@1.2.0 - - @reown/appkit-siwe-react-native@1.2.0 - -## 1.1.1 - -### Patch Changes - -- [#292](https://github.com/reown-com/appkit-react-native/pull/292) [`ff75ba8`](https://github.com/reown-com/appkit-react-native/commit/ff75ba8ce9828b85f0c38a08e0ce33e3020c48d8) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: solved auth ethers connector type error - -- Updated dependencies [[`ff75ba8`](https://github.com/reown-com/appkit-react-native/commit/ff75ba8ce9828b85f0c38a08e0ce33e3020c48d8)]: - - @reown/appkit-common-react-native@1.1.1 - - @reown/appkit-core-react-native@1.1.1 - - @reown/appkit-siwe-react-native@1.1.1 - - @reown/appkit-ui-react-native@1.1.1 - -## 1.1.0 - -### Minor Changes - -- [#265](https://github.com/reown-com/appkit-react-native/pull/265) [`fb85422`](https://github.com/reown-com/appkit-react-native/commit/fb85422e0d544efc21a686287d91b16a638cf99c) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: social login - -- [#230](https://github.com/reown-com/appkit-react-native/pull/230) [`b02a7d3`](https://github.com/reown-com/appkit-react-native/commit/b02a7d39f4c23802dd4619671fe263a2faa17775) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: added wallet features for universal wallets - -- [#266](https://github.com/reown-com/appkit-react-native/pull/266) [`3193c6f`](https://github.com/reown-com/appkit-react-native/commit/3193c6fac3a2d7f83f53bf982180469e23f156c4) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: smart accounts for social login - -### Patch Changes - -- [#276](https://github.com/reown-com/appkit-react-native/pull/276) [`1fe0f3f`](https://github.com/reown-com/appkit-react-native/commit/1fe0f3fa0f96d14f63f2ffc96092b2bc239c4f92) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed siwe imports to solve issues on some android devices - -- [#273](https://github.com/reown-com/appkit-react-native/pull/273) [`aa3ba46`](https://github.com/reown-com/appkit-react-native/commit/aa3ba464c88cf38aae54b5e2185b64b513418b1b) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: watch token balance - -- [#268](https://github.com/reown-com/appkit-react-native/pull/268) [`0d2bae4`](https://github.com/reown-com/appkit-react-native/commit/0d2bae4a91063fc9fc6feb10e9fe780924cb670f) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: removed nft tab - -- [#272](https://github.com/reown-com/appkit-react-native/pull/272) [`5f7b88c`](https://github.com/reown-com/appkit-react-native/commit/5f7b88c220a312b07c1787dbd8aae2ee8d3a1120) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: small changes for web - -- [#274](https://github.com/reown-com/appkit-react-native/pull/274) [`f7e04d4`](https://github.com/reown-com/appkit-react-native/commit/f7e04d48a546d6c2b647abf417761d45570f11ef) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show custom wallets in all wallets view - -- [#275](https://github.com/reown-com/appkit-react-native/pull/275) [`2cb59a4`](https://github.com/reown-com/appkit-react-native/commit/2cb59a46a90bea3b919f47f4be8f485d867890ef) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: improved loading message in social connections - -- [#262](https://github.com/reown-com/appkit-react-native/pull/262) [`19bbffd`](https://github.com/reown-com/appkit-react-native/commit/19bbffde847b8d962d30e7dedacd93a4ef0c543c) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: get custom token balance if provided - -- [#240](https://github.com/reown-com/appkit-react-native/pull/240) [`6f8ddfa`](https://github.com/reown-com/appkit-react-native/commit/6f8ddfa292a122356565f710de1d2daf19210369) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed headers function in event controller - -- [#267](https://github.com/reown-com/appkit-react-native/pull/267) [`37983e7`](https://github.com/reown-com/appkit-react-native/commit/37983e71f78f85bcbd6b3c1708ddb359b920319b) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: bump walletconnect deps to 2.17.2 in ethers packages - -- [#238](https://github.com/reown-com/appkit-react-native/pull/238) [`68dc856`](https://github.com/reown-com/appkit-react-native/commit/68dc856c4ab39ebd17e1885b8536fa3a1a391186) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: updated packages info - -- [#277](https://github.com/reown-com/appkit-react-native/pull/277) [`285a64b`](https://github.com/reown-com/appkit-react-native/commit/285a64bfb310913c79b1ba9a85a82387e41a63ff) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: ui changes in social webview to solve android issues - -- [#271](https://github.com/reown-com/appkit-react-native/pull/271) [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show error message and retry button in case wallet fetch fails - -- [#289](https://github.com/reown-com/appkit-react-native/pull/289) [`047cb8e`](https://github.com/reown-com/appkit-react-native/commit/047cb8e2d4a435e1728cf1918c3754c217a60be2) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - chore: updated ethereum provider to 2.17.3 - -- [#269](https://github.com/reown-com/appkit-react-native/pull/269) [`f5ca754`](https://github.com/reown-com/appkit-react-native/commit/f5ca7548115d25e121931961295d822ecd13833c) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: show wallet view for email wallets - -- [#271](https://github.com/reown-com/appkit-react-native/pull/271) [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - feat: improved provider error handling - -- Updated dependencies [[`1fe0f3f`](https://github.com/reown-com/appkit-react-native/commit/1fe0f3fa0f96d14f63f2ffc96092b2bc239c4f92), [`fb85422`](https://github.com/reown-com/appkit-react-native/commit/fb85422e0d544efc21a686287d91b16a638cf99c), [`aa3ba46`](https://github.com/reown-com/appkit-react-native/commit/aa3ba464c88cf38aae54b5e2185b64b513418b1b), [`0d2bae4`](https://github.com/reown-com/appkit-react-native/commit/0d2bae4a91063fc9fc6feb10e9fe780924cb670f), [`5f7b88c`](https://github.com/reown-com/appkit-react-native/commit/5f7b88c220a312b07c1787dbd8aae2ee8d3a1120), [`f7e04d4`](https://github.com/reown-com/appkit-react-native/commit/f7e04d48a546d6c2b647abf417761d45570f11ef), [`2cb59a4`](https://github.com/reown-com/appkit-react-native/commit/2cb59a46a90bea3b919f47f4be8f485d867890ef), [`b02a7d3`](https://github.com/reown-com/appkit-react-native/commit/b02a7d39f4c23802dd4619671fe263a2faa17775), [`19bbffd`](https://github.com/reown-com/appkit-react-native/commit/19bbffde847b8d962d30e7dedacd93a4ef0c543c), [`6f8ddfa`](https://github.com/reown-com/appkit-react-native/commit/6f8ddfa292a122356565f710de1d2daf19210369), [`37983e7`](https://github.com/reown-com/appkit-react-native/commit/37983e71f78f85bcbd6b3c1708ddb359b920319b), [`3193c6f`](https://github.com/reown-com/appkit-react-native/commit/3193c6fac3a2d7f83f53bf982180469e23f156c4), [`68dc856`](https://github.com/reown-com/appkit-react-native/commit/68dc856c4ab39ebd17e1885b8536fa3a1a391186), [`285a64b`](https://github.com/reown-com/appkit-react-native/commit/285a64bfb310913c79b1ba9a85a82387e41a63ff), [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150), [`047cb8e`](https://github.com/reown-com/appkit-react-native/commit/047cb8e2d4a435e1728cf1918c3754c217a60be2), [`f5ca754`](https://github.com/reown-com/appkit-react-native/commit/f5ca7548115d25e121931961295d822ecd13833c), [`636285b`](https://github.com/reown-com/appkit-react-native/commit/636285bb261815b6766b78728219c7820532e150)]: - - @reown/appkit-common-react-native@1.1.0 - - @reown/appkit-core-react-native@1.1.0 - - @reown/appkit-siwe-react-native@1.1.0 - - @reown/appkit-ui-react-native@1.1.0 - -## 1.0.2 - -### Patch Changes - -- [#260](https://github.com/reown-com/appkit-react-native/pull/260) [`d5c55ed`](https://github.com/reown-com/appkit-react-native/commit/d5c55eda869ff874f44770110f2381d144d2c3b4) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed init config of email provider - -- Updated dependencies [[`d5c55ed`](https://github.com/reown-com/appkit-react-native/commit/d5c55eda869ff874f44770110f2381d144d2c3b4)]: - - @reown/appkit-common-react-native@1.0.2 - - @reown/appkit-core-react-native@1.0.2 - - @reown/appkit-siwe-react-native@1.0.2 - - @reown/appkit-ui-react-native@1.0.2 - -## 1.0.1 - -### Patch Changes - -- [#257](https://github.com/reown-com/appkit-react-native/pull/257) [`4b1ce96`](https://github.com/reown-com/appkit-react-native/commit/4b1ce966d087b7bf79efdda7d3e63bd89e5b9433) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: added timeout for secure site - -- [#259](https://github.com/reown-com/appkit-react-native/pull/259) [`656ed84`](https://github.com/reown-com/appkit-react-native/commit/656ed849754ad90dbb51b4e7a79183ccf8ae11de) Thanks [@ignaciosantise](https://github.com/ignaciosantise)! - fix: changed siwe import package - -- Updated dependencies [[`4b1ce96`](https://github.com/reown-com/appkit-react-native/commit/4b1ce966d087b7bf79efdda7d3e63bd89e5b9433), [`656ed84`](https://github.com/reown-com/appkit-react-native/commit/656ed849754ad90dbb51b4e7a79183ccf8ae11de)]: - - @reown/appkit-common-react-native@1.0.1 - - @reown/appkit-core-react-native@1.0.1 - - @reown/appkit-siwe-react-native@1.0.1 - - @reown/appkit-ui-react-native@1.0.1 diff --git a/packages/scaffold/package.json b/packages/scaffold/package.json deleted file mode 100644 index bc6da720..00000000 --- a/packages/scaffold/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@reown/appkit-scaffold-react-native", - "version": "1.2.3", - "main": "lib/commonjs/index.js", - "types": "lib/typescript/index.d.ts", - "module": "lib/module/index.js", - "source": "src/index.ts", - "scripts": { - "build": "bob build", - "clean": "rm -rf lib", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx" - }, - "files": [ - "src", - "lib", - "!**/__tests__", - "!**/__fixtures__", - "!**/__mocks__" - ], - "keywords": [ - "web3", - "crypto", - "ethereum", - "appkit", - "walletconnect", - "react-native" - ], - "repository": "https://github.com/reown-com/appkit-react-native", - "author": "Reown (https://reown.com)", - "homepage": "https://reown.com/appkit", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/reown-com/appkit-react-native/issues" - }, - "publishConfig": { - "registry": "https://registry.npmjs.org/", - "access": "public" - }, - "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-core-react-native": "1.2.3", - "@reown/appkit-siwe-react-native": "1.2.3", - "@reown/appkit-ui-react-native": "1.2.3" - }, - "peerDependencies": { - "react": ">=17", - "react-native": ">=0.68.5", - "react-native-modal": ">=13" - }, - "react-native": "src/index.ts", - "react-native-builder-bob": { - "source": "src", - "output": "lib", - "targets": [ - "commonjs", - "module", - [ - "typescript", - { - "tsc": "../../node_modules/.bin/tsc" - } - ] - ] - }, - "eslintIgnore": [ - "node_modules/", - "lib/" - ] -} diff --git a/packages/scaffold/readme.md b/packages/scaffold/readme.md deleted file mode 100644 index 60524ccd..00000000 --- a/packages/scaffold/readme.md +++ /dev/null @@ -1,9 +0,0 @@ -#### 📚 [Documentation](https://docs.reown.com/appkit/react-native/core/installation) - -#### 🔎 [Examples](https://github.com/reown-com/react-native-examples) - -#### 🔗 [Website](https://reown.com/appkit) - -# AppKit - -Your on-ramp to web3 multichain. AppKit is a versatile library that makes it super easy to connect users with your Dapp and start interacting with the blockchain. diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts deleted file mode 100644 index 3cd6b06b..00000000 --- a/packages/scaffold/src/client.ts +++ /dev/null @@ -1,384 +0,0 @@ -import './config/animations'; - -import type { - AccountControllerState, - ConnectionControllerClient, - ModalControllerState, - NetworkControllerClient, - NetworkControllerState, - OptionsControllerState, - EventsControllerState, - PublicStateControllerState, - ThemeControllerState, - Connector, - ConnectedWalletInfo, - Features, - EventName -} from '@reown/appkit-core-react-native'; -import { SIWEController, type SIWEControllerClient } from '@reown/appkit-siwe-react-native'; -import { - AccountController, - BlockchainApiController, - ConnectionController, - ConnectorController, - EnsController, - EventsController, - ModalController, - NetworkController, - OptionsController, - PublicStateController, - SnackController, - StorageUtil, - ThemeController, - TransactionsController -} from '@reown/appkit-core-react-native'; -import { - ConstantsUtil, - ErrorUtil, - type ThemeMode, - type ThemeVariables -} from '@reown/appkit-common-react-native'; -import { Appearance } from 'react-native'; - -// -- Types --------------------------------------------------------------------- -export interface LibraryOptions { - projectId: OptionsControllerState['projectId']; - metadata: OptionsControllerState['metadata']; - themeMode?: ThemeMode; - themeVariables?: ThemeVariables; - includeWalletIds?: OptionsControllerState['includeWalletIds']; - excludeWalletIds?: OptionsControllerState['excludeWalletIds']; - featuredWalletIds?: OptionsControllerState['featuredWalletIds']; - customWallets?: OptionsControllerState['customWallets']; - defaultChain?: NetworkControllerState['caipNetwork']; - tokens?: OptionsControllerState['tokens']; - clipboardClient?: OptionsControllerState['_clipboardClient']; - enableAnalytics?: OptionsControllerState['enableAnalytics']; - _sdkVersion: OptionsControllerState['sdkVersion']; - debug?: OptionsControllerState['debug']; - features?: Features; -} - -export interface ScaffoldOptions extends LibraryOptions { - networkControllerClient: NetworkControllerClient; - connectionControllerClient: ConnectionControllerClient; - siweControllerClient?: SIWEControllerClient; -} - -export interface OpenOptions { - view: 'Account' | 'Connect' | 'Networks' | 'Swap' | 'OnRamp'; -} - -// -- Client -------------------------------------------------------------------- -export class AppKitScaffold { - public reportedAlertErrors: Record = {}; - - public constructor(options: ScaffoldOptions) { - this.initControllers(options); - } - - // -- Public ------------------------------------------------------------------- - public async open(options?: OpenOptions) { - ModalController.open(options); - } - - public async close() { - ModalController.close(); - } - - public getThemeMode() { - return ThemeController.state.themeMode; - } - - public getThemeVariables() { - return ThemeController.state.themeVariables; - } - - public setThemeMode(themeMode: ThemeControllerState['themeMode']) { - ThemeController.setThemeMode(themeMode); - } - - public setThemeVariables(themeVariables: ThemeControllerState['themeVariables']) { - ThemeController.setThemeVariables(themeVariables); - } - - public subscribeTheme(callback: (newState: ThemeControllerState) => void) { - return ThemeController.subscribe(callback); - } - - public getWalletInfo() { - return AccountController.state.connectedWalletInfo; - } - - public subscribeWalletInfo(callback: (newState: ConnectedWalletInfo) => void) { - return AccountController.subscribeKey('connectedWalletInfo', callback); - } - - public getState() { - return { ...PublicStateController.state }; - } - - public subscribeState(callback: (newState: PublicStateControllerState) => void) { - return PublicStateController.subscribe(callback); - } - - public subscribeStateKey( - key: K, - callback: (value: PublicStateControllerState[K]) => void - ) { - return PublicStateController.subscribeKey(key, callback); - } - - public subscribeConnection( - callback: (isConnected: AccountControllerState['isConnected']) => void - ) { - return AccountController.subscribeKey('isConnected', callback); - } - - public setLoading(loading: ModalControllerState['loading']) { - ModalController.setLoading(loading); - } - - public getEvent() { - return { ...EventsController.state }; - } - - public subscribeEvents(callback: (newEvent: EventsControllerState) => void) { - return EventsController.subscribe(callback); - } - - public subscribeEvent(event: EventName, callback: (newEvent: EventsControllerState) => void) { - return EventsController.subscribeEvent(event, callback); - } - - public resolveReownName = async (name: string) => { - const wcNameAddress = await EnsController.resolveName(name); - const networkNameAddresses = wcNameAddress?.addresses - ? Object.values(wcNameAddress?.addresses) - : []; - - return networkNameAddresses[0]?.address || false; - }; - - // -- Protected ---------------------------------------------------------------- - protected setIsConnected: (typeof AccountController)['setIsConnected'] = isConnected => { - AccountController.setIsConnected(isConnected); - }; - - protected setCaipAddress: (typeof AccountController)['setCaipAddress'] = caipAddress => { - AccountController.setCaipAddress(caipAddress); - }; - - protected getCaipAddress = () => AccountController.state.caipAddress; - - protected setBalance: (typeof AccountController)['setBalance'] = (balance, balanceSymbol) => { - AccountController.setBalance(balance, balanceSymbol); - }; - - protected setProfileName: (typeof AccountController)['setProfileName'] = profileName => { - AccountController.setProfileName(profileName); - }; - - protected setProfileImage: (typeof AccountController)['setProfileImage'] = profileImage => { - AccountController.setProfileImage(profileImage); - }; - - protected resetAccount: (typeof AccountController)['resetAccount'] = () => { - AccountController.resetAccount(); - }; - - protected setCaipNetwork: (typeof NetworkController)['setCaipNetwork'] = caipNetwork => { - NetworkController.setCaipNetwork(caipNetwork); - }; - - protected getCaipNetwork = () => NetworkController.state.caipNetwork; - - protected setRequestedCaipNetworks: (typeof NetworkController)['setRequestedCaipNetworks'] = - requestedCaipNetworks => { - NetworkController.setRequestedCaipNetworks(requestedCaipNetworks); - }; - - protected getApprovedCaipNetworksData: (typeof NetworkController)['getApprovedCaipNetworksData'] = - () => NetworkController.getApprovedCaipNetworksData(); - - protected resetNetwork: (typeof NetworkController)['resetNetwork'] = () => { - NetworkController.resetNetwork(); - }; - - protected setConnectors: (typeof ConnectorController)['setConnectors'] = ( - connectors: Connector[] - ) => { - ConnectorController.setConnectors(connectors); - this.setConnectorExcludedWallets(connectors); - }; - - protected addConnector: (typeof ConnectorController)['addConnector'] = (connector: Connector) => { - ConnectorController.addConnector(connector); - }; - - protected getConnectors: (typeof ConnectorController)['getConnectors'] = () => - ConnectorController.getConnectors(); - - protected resetWcConnection: (typeof ConnectionController)['resetWcConnection'] = () => { - ConnectionController.resetWcConnection(); - TransactionsController.resetTransactions(); - }; - - protected fetchIdentity: (typeof BlockchainApiController)['fetchIdentity'] = request => - BlockchainApiController.fetchIdentity(request); - - protected setAddressExplorerUrl: (typeof AccountController)['setAddressExplorerUrl'] = - addressExplorerUrl => { - AccountController.setAddressExplorerUrl(addressExplorerUrl); - }; - - protected setConnectedWalletInfo: (typeof AccountController)['setConnectedWalletInfo'] = - connectedWalletInfo => { - AccountController.setConnectedWalletInfo(connectedWalletInfo); - }; - - protected setClientId: (typeof BlockchainApiController)['setClientId'] = clientId => { - BlockchainApiController.setClientId(clientId); - }; - - protected setPreferredAccountType: (typeof AccountController)['setPreferredAccountType'] = - preferredAccountType => { - AccountController.setPreferredAccountType(preferredAccountType); - }; - - protected handleAlertError(error?: string | { shortMessage: string; longMessage: string }) { - if (!error) return; - - if (typeof error === 'object') { - SnackController.showInternalError(error); - - return; - } - - // Check if the error is a universal provider error - const matchedUniversalProviderError = Object.entries(ErrorUtil.UniversalProviderErrors).find( - ([, { message }]) => error?.includes(message) - ); - - const [errorKey, errorValue] = matchedUniversalProviderError ?? []; - - const { message, alertErrorKey } = errorValue ?? {}; - - if (errorKey && message && !this.reportedAlertErrors[errorKey]) { - const alertError = - ErrorUtil.ALERT_ERRORS[alertErrorKey as keyof typeof ErrorUtil.ALERT_ERRORS]; - - if (alertError) { - SnackController.showInternalError(alertError); - this.reportedAlertErrors[errorKey] = true; - } - } - } - - // -- Private ------------------------------------------------------------------ - private async initControllers(options: ScaffoldOptions) { - this.initAsyncValues(options); - NetworkController.setClient(options.networkControllerClient); - NetworkController.setDefaultCaipNetwork(options.defaultChain); - - OptionsController.setProjectId(options.projectId); - OptionsController.setIncludeWalletIds(options.includeWalletIds); - OptionsController.setExcludeWalletIds(options.excludeWalletIds); - OptionsController.setFeaturedWalletIds(options.featuredWalletIds); - OptionsController.setTokens(options.tokens); - OptionsController.setCustomWallets(options.customWallets); - OptionsController.setEnableAnalytics(options.enableAnalytics); - OptionsController.setSdkVersion(options._sdkVersion); - OptionsController.setDebug(options.debug); - - if (options.clipboardClient) { - OptionsController.setClipboardClient(options.clipboardClient); - } - - ConnectionController.setClient(options.connectionControllerClient); - - if (options.themeMode) { - ThemeController.setThemeMode(options.themeMode); - } else { - ThemeController.setThemeMode(Appearance.getColorScheme() as ThemeMode); - } - - if (options.themeVariables) { - ThemeController.setThemeVariables(options.themeVariables); - } - if (options.metadata) { - OptionsController.setMetadata(options.metadata); - } - - if (options.siweControllerClient) { - SIWEController.setSIWEClient(options.siweControllerClient); - } - - if (options.features) { - OptionsController.setFeatures(options.features); - } - - if ( - (options.features?.onramp === true || options.features?.onramp === undefined) && - (options.metadata?.redirect?.universal || options.metadata?.redirect?.native) - ) { - OptionsController.setIsOnRampEnabled(true); - } - } - - private async setConnectorExcludedWallets(connectors: Connector[]) { - const excludedWallets = OptionsController.state.excludeWalletIds || []; - - // Exclude Coinbase if the connector is not implemented - const excludeCoinbase = - connectors.findIndex(connector => connector.id === ConstantsUtil.COINBASE_CONNECTOR_ID) === - -1; - - if (excludeCoinbase) { - excludedWallets.push(ConstantsUtil.COINBASE_EXPLORER_ID); - } - - OptionsController.setExcludeWalletIds(excludedWallets); - } - - private async initRecentWallets(options: ScaffoldOptions) { - const wallets = await StorageUtil.getRecentWallets(); - const connectedWalletImage = await StorageUtil.getConnectedWalletImageUrl(); - - const filteredWallets = wallets.filter(wallet => { - const { includeWalletIds, excludeWalletIds } = options; - if (includeWalletIds) { - return includeWalletIds.includes(wallet.id); - } - if (excludeWalletIds) { - return !excludeWalletIds.includes(wallet.id); - } - - return true; - }); - - ConnectionController.setRecentWallets(filteredWallets); - - if (connectedWalletImage) { - ConnectionController.setConnectedWalletImageUrl(connectedWalletImage); - } - } - - private async initConnectedConnector() { - const connectedConnector = await StorageUtil.getConnectedConnector(); - if (connectedConnector) { - ConnectorController.setConnectedConnector(connectedConnector, false); - } - } - - private async initSocial() { - const connectedSocialProvider = await StorageUtil.getConnectedSocialProvider(); - ConnectionController.setConnectedSocialProvider(connectedSocialProvider); - } - - private async initAsyncValues(options: ScaffoldOptions) { - await this.initConnectedConnector(); - await this.initRecentWallets(options); - await this.initSocial(); - } -} diff --git a/packages/scaffold/src/config/animations.ts b/packages/scaffold/src/config/animations.ts deleted file mode 100644 index ff7034f0..00000000 --- a/packages/scaffold/src/config/animations.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Platform, UIManager } from 'react-native'; - -if (Platform.OS === 'android') { - if (UIManager.setLayoutAnimationEnabledExperimental) { - UIManager.setLayoutAnimationEnabledExperimental(true); - } -} diff --git a/packages/scaffold/src/hooks/useCustomDimensions.ts b/packages/scaffold/src/hooks/useCustomDimensions.ts deleted file mode 100644 index c446a39d..00000000 --- a/packages/scaffold/src/hooks/useCustomDimensions.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useWindowDimensions } from 'react-native'; - -/** - * Hook used to get the width of the screen and the padding needed to accomplish portrait and landscape modes. - * @returns { width: number, isPortrait: boolean, isLandscape: boolean, padding: number } - */ -export function useCustomDimensions() { - const { width, height } = useWindowDimensions(); - const [maxWidth, setMaxWidth] = useState(Math.min(width, height)); - const [isPortrait, setIsPortrait] = useState(height > width); - const [padding, setPadding] = useState(0); - - useEffect(() => { - setMaxWidth(Math.min(width, height)); - setIsPortrait(height > width); - setPadding(width < height ? 0 : (width - height) / 2); - }, [width, height]); - - return { maxWidth, isPortrait, isLandscape: !isPortrait, padding }; -} diff --git a/packages/scaffold/src/hooks/useDebounceCallback.ts b/packages/scaffold/src/hooks/useDebounceCallback.ts deleted file mode 100644 index 684ca1ad..00000000 --- a/packages/scaffold/src/hooks/useDebounceCallback.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; - -interface Props { - callback: ((args?: any) => any) | ((args?: any) => Promise); - delay?: number; -} - -export function useDebounceCallback({ callback, delay = 250 }: Props) { - const timeoutRef = useRef(null); - const callbackRef = useRef(callback); - - useEffect(() => { - callbackRef.current = callback; - }, [callback]); - - const abort = useCallback(() => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - timeoutRef.current = null; - } - }, []); - - const debouncedCallback = useCallback( - (args?: any) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - timeoutRef.current = setTimeout(() => { - callbackRef.current(args); - }, delay); - }, - [delay] - ); - - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - }, []); - - return { debouncedCallback, abort }; -} diff --git a/packages/scaffold/src/hooks/useKeyboard.ts b/packages/scaffold/src/hooks/useKeyboard.ts deleted file mode 100644 index ba064536..00000000 --- a/packages/scaffold/src/hooks/useKeyboard.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Keyboard, type KeyboardEventListener, type KeyboardMetrics } from 'react-native'; - -const emptyCoordinates = Object.freeze({ - screenX: 0, - screenY: 0, - width: 0, - height: 0 -}); -const initialValue = { - start: emptyCoordinates, - end: emptyCoordinates -}; - -export function useKeyboard() { - const [shown, setShown] = useState(false); - const [coordinates, setCoordinates] = useState<{ - start: undefined | KeyboardMetrics; - end: KeyboardMetrics; - }>(initialValue); - const [keyboardHeight, setKeyboardHeight] = useState(0); - - const handleKeyboardWillShow: KeyboardEventListener = e => { - setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); - }; - const handleKeyboardDidShow: KeyboardEventListener = e => { - setShown(true); - setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); - setKeyboardHeight(e.endCoordinates.height); - }; - const handleKeyboardWillHide: KeyboardEventListener = e => { - setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); - }; - const handleKeyboardDidHide: KeyboardEventListener = e => { - setShown(false); - if (e) { - setCoordinates({ start: e.startCoordinates, end: e.endCoordinates }); - } else { - setCoordinates(initialValue); - setKeyboardHeight(0); - } - }; - - useEffect(() => { - const subscriptions = [ - Keyboard.addListener('keyboardWillShow', handleKeyboardWillShow), - Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow), - Keyboard.addListener('keyboardWillHide', handleKeyboardWillHide), - Keyboard.addListener('keyboardDidHide', handleKeyboardDidHide) - ]; - - return () => { - subscriptions.forEach(subscription => subscription.remove()); - }; - }, []); - - return { - keyboardShown: shown, - coordinates, - keyboardHeight - }; -} diff --git a/packages/scaffold/src/hooks/useTimeout.ts b/packages/scaffold/src/hooks/useTimeout.ts deleted file mode 100644 index 90e02795..00000000 --- a/packages/scaffold/src/hooks/useTimeout.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; - -function useTimeout(delay: number) { - const timeLeftRef = useRef(delay); - const [timeLeft, setTimeLeft] = useState(delay); - const interval = useRef(); - - const startTimer = useCallback((newDelay: number) => { - timeLeftRef.current = newDelay; - setTimeLeft(newDelay); - interval.current = setInterval(() => { - if (timeLeftRef.current > 0) { - timeLeftRef.current -= 1; - setTimeLeft(timeLeftRef.current); - } else { - if (typeof interval.current === 'number') { - clearInterval(interval.current); - } - } - }, 1000); - }, []); - - useEffect(() => { - return () => { - if (typeof interval.current === 'number') { - clearInterval(interval.current); - } - }; - }, [interval]); - - return { timeLeft, startTimer }; -} - -export default useTimeout; diff --git a/packages/scaffold/src/index.ts b/packages/scaffold/src/index.ts deleted file mode 100644 index 5620f103..00000000 --- a/packages/scaffold/src/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -export { - AccountButton as AccountButton, - type AccountButtonProps -} from './modal/w3m-account-button'; -export { AppKitButton, type AppKitButtonProps } from './modal/w3m-button'; -export { - ConnectButton as ConnectButton, - type ConnectButtonProps as ConnectButtonProps -} from './modal/w3m-connect-button'; -export { - NetworkButton as NetworkButton, - type NetworkButtonProps as NetworkButtonProps -} from './modal/w3m-network-button'; -export { AppKit } from './modal/w3m-modal'; -export { AppKitRouter } from './modal/w3m-router'; - -export { AppKitScaffold } from './client'; -export type { LibraryOptions, ScaffoldOptions } from './client'; - -export type * from '@reown/appkit-core-react-native'; -export { CoreHelperUtil } from '@reown/appkit-core-react-native'; diff --git a/packages/scaffold/src/modal/w3m-account-button/index.tsx b/packages/scaffold/src/modal/w3m-account-button/index.tsx deleted file mode 100644 index 8bb37376..00000000 --- a/packages/scaffold/src/modal/w3m-account-button/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { - AccountController, - CoreHelperUtil, - NetworkController, - ModalController, - AssetUtil, - ThemeController -} from '@reown/appkit-core-react-native'; - -import { AccountButton as AccountButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; -import { ApiController } from '@reown/appkit-core-react-native'; -import type { StyleProp, ViewStyle } from 'react-native'; - -export interface AccountButtonProps { - balance?: 'show' | 'hide'; - disabled?: boolean; - style?: StyleProp; - testID?: string; -} - -export function AccountButton({ balance, disabled, style, testID }: AccountButtonProps) { - const { - address, - balance: balanceVal, - balanceSymbol, - profileImage, - profileName - } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { themeMode, themeVariables } = useSnapshot(ThemeController.state); - - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const showBalance = balance === 'show'; - - return ( - - ModalController.open()} - address={address} - profileName={profileName} - networkSrc={networkImage} - imageHeaders={ApiController._getApiHeaders()} - avatarSrc={profileImage} - disabled={disabled} - style={style} - balance={showBalance ? CoreHelperUtil.formatBalance(balanceVal, balanceSymbol) : ''} - testID={testID} - /> - - ); -} diff --git a/packages/scaffold/src/modal/w3m-button/index.tsx b/packages/scaffold/src/modal/w3m-button/index.tsx deleted file mode 100644 index e6bf0481..00000000 --- a/packages/scaffold/src/modal/w3m-button/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { AccountButton, type AccountButtonProps } from '../w3m-account-button'; -import { ConnectButton, type ConnectButtonProps } from '../w3m-connect-button'; -import { AccountController, ModalController } from '@reown/appkit-core-react-native'; - -export interface AppKitButtonProps { - balance?: AccountButtonProps['balance']; - disabled?: AccountButtonProps['disabled']; - size?: ConnectButtonProps['size']; - label?: ConnectButtonProps['label']; - loadingLabel?: ConnectButtonProps['loadingLabel']; - accountStyle?: AccountButtonProps['style']; - connectStyle?: ConnectButtonProps['style']; -} - -export function AppKitButton({ - balance, - disabled, - size, - label = 'Connect', - loadingLabel = 'Connecting', - accountStyle, - connectStyle -}: AppKitButtonProps) { - const { isConnected } = useSnapshot(AccountController.state); - const { loading } = useSnapshot(ModalController.state); - - return !loading && isConnected ? ( - - ) : ( - - ); -} diff --git a/packages/scaffold/src/modal/w3m-connect-button/index.tsx b/packages/scaffold/src/modal/w3m-connect-button/index.tsx deleted file mode 100644 index 98f0c0e1..00000000 --- a/packages/scaffold/src/modal/w3m-connect-button/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { ModalController, ThemeController } from '@reown/appkit-core-react-native'; -import { - ConnectButton as ConnectButtonUI, - ThemeProvider, - type ConnectButtonProps as ConnectButtonUIProps -} from '@reown/appkit-ui-react-native'; - -export interface ConnectButtonProps { - label: string; - loadingLabel: string; - size?: ConnectButtonUIProps['size']; - style?: ConnectButtonUIProps['style']; - disabled?: ConnectButtonUIProps['disabled']; - testID?: string; -} - -export function ConnectButton({ - label, - loadingLabel, - size = 'md', - style, - disabled, - testID -}: ConnectButtonProps) { - const { open, loading } = useSnapshot(ModalController.state); - const { themeMode, themeVariables } = useSnapshot(ThemeController.state); - - return ( - - ModalController.open()} - size={size} - loading={loading || open} - style={style} - testID={testID} - disabled={disabled} - > - {loading || open ? loadingLabel : label} - - - ); -} diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx deleted file mode 100644 index 631bc2fa..00000000 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useCallback, useEffect } from 'react'; -import { useWindowDimensions, StatusBar } from 'react-native'; -import Modal from 'react-native-modal'; -import { Card, ThemeProvider } from '@reown/appkit-ui-react-native'; -import { - AccountController, - ApiController, - ConnectionController, - ConnectorController, - CoreHelperUtil, - EventsController, - ModalController, - OptionsController, - RouterController, - TransactionsController, - type AppKitFrameProvider, - WebviewController, - ThemeController -} from '@reown/appkit-core-react-native'; -import type { CaipAddress } from '@reown/appkit-common-react-native'; -import { SIWEController } from '@reown/appkit-siwe-react-native'; - -import { AppKitRouter } from '../w3m-router'; -import { Header } from '../../partials/w3m-header'; -import { Snackbar } from '../../partials/w3m-snackbar'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function AppKit() { - const { open, loading } = useSnapshot(ModalController.state); - const { connectors, connectedConnector } = useSnapshot(ConnectorController.state); - const { caipAddress, isConnected } = useSnapshot(AccountController.state); - const { frameViewVisible, webviewVisible } = useSnapshot(WebviewController.state); - const { themeMode, themeVariables } = useSnapshot(ThemeController.state); - const { height } = useWindowDimensions(); - const { isLandscape } = useCustomDimensions(); - const portraitHeight = height - 80; - const landScapeHeight = height * 0.95 - (StatusBar.currentHeight ?? 0); - const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; - const AuthView = authProvider?.AuthView; - const SocialView = authProvider?.Webview; - const showAuth = !connectedConnector || connectedConnector === 'AUTH'; - - const onBackButtonPress = () => { - if (RouterController.state.history.length > 1) { - return RouterController.goBack(); - } - - return handleClose(); - }; - - const prefetch = async () => { - await ApiController.prefetch(); - EventsController.sendEvent({ type: 'track', event: 'MODAL_LOADED' }); - }; - - const handleClose = async () => { - if (OptionsController.state.isSiweEnabled) { - if (SIWEController.state.status !== 'success' && AccountController.state.isConnected) { - await ConnectionController.disconnect(); - } - } - - if ( - RouterController.state.view === 'OnRampLoading' && - EventsController.state.data.event === 'BUY_SUBMITTED' - ) { - // Send event only if the onramp url was already created - EventsController.sendEvent({ type: 'track', event: 'BUY_CANCEL' }); - } - }; - - const onNewAddress = useCallback( - async (address?: CaipAddress) => { - if (!isConnected || loading) { - return; - } - - const newAddress = CoreHelperUtil.getPlainAddress(address); - TransactionsController.resetTransactions(); - - if (OptionsController.state.isSiweEnabled) { - const newNetworkId = CoreHelperUtil.getNetworkId(address); - - const { signOutOnAccountChange, signOutOnNetworkChange } = - SIWEController.state._client?.options ?? {}; - const session = await SIWEController.getSession(); - - if (session && newAddress && signOutOnAccountChange) { - // If the address has changed and signOnAccountChange is enabled, sign out - await SIWEController.signOut(); - onSiweNavigation(); - } else if ( - newNetworkId && - session?.chainId.toString() !== newNetworkId && - signOutOnNetworkChange - ) { - // If the network has changed and signOnNetworkChange is enabled, sign out - await SIWEController.signOut(); - onSiweNavigation(); - } else if (!session) { - // If it's connected but there's no session, show sign view - onSiweNavigation(); - } - } - }, - [isConnected, loading] - ); - - const onSiweNavigation = () => { - if (ModalController.state.open) { - RouterController.push('ConnectingSiwe'); - } else { - ModalController.open({ view: 'ConnectingSiwe' }); - } - }; - - useEffect(() => { - prefetch(); - }, []); - - useEffect(() => { - onNewAddress(caipAddress); - }, [caipAddress, onNewAddress]); - - return ( - <> - - - -
- - - - - {!!showAuth && AuthView && } - {!!showAuth && SocialView && } - - - ); -} diff --git a/packages/scaffold/src/modal/w3m-modal/styles.ts b/packages/scaffold/src/modal/w3m-modal/styles.ts deleted file mode 100644 index ac3a35a0..00000000 --- a/packages/scaffold/src/modal/w3m-modal/styles.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - modal: { - margin: 0, - justifyContent: 'flex-end' - }, - card: { - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0, - maxHeight: '80%' - } -}); diff --git a/packages/scaffold/src/modal/w3m-network-button/index.tsx b/packages/scaffold/src/modal/w3m-network-button/index.tsx deleted file mode 100644 index 353a1804..00000000 --- a/packages/scaffold/src/modal/w3m-network-button/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useSnapshot } from 'valtio'; -import type { StyleProp, ViewStyle } from 'react-native'; -import { - AccountController, - ApiController, - AssetUtil, - EventsController, - ModalController, - NetworkController, - ThemeController -} from '@reown/appkit-core-react-native'; -import { NetworkButton as NetworkButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native'; - -export interface NetworkButtonProps { - disabled?: boolean; - style?: StyleProp; -} - -export function NetworkButton({ disabled, style }: NetworkButtonProps) { - const { isConnected } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { loading } = useSnapshot(ModalController.state); - const { themeMode, themeVariables } = useSnapshot(ThemeController.state); - - const onNetworkPress = () => { - ModalController.open({ view: 'Networks' }); - EventsController.sendEvent({ - type: 'track', - event: 'CLICK_NETWORKS' - }); - }; - - return ( - - - {caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} - - - ); -} diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx deleted file mode 100644 index 761770ef..00000000 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { useLayoutEffect, useMemo } from 'react'; -import { useSnapshot } from 'valtio'; -import { RouterController } from '@reown/appkit-core-react-native'; - -import { AccountDefaultView } from '../../views/w3m-account-default-view'; -import { AccountView } from '../../views/w3m-account-view'; -import { AllWalletsView } from '../../views/w3m-all-wallets-view'; -import { ConnectView } from '../../views/w3m-connect-view'; -import { ConnectSocialsView } from '../../views/w3m-connect-socials-view'; -import { ConnectingView } from '../../views/w3m-connecting-view'; -import { ConnectingExternalView } from '../../views/w3m-connecting-external-view'; -import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; -import { ConnectingSocialView } from '../../views/w3m-connecting-social-view'; -import { CreateView } from '../../views/w3m-create-view'; -import { ConnectingSiweView } from '@reown/appkit-siwe-react-native'; -import { EmailVerifyOtpView } from '../../views/w3m-email-verify-otp-view'; -import { EmailVerifyDeviceView } from '../../views/w3m-email-verify-device-view'; -import { GetWalletView } from '../../views/w3m-get-wallet-view'; -import { NetworksView } from '../../views/w3m-networks-view'; -import { NetworkSwitchView } from '../../views/w3m-network-switch-view'; -import { OnRampLoadingView } from '../../views/w3m-onramp-loading-view'; -import { OnRampView } from '../../views/w3m-onramp-view'; -import { OnRampCheckoutView } from '../../views/w3m-onramp-checkout-view'; -import { OnRampSettingsView } from '../../views/w3m-onramp-settings-view'; -import { OnRampTransactionView } from '../../views/w3m-onramp-transaction-view'; -import { SwapView } from '../../views/w3m-swap-view'; -import { SwapPreviewView } from '../../views/w3m-swap-preview-view'; -import { SwapSelectTokenView } from '../../views/w3m-swap-select-token-view'; -import { TransactionsView } from '../../views/w3m-transactions-view'; -import { UnsupportedChainView } from '../../views/w3m-unsupported-chain-view'; -import { UpdateEmailWalletView } from '../../views/w3m-update-email-wallet-view'; -import { UpdateEmailPrimaryOtpView } from '../../views/w3m-update-email-primary-otp-view'; -import { UpdateEmailSecondaryOtpView } from '../../views/w3m-update-email-secondary-otp-view'; -import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-view'; -import { UpgradeToSmartAccountView } from '../../views/w3m-upgrade-to-smart-account-view'; -import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-networks-view'; -import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; -import { WalletSendView } from '../../views/w3m-wallet-send-view'; -import { WalletSendPreviewView } from '../../views/w3m-wallet-send-preview-view'; -import { WalletSendSelectTokenView } from '../../views/w3m-wallet-send-select-token-view'; -import { WhatIsANetworkView } from '../../views/w3m-what-is-a-network-view'; -import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; -import { UiUtil } from '../../utils/UiUtil'; - -export function AppKitRouter() { - const { view } = useSnapshot(RouterController.state); - - useLayoutEffect(() => { - UiUtil.createViewTransition(); - }, [view]); - - const ViewComponent = useMemo(() => { - switch (view) { - case 'Account': - return AccountView; - case 'AccountDefault': - return AccountDefaultView; - case 'AllWallets': - return AllWalletsView; - case 'Connect': - return ConnectView; - case 'ConnectSocials': - return ConnectSocialsView; - case 'ConnectingExternal': - return ConnectingExternalView; - case 'ConnectingSiwe': - return ConnectingSiweView; - case 'ConnectingSocial': - return ConnectingSocialView; - case 'ConnectingFarcaster': - return ConnectingFarcasterView; - case 'ConnectingWalletConnect': - return ConnectingView; - case 'Create': - return CreateView; - case 'EmailVerifyDevice': - return EmailVerifyDeviceView; - case 'EmailVerifyOtp': - return EmailVerifyOtpView; - case 'GetWallet': - return GetWalletView; - case 'Networks': - return NetworksView; - case 'OnRamp': - return OnRampView; - case 'OnRampCheckout': - return OnRampCheckoutView; - case 'OnRampSettings': - return OnRampSettingsView; - case 'OnRampLoading': - return OnRampLoadingView; - case 'SwitchNetwork': - return NetworkSwitchView; - case 'OnRampTransaction': - return OnRampTransactionView; - case 'Swap': - return SwapView; - case 'SwapPreview': - return SwapPreviewView; - case 'SwapSelectToken': - return SwapSelectTokenView; - case 'Transactions': - return TransactionsView; - case 'UnsupportedChain': - return UnsupportedChainView; - case 'UpdateEmailPrimaryOtp': - return UpdateEmailPrimaryOtpView; - case 'UpdateEmailSecondaryOtp': - return UpdateEmailSecondaryOtpView; - case 'UpdateEmailWallet': - return UpdateEmailWalletView; - case 'UpgradeEmailWallet': - return UpgradeEmailWalletView; - case 'UpgradeToSmartAccount': - return UpgradeToSmartAccountView; - case 'WalletCompatibleNetworks': - return WalletCompatibleNetworks; - case 'WalletReceive': - return WalletReceiveView; - case 'WalletSend': - return WalletSendView; - case 'WalletSendPreview': - return WalletSendPreviewView; - case 'WalletSendSelectToken': - return WalletSendSelectTokenView; - case 'WhatIsANetwork': - return WhatIsANetworkView; - case 'WhatIsAWallet': - return WhatIsAWalletView; - default: - return ConnectView; - } - }, [view]); - - return ; -} diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx deleted file mode 100644 index 3ec7ee05..00000000 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { ScrollView, View, type StyleProp, type ViewStyle, RefreshControl } from 'react-native'; -import { - FlexView, - Link, - ListTransaction, - LoadingSpinner, - Text, - TransactionUtil, - useTheme -} from '@reown/appkit-ui-react-native'; -import { type Transaction, type TransactionImage } from '@reown/appkit-common-react-native'; -import { - AccountController, - AssetUtil, - EventsController, - NetworkController, - OptionsController, - TransactionsController -} from '@reown/appkit-core-react-native'; -import { Placeholder } from '../w3m-placeholder'; -import { getTransactionListItemProps } from './utils'; -import styles from './styles'; - -interface Props { - style?: StyleProp; -} - -export function AccountActivity({ style }: Props) { - const Theme = useTheme(); - const [refreshing, setRefreshing] = useState(false); - const [initialLoad, setInitialLoad] = useState(true); - const { loading, transactions, next } = useSnapshot(TransactionsController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - - const handleLoadMore = () => { - TransactionsController.fetchTransactions(AccountController.state.address); - EventsController.sendEvent({ - type: 'track', - event: 'LOAD_MORE_TRANSACTIONS', - properties: { - address: AccountController.state.address, - projectId: OptionsController.state.projectId, - cursor: TransactionsController.state.next, - isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' - } - }); - }; - - const onRefresh = useCallback(async () => { - setRefreshing(true); - await TransactionsController.fetchTransactions(AccountController.state.address, true); - setRefreshing(false); - }, []); - - const transactionsByYear = useMemo(() => { - return TransactionsController.getTransactionsByYearAndMonth(transactions as Transaction[]); - }, [transactions]); - - useEffect(() => { - if (!TransactionsController.state.transactions.length) { - TransactionsController.fetchTransactions(AccountController.state.address, true); - } - // Set initial load to false after first fetch - const timer = setTimeout(() => setInitialLoad(false), 100); - - return () => clearTimeout(timer); - }, []); - - // Show loading spinner during initial load or when loading with no transactions - if ((initialLoad || loading) && !transactions.length) { - return ( - - - - ); - } - - // Only show placeholder when we're not in initial load or loading state - if (!Object.keys(transactionsByYear).length && !loading && !initialLoad) { - return ( - - ); - } - - return ( - - } - > - {Object.keys(transactionsByYear) - .reverse() - .map(year => ( - - {Object.keys(transactionsByYear[year] || {}) - .reverse() - .map(month => ( - - - {TransactionUtil.getTransactionGroupTitle(year, month)} - - {transactionsByYear[year]?.[month]?.map((transaction: Transaction) => { - const { date, type, descriptions, status, images, isAllNFT, transfers } = - getTransactionListItemProps(transaction); - const hasMultipleTransfers = transfers?.length > 2; - - // Show only the first transfer - if (hasMultipleTransfers) { - const description = TransactionUtil.getTransferDescription(transfers[0]); - - return ( - - ); - } - - return ( - - ); - })} - - ))} - - ))} - {(next || loading) && !refreshing && ( - - {next && !loading && ( - - Load more - - )} - {loading && } - - )} - - ); -} diff --git a/packages/scaffold/src/partials/w3m-account-activity/styles.ts b/packages/scaffold/src/partials/w3m-account-activity/styles.ts deleted file mode 100644 index 64c13aab..00000000 --- a/packages/scaffold/src/partials/w3m-account-activity/styles.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - paddingHorizontal: Spacing.xs - }, - contentContainer: { - paddingBottom: Spacing.m - }, - separatorText: { - marginVertical: Spacing.xs - }, - transactionItem: { - marginVertical: Spacing.xs - }, - footer: { - height: 40 - }, - placeholder: { - minHeight: 200, - flex: 0 - }, - loadMoreButton: { - alignSelf: 'center', - width: 100, - marginVertical: Spacing.xs - } -}); diff --git a/packages/scaffold/src/partials/w3m-account-activity/utils.ts b/packages/scaffold/src/partials/w3m-account-activity/utils.ts deleted file mode 100644 index be865523..00000000 --- a/packages/scaffold/src/partials/w3m-account-activity/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { DateUtil, type Transaction } from '@reown/appkit-common-react-native'; -import { TransactionUtil } from '@reown/appkit-ui-react-native'; -import type { TransactionType } from '@reown/appkit-ui-react-native/lib/typescript/utils/TypesUtil'; - -export function getTransactionListItemProps(transaction: Transaction) { - const date = DateUtil.formatDate(transaction?.metadata?.minedAt); - const descriptions = TransactionUtil.getTransactionDescriptions(transaction); - - const transfers = transaction?.transfers; - const transfer = transaction?.transfers?.[0]; - const isAllNFT = - Boolean(transfer) && transaction?.transfers?.every(item => Boolean(item.nft_info)); - const images = TransactionUtil.getTransactionImages(transfers); - - return { - date, - direction: transfer?.direction, - descriptions, - isAllNFT, - images, - status: transaction.metadata?.status, - transfers, - type: transaction.metadata?.operationType as TransactionType - }; -} diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx deleted file mode 100644 index 26db07f9..00000000 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useCallback, useState } from 'react'; -import { - RefreshControl, - ScrollView, - StyleSheet, - type StyleProp, - type ViewStyle -} from 'react-native'; -import { useSnapshot } from 'valtio'; -import { - AccountController, - AssetUtil, - NetworkController, - RouterController -} from '@reown/appkit-core-react-native'; -import { - FlexView, - ListItem, - Text, - ListToken, - useTheme, - Spacing -} from '@reown/appkit-ui-react-native'; - -interface Props { - style?: StyleProp; -} - -export function AccountTokens({ style }: Props) { - const Theme = useTheme(); - const [refreshing, setRefreshing] = useState(false); - const { tokenBalance } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - - const onRefresh = useCallback(async () => { - setRefreshing(true); - AccountController.fetchTokenBalance(); - setRefreshing(false); - }, []); - - const onReceivePress = () => { - RouterController.push('WalletReceive'); - }; - - if (!tokenBalance?.length) { - return ( - - - - Receive funds - - - Transfer tokens on your wallet - - - - ); - } - - return ( - - } - > - {tokenBalance.map(token => ( - - ))} - - ); -} - -const styles = StyleSheet.create({ - receiveButton: { - width: 'auto', - marginHorizontal: Spacing.s - } -}); diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx deleted file mode 100644 index 66de6277..00000000 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { useState } from 'react'; -import { useSnapshot } from 'valtio'; -import { Balance, FlexView, IconLink, Tabs } from '@reown/appkit-ui-react-native'; -import { - AccountController, - ConstantsUtil, - CoreHelperUtil, - EventsController, - NetworkController, - OnRampController, - OptionsController, - RouterController, - SwapController -} from '@reown/appkit-core-react-native'; -import type { Balance as BalanceType } from '@reown/appkit-common-react-native'; -import { AccountActivity } from '../w3m-account-activity'; -import { AccountTokens } from '../w3m-account-tokens'; -import styles from './styles'; - -export interface AccountWalletFeaturesProps { - value: string; -} - -export function AccountWalletFeatures() { - const [activeTab, setActiveTab] = useState(0); - const { tokenBalance } = useSnapshot(AccountController.state); - const { features, isOnRampEnabled } = useSnapshot(OptionsController.state); - const balance = CoreHelperUtil.calculateAndFormatBalance(tokenBalance as BalanceType[]); - const isSwapsEnabled = features?.swaps; - - const onTabChange = (index: number) => { - setActiveTab(index); - if (index === 2) { - onTransactionsPress(); - } - }; - - const onTransactionsPress = () => { - EventsController.sendEvent({ - type: 'track', - event: 'CLICK_TRANSACTIONS', - properties: { - isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' - } - }); - }; - - const onSwapPress = () => { - if ( - NetworkController.state.caipNetwork?.id && - !ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(`${NetworkController.state.caipNetwork.id}`) - ) { - RouterController.push('UnsupportedChain'); - } else { - SwapController.resetState(); - EventsController.sendEvent({ - type: 'track', - event: 'OPEN_SWAP', - properties: { - network: NetworkController.state.caipNetwork?.id || '', - isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' - } - }); - RouterController.push('Swap'); - } - }; - - const onSendPress = () => { - EventsController.sendEvent({ - type: 'track', - event: 'OPEN_SEND', - properties: { - network: NetworkController.state.caipNetwork?.id || '', - isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' - } - }); - RouterController.push('WalletSend'); - }; - - const onReceivePress = () => { - RouterController.push('WalletReceive'); - }; - - const onBuyPress = () => { - EventsController.sendEvent({ - type: 'track', - event: 'SELECT_BUY_CRYPTO' - }); - OnRampController.resetState(); - RouterController.push('OnRamp'); - }; - - return ( - - - - {isOnRampEnabled && ( - - )} - {isSwapsEnabled && ( - - )} - - - - - - - - {activeTab === 0 && } - {activeTab === 1 && } - - - ); -} diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts deleted file mode 100644 index 6722e5bf..00000000 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - height: 400 - }, - balanceText: { - fontSize: 40, - fontWeight: '500' - }, - actionsContainer: { - width: '100%', - marginTop: Spacing.s, - marginBottom: Spacing.l - }, - action: { - flex: 1, - height: 52 - }, - actionLeft: { - marginRight: 8 - }, - actionRight: { - marginLeft: 8 - }, - actionCenter: { - marginHorizontal: 8 - }, - tab: { - width: '100%', - paddingHorizontal: Spacing.s - }, - tabContainer: { - flex: 1, - width: '100%' - }, - tabContent: { - paddingHorizontal: Spacing.m - } -}); diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx deleted file mode 100644 index d2d6040a..00000000 --- a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useSnapshot } from 'valtio'; -import { FlatList, View } from 'react-native'; -import { - ApiController, - AssetUtil, - OptionsController, - SnackController, - type OptionsControllerState, - type WcWallet -} from '@reown/appkit-core-react-native'; -import { - CardSelect, - CardSelectLoader, - CardSelectHeight, - FlexView, - Spacing -} from '@reown/appkit-ui-react-native'; -import styles from './styles'; -import { UiUtil } from '../../utils/UiUtil'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { Placeholder } from '../w3m-placeholder'; - -interface AllWalletsListProps { - columns: number; - onItemPress: (wallet: WcWallet) => void; - itemWidth?: number; -} - -export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsListProps) { - const [loading, setLoading] = useState(ApiController.state.wallets.length === 0); - const [loadingError, setLoadingError] = useState(false); - const [pageLoading, setPageLoading] = useState(false); - const { maxWidth, padding } = useCustomDimensions(); - const { installed, featured, recommended, wallets } = useSnapshot(ApiController.state); - const { customWallets } = useSnapshot(OptionsController.state) as OptionsControllerState; - const imageHeaders = ApiController._getApiHeaders(); - const preloadedWallets = installed.length + featured.length + recommended.length; - const loadingItems = columns - ((100 + preloadedWallets) % columns); - - const combinedWallets = [ - ...(customWallets ?? []), - ...installed, - ...featured, - ...recommended, - ...wallets - ]; - - // Deduplicate by wallet ID - const uniqueWallets = Array.from( - new Map(combinedWallets.map(wallet => [wallet?.id, wallet])).values() - ).filter(wallet => wallet?.id); // Filter out any undefined wallets - - const walletList = [ - ...uniqueWallets, - ...(pageLoading ? (Array.from({ length: loadingItems }) as WcWallet[]) : []) - ]; - - const ITEM_HEIGHT = CardSelectHeight + Spacing.xs * 2; - - const loadingTemplate = (items: number) => { - return ( - - {Array.from({ length: items }).map((_, index) => ( - - - - ))} - - ); - }; - - const walletTemplate = ({ item }: { item: WcWallet; index: number }) => { - const isInstalled = ApiController.state.installed.find(wallet => wallet?.id === item?.id); - if (!item?.id) { - return ( - - - - ); - } - - return ( - - onItemPress(item)} - installed={!!isInstalled} - /> - - ); - }; - - const initialFetch = async () => { - try { - setLoading(true); - setLoadingError(false); - await ApiController.fetchWallets({ page: 1 }); - UiUtil.createViewTransition(); - setLoading(false); - } catch (error) { - SnackController.showError('Failed to load wallets'); - setLoading(false); - setLoadingError(true); - } - }; - - const fetchNextPage = async () => { - try { - if ( - walletList.length < ApiController.state.count && - !pageLoading && - !loading && - ApiController.state.page > 0 - ) { - setPageLoading(true); - await ApiController.fetchWallets({ page: ApiController.state.page + 1 }); - setPageLoading(false); - } - } catch (error) { - SnackController.showError('Failed to load more wallets'); - setPageLoading(false); - } - }; - - useEffect(() => { - if (!ApiController.state.wallets.length) { - initialFetch(); - } - }, []); - - if (loading) { - return loadingTemplate(20); - } - - if (loadingError) { - return ( - - ); - } - - return ( - item?.id ?? index} - getItemLayout={(_, index) => ({ - length: ITEM_HEIGHT, - offset: ITEM_HEIGHT * index, - index - })} - /> - ); -} diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/styles.ts b/packages/scaffold/src/partials/w3m-all-wallets-list/styles.ts deleted file mode 100644 index 4e6c30f0..00000000 --- a/packages/scaffold/src/partials/w3m-all-wallets-list/styles.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - height: '100%' - }, - contentContainer: { - paddingBottom: Spacing['2xl'] - }, - itemContainer: { - alignItems: 'center', - justifyContent: 'center', - marginVertical: Spacing.xs - }, - pageLoader: { - marginTop: Spacing.xl - }, - errorContainer: { - height: '90%' - }, - placeholderContainer: { - flex: 0, - height: '90%' - } -}); diff --git a/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx b/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx deleted file mode 100644 index 172f3b09..00000000 --- a/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { FlatList, View } from 'react-native'; -import { - ApiController, - AssetUtil, - SnackController, - type WcWallet -} from '@reown/appkit-core-react-native'; -import { - CardSelect, - CardSelectHeight, - CardSelectLoader, - FlexView, - Spacing -} from '@reown/appkit-ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { Placeholder } from '../w3m-placeholder'; -import styles from './styles'; - -export interface AllWalletsSearchProps { - columns: number; - onItemPress: (wallet: WcWallet) => void; - itemWidth?: number; - searchQuery?: string; -} - -export function AllWalletsSearch({ - searchQuery, - columns, - itemWidth, - onItemPress -}: AllWalletsSearchProps) { - const [loading, setLoading] = useState(false); - const [loadingError, setLoadingError] = useState(false); - const [prevSearchQuery, setPrevSearchQuery] = useState(''); - const imageHeaders = ApiController._getApiHeaders(); - const { maxWidth, padding, isLandscape } = useCustomDimensions(); - - const ITEM_HEIGHT = CardSelectHeight + Spacing.xs * 2; - - const walletTemplate = ({ item }: { item: WcWallet }) => { - const isInstalled = ApiController.state.installed.find(wallet => wallet?.id === item?.id); - - return ( - - onItemPress(item)} - installed={!!isInstalled} - testID={`wallet-search-item-${item?.id}`} - /> - - ); - }; - - const loadingTemplate = (items: number) => { - return ( - - {Array.from({ length: items }).map((_, index) => ( - - - - ))} - - ); - }; - - const emptyTemplate = () => { - return ( - - ); - }; - - const searchFetch = useCallback(async () => { - try { - setLoading(true); - setLoadingError(false); - await ApiController.searchWallet({ search: searchQuery }); - setLoading(false); - } catch (error) { - SnackController.showError('Failed to load wallets'); - setLoading(false); - setLoadingError(true); - } - }, [searchQuery]); - - useEffect(() => { - if (prevSearchQuery !== searchQuery) { - setPrevSearchQuery(searchQuery || ''); - searchFetch(); - } - }, [searchQuery, prevSearchQuery, searchFetch]); - - if (loading) { - return loadingTemplate(20); - } - - if (loadingError) { - return ( - - ); - } - - if (ApiController.state.search.length === 0) { - return emptyTemplate(); - } - - return ( - item.id} - getItemLayout={(_, index) => ({ - length: ITEM_HEIGHT, - offset: ITEM_HEIGHT * index, - index - })} - /> - ); -} diff --git a/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts b/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts deleted file mode 100644 index d425dea3..00000000 --- a/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - height: '100%' - }, - contentContainer: { - paddingBottom: Spacing['2xl'] - }, - placeholderContainer: { - flex: 0, - height: '90%' - }, - emptyContainer: { - flex: 0, - height: '90%' - }, - emptyLandscape: { - paddingTop: '10%' - }, - itemContainer: { - alignItems: 'center', - justifyContent: 'center', - marginVertical: Spacing.xs - }, - text: { - marginTop: Spacing.xs - } -}); diff --git a/packages/scaffold/src/partials/w3m-connecting-body/index.tsx b/packages/scaffold/src/partials/w3m-connecting-body/index.tsx deleted file mode 100644 index 0d0e8c2e..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-body/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { FlexView, Spacing, Text } from '@reown/appkit-ui-react-native'; - -export * from './utils'; - -export interface ConnectingBodyProps { - title: string; - description?: string; -} - -export function ConnectingBody({ title, description }: ConnectingBodyProps) { - return ( - - {title} - {description && ( - - {description} - - )} - - ); -} - -const styles = StyleSheet.create({ - textContainer: { - marginVertical: Spacing.xs - }, - descriptionText: { - marginTop: Spacing.xs, - marginHorizontal: Spacing['3xl'] - } -}); diff --git a/packages/scaffold/src/partials/w3m-connecting-body/utils.ts b/packages/scaffold/src/partials/w3m-connecting-body/utils.ts deleted file mode 100644 index 49f60b72..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-body/utils.ts +++ /dev/null @@ -1,34 +0,0 @@ -export type BodyErrorType = 'not_installed' | 'default' | 'declined' | undefined; - -interface Props { - walletName?: string; - declined?: boolean; - errorType?: BodyErrorType; - isWeb?: boolean; -} - -export const getMessage = ({ walletName, declined, errorType, isWeb }: Props) => { - if (declined || errorType === 'declined') { - return { - title: 'Connection declined', - description: 'Connection can be declined if a previous request is still active' - }; - } - - switch (errorType) { - case 'not_installed': - return { title: 'App not installed' }; - case 'default': - return { - title: 'Connection error', - description: 'There was an unexpected connection error.' - }; - default: - return { - title: `Continue in ${walletName ?? 'Wallet'}`, - description: isWeb - ? 'Open and continue in a browser tab' - : 'Accept connection request in the wallet' - }; - } -}; diff --git a/packages/scaffold/src/partials/w3m-connecting-header/index.tsx b/packages/scaffold/src/partials/w3m-connecting-header/index.tsx deleted file mode 100644 index 45a11931..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-header/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { Platform } from '@reown/appkit-core-react-native'; -import { FlexView, Tabs, type IconType } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export interface ConnectingHeaderProps { - platforms: Platform[]; - onSelectPlatform: (platform: Platform) => void; -} - -interface Tab { - label: string; - icon: IconType; - platform: Platform; -} - -export function ConnectingHeader({ platforms, onSelectPlatform }: ConnectingHeaderProps) { - const generateTabs = () => { - const tabs = platforms - .map(platform => { - if (platform === 'mobile') { - return { label: 'Mobile', icon: 'mobile', platform: 'mobile' } as const; - } else if (platform === 'web') { - return { label: 'Web', icon: 'browser', platform: 'web' } as const; - } else { - return undefined; - } - }) - .filter(Boolean) as Tab[]; - - return tabs; - }; - - const onTabChange = (index: number) => { - const platform = platforms[index]; - if (platform) { - onSelectPlatform(platform); - } - }; - - const tabs = generateTabs(); - - return ( - - - - ); -} - -const styles = StyleSheet.create({ - tab: { - maxWidth: '50%' - } -}); diff --git a/packages/scaffold/src/partials/w3m-connecting-mobile/components/StoreLink.tsx b/packages/scaffold/src/partials/w3m-connecting-mobile/components/StoreLink.tsx deleted file mode 100644 index c1f72476..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-mobile/components/StoreLink.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { ActionEntry, Button, Spacing, Text } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export interface StoreLinkProps { - visible: boolean; - walletName?: string; - onPress: () => void; -} - -export function StoreLink({ visible, walletName = 'Wallet', onPress }: StoreLinkProps) { - if (!visible) return null; - - return ( - - - {`Don't have ${walletName}?`} - - - - ); -} - -const styles = StyleSheet.create({ - storeButton: { - justifyContent: 'space-between', - paddingHorizontal: Spacing.l, - marginHorizontal: Spacing.xl, - marginTop: Spacing.l - } -}); diff --git a/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx b/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx deleted file mode 100644 index fe52031d..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-mobile/index.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useCallback, useEffect, useState } from 'react'; -import { Platform, ScrollView } from 'react-native'; -import { - RouterController, - ApiController, - AssetUtil, - ConnectionController, - CoreHelperUtil, - OptionsController, - EventsController, - ConstantsUtil -} from '@reown/appkit-core-react-native'; -import { - Button, - FlexView, - LoadingThumbnail, - WalletImage, - Link, - IconBox -} from '@reown/appkit-ui-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { UiUtil } from '../../utils/UiUtil'; -import { StoreLink } from './components/StoreLink'; -import { ConnectingBody, getMessage, type BodyErrorType } from '../w3m-connecting-body'; -import styles from './styles'; - -interface Props { - onRetry: () => void; - onCopyUri: (uri?: string) => void; - isInstalled?: boolean; -} - -export function ConnectingMobile({ onRetry, onCopyUri, isInstalled }: Props) { - const { data } = RouterController.state; - const { maxWidth: width } = useCustomDimensions(); - const { wcUri, wcError } = useSnapshot(ConnectionController.state); - const [errorType, setErrorType] = useState(); - const showCopy = - OptionsController.isClipboardAvailable() && - errorType !== 'not_installed' && - !CoreHelperUtil.isLinkModeURL(wcUri); - - const showRetry = errorType !== 'not_installed'; - const bodyMessage = getMessage({ walletName: data?.wallet?.name, errorType, declined: wcError }); - - const storeUrl = Platform.select({ - ios: data?.wallet?.app_store, - android: data?.wallet?.play_store - }); - - const onRetryPress = () => { - setErrorType(undefined); - ConnectionController.setWcError(false); - onRetry?.(); - }; - - const onStorePress = () => { - if (storeUrl) { - CoreHelperUtil.openLink(storeUrl); - } - }; - - const onConnect = useCallback(async () => { - try { - const { name, mobile_link } = data?.wallet ?? {}; - if (name && mobile_link && wcUri) { - const { redirect, href } = CoreHelperUtil.formatNativeUrl(mobile_link, wcUri); - const wcLinking = { name, href }; - ConnectionController.setWcLinking(wcLinking); - ConnectionController.setPressedWallet(data?.wallet); - await CoreHelperUtil.openLink(redirect); - await ConnectionController.state.wcPromise; - UiUtil.storeConnectedWallet(wcLinking, data?.wallet); - EventsController.sendEvent({ - type: 'track', - event: 'CONNECT_SUCCESS', - properties: { - method: 'mobile', - name: data?.wallet?.name ?? 'Unknown', - explorer_id: data?.wallet?.id - } - }); - } - } catch (error: any) { - if (error.message.includes(ConstantsUtil.LINKING_ERROR)) { - setErrorType('not_installed'); - } else { - setErrorType('default'); - } - } - }, [wcUri, data]); - - useEffect(() => { - if (wcUri) { - onConnect(); - } - }, [wcUri, onConnect]); - - return ( - - - - - {wcError && ( - - )} - - - {showRetry && ( - - )} - - {showCopy && ( - onCopyUri(wcUri)} - > - Copy link - - )} - - - ); -} diff --git a/packages/scaffold/src/partials/w3m-connecting-mobile/styles.ts b/packages/scaffold/src/partials/w3m-connecting-mobile/styles.ts deleted file mode 100644 index d84e2bc6..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-mobile/styles.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - container: { - paddingBottom: Spacing['3xl'] - }, - retryButton: { - marginTop: Spacing.m - }, - retryIcon: { - transform: [{ rotateY: '180deg' }] - }, - copyButton: { - alignSelf: 'center', - marginTop: Spacing.m - }, - errorIcon: { - position: 'absolute', - bottom: 5, - right: 5, - zIndex: 2 - } -}); diff --git a/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx b/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx deleted file mode 100644 index 3a035706..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useEffect } from 'react'; -import { useSnapshot } from 'valtio'; -import { - AssetUtil, - ConnectionController, - ConnectorController, - EventsController, - OptionsController, - SnackController -} from '@reown/appkit-core-react-native'; -import { FlexView, Link, QrCode, Text, Spacing } from '@reown/appkit-ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function ConnectingQrCode() { - const { wcUri } = useSnapshot(ConnectionController.state); - const showCopy = OptionsController.isClipboardAvailable(); - const { maxWidth: windowSize, isPortrait } = useCustomDimensions(); - const qrSize = (windowSize - Spacing.xl * 2) / (isPortrait ? 1 : 1.5); - - const onCopyAddress = () => { - if (ConnectionController.state.wcUri) { - OptionsController.copyToClipboard(ConnectionController.state.wcUri); - SnackController.showSuccess('Link copied'); - } - }; - - const onConnect = async () => { - await ConnectionController.state.wcPromise; - - EventsController.sendEvent({ - type: 'track', - event: 'CONNECT_SUCCESS', - properties: { - method: 'qrcode', - name: 'WalletConnect' - } - }); - - const connectors = ConnectorController.state.connectors; - const connector = connectors.find(c => c.type === 'WALLET_CONNECT'); - const url = AssetUtil.getConnectorImage(connector); - ConnectionController.setConnectedWalletImageUrl(url); - }; - - useEffect(() => { - if (wcUri) { - onConnect(); - } - }, [wcUri]); - - return ( - - - - Scan this QR code with your phone - {showCopy && ( - - Copy link - - )} - - - ); -} diff --git a/packages/scaffold/src/partials/w3m-connecting-qrcode/styles.ts b/packages/scaffold/src/partials/w3m-connecting-qrcode/styles.ts deleted file mode 100644 index c6a0df01..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-qrcode/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - copyButton: { - marginTop: Spacing.m - } -}); diff --git a/packages/scaffold/src/partials/w3m-connecting-web/index.tsx b/packages/scaffold/src/partials/w3m-connecting-web/index.tsx deleted file mode 100644 index 6d7a4825..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-web/index.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useCallback } from 'react'; -import { Linking, ScrollView } from 'react-native'; -import { - RouterController, - ApiController, - AssetUtil, - ConnectionController, - CoreHelperUtil, - OptionsController, - EventsController -} from '@reown/appkit-core-react-native'; -import { - Button, - FlexView, - LoadingThumbnail, - WalletImage, - Link, - IconBox -} from '@reown/appkit-ui-react-native'; - -import { UiUtil } from '../../utils/UiUtil'; -import { ConnectingBody, getMessage } from '../w3m-connecting-body'; -import styles from './styles'; - -interface ConnectingWebProps { - onCopyUri: (uri?: string) => void; -} - -export function ConnectingWeb({ onCopyUri }: ConnectingWebProps) { - const { data } = RouterController.state; - const { wcUri, wcError } = useSnapshot(ConnectionController.state); - const showCopy = OptionsController.isClipboardAvailable(); - const bodyMessage = getMessage({ - walletName: data?.wallet?.name, - declined: wcError, - isWeb: true - }); - - const onConnect = useCallback(async () => { - try { - const { name, webapp_link } = data?.wallet ?? {}; - if (name && webapp_link && wcUri) { - ConnectionController.setWcError(false); - const { redirect, href } = CoreHelperUtil.formatUniversalUrl(webapp_link, wcUri); - const wcLinking = { name, href }; - ConnectionController.setWcLinking(wcLinking); - ConnectionController.setPressedWallet(data?.wallet); - await Linking.openURL(redirect); - await ConnectionController.state.wcPromise; - - UiUtil.storeConnectedWallet(wcLinking, data?.wallet); - - EventsController.sendEvent({ - type: 'track', - event: 'CONNECT_SUCCESS', - properties: { - method: 'web', - name: data?.wallet?.name ?? 'Unknown', - explorer_id: data?.wallet?.id - } - }); - } - } catch {} - }, [data?.wallet, wcUri]); - - return ( - - - - - {wcError && ( - - )} - - - - {showCopy && ( - onCopyUri(wcUri)} - > - Copy link - - )} - - - ); -} diff --git a/packages/scaffold/src/partials/w3m-connecting-web/styles.ts b/packages/scaffold/src/partials/w3m-connecting-web/styles.ts deleted file mode 100644 index 5247da44..00000000 --- a/packages/scaffold/src/partials/w3m-connecting-web/styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - openButton: { - marginTop: Spacing.m - }, - copyButton: { - marginTop: Spacing.m - }, - errorIcon: { - position: 'absolute', - bottom: 5, - right: 5, - zIndex: 2 - }, - marginBottom: { - marginBottom: Spacing.xs - } -}); diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx deleted file mode 100644 index 7ce32ce6..00000000 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { - RouterController, - ModalController, - EventsController, - type RouterControllerState, - ConnectionController, - ConnectorController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; -import { IconLink, Text, FlexView } from '@reown/appkit-ui-react-native'; -import { StringUtil } from '@reown/appkit-common-react-native'; - -import styles from './styles'; - -export function Header() { - const { data, view } = useSnapshot(RouterController.state); - const onHelpPress = () => { - RouterController.push('WhatIsAWallet'); - EventsController.sendEvent({ type: 'track', event: 'CLICK_WALLET_HELP' }); - }; - - const headings = (_data: RouterControllerState['data'], _view: RouterControllerState['view']) => { - const connectorName = _data?.connector?.name; - const walletName = _data?.wallet?.name; - const networkName = _data?.network?.name; - const socialName = ConnectionController.state.selectedSocialProvider - ? StringUtil.capitalize(ConnectionController.state.selectedSocialProvider) - : undefined; - - return { - Account: undefined, - AccountDefault: undefined, - AllWallets: 'All wallets', - Connect: 'Connect wallet', - ConnectSocials: 'All socials', - ConnectingExternal: connectorName ?? 'Connect wallet', - ConnectingSiwe: undefined, - ConnectingFarcaster: socialName ?? 'Connecting Social', - ConnectingSocial: socialName ?? 'Connecting Social', - ConnectingWalletConnect: walletName ?? 'WalletConnect', - Create: 'Create wallet', - EmailVerifyDevice: ' ', - EmailVerifyOtp: 'Confirm email', - GetWallet: 'Get a wallet', - Networks: 'Select network', - OnRamp: undefined, - OnRampCheckout: 'Checkout', - OnRampSettings: 'Preferences', - OnRampLoading: undefined, - OnRampTransaction: ' ', - SwitchNetwork: networkName ?? 'Switch network', - Swap: 'Swap', - SwapSelectToken: 'Select token', - SwapPreview: 'Review swap', - Transactions: 'Activity', - UnsupportedChain: 'Switch network', - UpdateEmailPrimaryOtp: 'Confirm current email', - UpdateEmailSecondaryOtp: 'Confirm new email', - UpdateEmailWallet: 'Edit email', - UpgradeEmailWallet: 'Upgrade wallet', - UpgradeToSmartAccount: undefined, - WalletCompatibleNetworks: 'Compatible networks', - WalletReceive: 'Receive', - WalletSend: 'Send', - WalletSendPreview: 'Review send', - WalletSendSelectToken: 'Select token', - WhatIsANetwork: 'What is a network?', - WhatIsAWallet: 'What is a wallet?' - }[_view]; - }; - - const noCloseViews = ['OnRampSettings']; - const showClose = !noCloseViews.includes(view); - const header = headings(data, view); - - const checkSocial = () => { - if ( - RouterController.state.view === 'ConnectingFarcaster' || - RouterController.state.view === 'ConnectingSocial' - ) { - const socialProvider = ConnectionController.state.selectedSocialProvider; - const authProvider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; - - if (authProvider && socialProvider === 'farcaster') { - // TODO: remove this once Farcaster session refresh is implemented - // @ts-expect-error - authProvider.webviewRef?.current?.reload(); - } - - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_CANCELED', - properties: { provider: ConnectionController.state.selectedSocialProvider! } - }); - } - }; - - const handleGoBack = () => { - checkSocial(); - RouterController.goBack(); - }; - - const handleClose = () => { - checkSocial(); - ModalController.close(); - }; - - const dynamicButtonTemplate = () => { - const showBack = RouterController.state.history.length > 1; - const showHelp = RouterController.state.view === 'Connect'; - - if (showHelp) { - return ; - } - - if (showBack) { - return ; - } - - return ; - }; - - if (!header) return null; - - const bottomPadding = header === ' ' ? '0' : '4xs'; - - return ( - - {dynamicButtonTemplate()} - - {header} - - {showClose ? ( - - ) : ( - - )} - - ); -} diff --git a/packages/scaffold/src/partials/w3m-header/styles.ts b/packages/scaffold/src/partials/w3m-header/styles.ts deleted file mode 100644 index f26ba320..00000000 --- a/packages/scaffold/src/partials/w3m-header/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - iconPlaceholder: { - height: 32, - width: 32 - } -}); diff --git a/packages/scaffold/src/partials/w3m-information-modal/index.tsx b/packages/scaffold/src/partials/w3m-information-modal/index.tsx deleted file mode 100644 index 2392c6aa..00000000 --- a/packages/scaffold/src/partials/w3m-information-modal/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import Modal from 'react-native-modal'; -import { - FlexView, - Text, - type IconType, - IconBox, - useTheme, - Button -} from '@reown/appkit-ui-react-native'; -import styles from './styles'; - -interface InformationModalProps { - iconName: IconType; - title?: string; - description?: string; - visible: boolean; - onClose: () => void; -} - -export function InformationModal({ - iconName, - title, - description, - visible, - onClose -}: InformationModalProps) { - const Theme = useTheme(); - - return ( - - - - {!!title && ( - - {title} - - )} - - {!!description && ( - - {description} - - )} - - - - ); -} diff --git a/packages/scaffold/src/partials/w3m-information-modal/styles.ts b/packages/scaffold/src/partials/w3m-information-modal/styles.ts deleted file mode 100644 index 5fe4bd34..00000000 --- a/packages/scaffold/src/partials/w3m-information-modal/styles.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - modal: { - margin: 0, - justifyContent: 'flex-end' - }, - content: { - borderTopLeftRadius: 16, - borderTopRightRadius: 16, - alignItems: 'center' - }, - title: { - marginTop: Spacing.s, - marginBottom: Spacing.xs - }, - button: { - marginTop: Spacing.xl, - width: '100%' - } -}); diff --git a/packages/scaffold/src/partials/w3m-otp-code/index.tsx b/packages/scaffold/src/partials/w3m-otp-code/index.tsx deleted file mode 100644 index bc88503b..00000000 --- a/packages/scaffold/src/partials/w3m-otp-code/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Platform } from 'react-native'; -import { FlexView, Link, LoadingSpinner, Otp, Spacing, Text } from '@reown/appkit-ui-react-native'; - -import { useKeyboard } from '../../hooks/useKeyboard'; -import styles from './styles'; - -interface Props { - onCodeChange?: (code: string) => void; - onSubmit: (code: string) => void; - onRetry: () => void; - loading?: boolean; - error?: string; - email?: string; - timeLeft?: number; - codeExpiry?: number; - retryLabel?: string; - retryDisabledButtonLabel?: string; - retryButtonLabel?: string; -} - -export function OtpCodeView({ - onCodeChange, - onSubmit, - onRetry, - error, - loading, - email, - timeLeft = 0, - codeExpiry = 20, - retryLabel = "Didn't receive it?", - retryDisabledButtonLabel = 'Resend', - retryButtonLabel = 'Resend code' -}: Props) { - const { keyboardShown, keyboardHeight } = useKeyboard(); - const paddingBottom = Platform.select({ - android: keyboardShown ? keyboardHeight + Spacing.l : Spacing.l, - default: Spacing.l - }); - - const handleCodeChange = (code: string) => { - onCodeChange?.(code); - - if (code.length === 6) { - onSubmit?.(code); - } - }; - - return ( - - - Enter the code we sent to{' '} - - {email ?? 'your email'} - - {`The code expires in ${codeExpiry} minutes`} - - - {loading ? ( - - ) : ( - - )} - - {error && ( - - {error} - - )} - {!loading && ( - - - {retryLabel} - - 0 || loading}> - {timeLeft > 0 ? `${retryDisabledButtonLabel} in ${timeLeft}s` : retryButtonLabel} - - - )} - - ); -} diff --git a/packages/scaffold/src/partials/w3m-otp-code/styles.ts b/packages/scaffold/src/partials/w3m-otp-code/styles.ts deleted file mode 100644 index 07c9153c..00000000 --- a/packages/scaffold/src/partials/w3m-otp-code/styles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - expiryText: { - marginTop: Spacing.s, - marginBottom: Spacing.l - }, - otpContainer: { - height: 60 - }, - errorText: { - marginTop: Spacing['2xs'] - } -}); diff --git a/packages/scaffold/src/partials/w3m-placeholder/index.tsx b/packages/scaffold/src/partials/w3m-placeholder/index.tsx deleted file mode 100644 index 8fed2e11..00000000 --- a/packages/scaffold/src/partials/w3m-placeholder/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; -import { - IconBox, - Text, - FlexView, - Spacing, - type IconType, - Button, - type ColorType -} from '@reown/appkit-ui-react-native'; - -interface Props { - icon?: IconType; - iconColor?: ColorType; - title?: string; - description?: string; - style?: StyleProp; - actionIcon?: IconType; - actionPress?: () => void; - actionTitle?: string; -} - -export function Placeholder({ - icon, - iconColor = 'fg-175', - title, - description, - style, - actionPress, - actionTitle, - actionIcon -}: Props) { - return ( - - {icon && ( - - )} - {title && ( - - {title} - - )} - {description && ( - - {description} - - )} - {actionPress && ( - - )} - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - minHeight: 200 - }, - icon: { - marginBottom: Spacing.l - }, - title: { - marginBottom: Spacing['2xs'] - }, - button: { - marginTop: Spacing.m - } -}); diff --git a/packages/scaffold/src/partials/w3m-selector-modal/index.tsx b/packages/scaffold/src/partials/w3m-selector-modal/index.tsx deleted file mode 100644 index 37c8c94e..00000000 --- a/packages/scaffold/src/partials/w3m-selector-modal/index.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { useSnapshot } from 'valtio'; -import Modal from 'react-native-modal'; -import { FlatList, View } from 'react-native'; -import { - FlexView, - IconBox, - IconLink, - Image, - SearchBar, - Separator, - Spacing, - Text, - useTheme -} from '@reown/appkit-ui-react-native'; -import styles from './styles'; -import { AssetUtil, NetworkController } from '@reown/appkit-core-react-native'; - -interface SelectorModalProps { - title?: string; - visible: boolean; - onClose: () => void; - items: any[]; - selectedItem?: any; - renderItem: ({ item }: { item: any }) => React.ReactElement; - keyExtractor: (item: any, index: number) => string; - onSearch: (value: string) => void; - itemHeight?: number; - showNetwork?: boolean; - searchPlaceholder?: string; -} - -const SEPARATOR_HEIGHT = Spacing.s; - -export function SelectorModal({ - title, - visible, - onClose, - items, - selectedItem, - renderItem, - onSearch, - searchPlaceholder, - keyExtractor, - itemHeight, - showNetwork -}: SelectorModalProps) { - const Theme = useTheme(); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - - const renderSeparator = () => { - return ; - }; - - return ( - - - - - {!!title && {title}} - {showNetwork ? ( - networkImage ? ( - - - - ) : ( - - ) - ) : ( - - )} - - - {selectedItem && ( - - {renderItem({ item: selectedItem })} - - - )} - ({ - length: itemHeight + SEPARATOR_HEIGHT, - offset: (itemHeight + SEPARATOR_HEIGHT) * index, - index - }) - : undefined - } - /> - - - ); -} diff --git a/packages/scaffold/src/partials/w3m-selector-modal/styles.ts b/packages/scaffold/src/partials/w3m-selector-modal/styles.ts deleted file mode 100644 index 3520474c..00000000 --- a/packages/scaffold/src/partials/w3m-selector-modal/styles.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - modal: { - margin: 0, - justifyContent: 'flex-end' - }, - header: { - marginBottom: Spacing.s, - paddingHorizontal: Spacing.m - }, - container: { - height: '80%', - borderTopLeftRadius: BorderRadius.l, - borderTopRightRadius: BorderRadius.l, - paddingTop: Spacing.m - }, - selectedContainer: { - paddingHorizontal: Spacing.m - }, - listContent: { - paddingTop: Spacing.s, - paddingHorizontal: Spacing.m - }, - iconPlaceholder: { - height: 32, - width: 32 - }, - networkImage: { - height: 20, - width: 20, - borderRadius: BorderRadius.full - }, - searchBar: { - marginBottom: Spacing.s, - marginHorizontal: Spacing.s - }, - separator: { - marginTop: Spacing.m - } -}); diff --git a/packages/scaffold/src/partials/w3m-send-input-address/index.tsx b/packages/scaffold/src/partials/w3m-send-input-address/index.tsx deleted file mode 100644 index 2cec2af3..00000000 --- a/packages/scaffold/src/partials/w3m-send-input-address/index.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useState } from 'react'; -import { TextInput } from 'react-native'; -import { FlexView, useTheme } from '@reown/appkit-ui-react-native'; -import { ConnectionController, SendController } from '@reown/appkit-core-react-native'; - -import { useDebounceCallback } from '../../hooks/useDebounceCallback'; -import styles from './styles'; - -export interface SendInputAddressProps { - value?: string; -} - -export function SendInputAddress({ value }: SendInputAddressProps) { - const Theme = useTheme(); - const [inputValue, setInputValue] = useState(value); - - const onSearch = async (search: string) => { - SendController.setLoading(true); - const address = await ConnectionController.getEnsAddress(search); - SendController.setLoading(false); - - if (address) { - SendController.setReceiverProfileName(search); - SendController.setReceiverAddress(address); - const avatar = await ConnectionController.getEnsAvatar(search); - SendController.setReceiverProfileImageUrl(avatar || undefined); - } else { - SendController.setReceiverAddress(search); - SendController.setReceiverProfileName(undefined); - SendController.setReceiverProfileImageUrl(undefined); - } - }; - - const { debouncedCallback: onDebounceSearch } = useDebounceCallback({ - callback: onSearch, - delay: 800 - }); - - const onInputChange = (address: string) => { - setInputValue(address); - SendController.setReceiverAddress(address); - onDebounceSearch(address); - }; - - return ( - - - - ); -} diff --git a/packages/scaffold/src/partials/w3m-send-input-address/styles.ts b/packages/scaffold/src/partials/w3m-send-input-address/styles.ts deleted file mode 100644 index 58ff557a..00000000 --- a/packages/scaffold/src/partials/w3m-send-input-address/styles.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { BorderRadius } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - container: { - height: 100, - width: '100%', - borderRadius: BorderRadius.s, - borderWidth: StyleSheet.hairlineWidth - }, - input: { - fontSize: 18 - } -}); diff --git a/packages/scaffold/src/partials/w3m-send-input-token/index.tsx b/packages/scaffold/src/partials/w3m-send-input-token/index.tsx deleted file mode 100644 index 8c5eb250..00000000 --- a/packages/scaffold/src/partials/w3m-send-input-token/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { useRef, useState } from 'react'; -import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; -import { FlexView, Link, Text, useTheme, TokenButton } from '@reown/appkit-ui-react-native'; -import { NumberUtil, type Balance } from '@reown/appkit-common-react-native'; -import { ConstantsUtil, SendController } from '@reown/appkit-core-react-native'; - -import { getMaxAmount, getSendValue } from './utils'; -import styles from './styles'; - -export interface SendInputTokenProps { - token?: Balance; - sendTokenAmount?: number; - gasPrice?: number; - style?: StyleProp; - onTokenPress?: () => void; -} - -export function SendInputToken({ - token, - sendTokenAmount, - gasPrice, - style, - onTokenPress -}: SendInputTokenProps) { - const Theme = useTheme(); - const valueInputRef = useRef(null); - const [inputValue, setInputValue] = useState(sendTokenAmount?.toString()); - const sendValue = getSendValue(token, sendTokenAmount); - const maxAmount = getMaxAmount(token); - const maxError = token && sendTokenAmount && sendTokenAmount > Number(token.quantity.numeric); - - const onInputChange = (value: string) => { - const formattedValue = value.replace(/,/g, '.'); - - if (Number(formattedValue) >= 0 || formattedValue === '') { - setInputValue(formattedValue); - SendController.setTokenAmount(Number(formattedValue)); - } - }; - - const onMaxPress = () => { - if (token && gasPrice) { - const isNetworkToken = - token.address === undefined || - Object.values(ConstantsUtil.NATIVE_TOKEN_ADDRESS).some( - nativeAddress => token?.address === nativeAddress - ); - - const numericGas = NumberUtil.bigNumber(gasPrice).shiftedBy(-token.quantity.decimals); - - const maxValue = isNetworkToken - ? NumberUtil.bigNumber(token.quantity.numeric).minus(numericGas) - : NumberUtil.bigNumber(token.quantity.numeric); - - SendController.setTokenAmount(Number(maxValue.toFixed(20))); - setInputValue(maxValue.toFixed(20)); - valueInputRef.current?.blur(); - } - }; - - return ( - - - - - - {token && ( - - - {sendValue ?? ''} - - - - {maxAmount ?? ''} - - Max - - - )} - - ); -} diff --git a/packages/scaffold/src/partials/w3m-send-input-token/styles.ts b/packages/scaffold/src/partials/w3m-send-input-token/styles.ts deleted file mode 100644 index e35dc185..00000000 --- a/packages/scaffold/src/partials/w3m-send-input-token/styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - height: 100, - width: '100%', - borderRadius: BorderRadius.s, - borderWidth: StyleSheet.hairlineWidth - }, - input: { - fontSize: 32, - flex: 1, - marginRight: Spacing.xs - }, - sendValue: { - flex: 1, - marginRight: Spacing.xs - } -}); diff --git a/packages/scaffold/src/partials/w3m-send-input-token/utils.ts b/packages/scaffold/src/partials/w3m-send-input-token/utils.ts deleted file mode 100644 index 38085ed3..00000000 --- a/packages/scaffold/src/partials/w3m-send-input-token/utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type Balance, NumberUtil } from '@reown/appkit-common-react-native'; -import { UiUtil } from '@reown/appkit-ui-react-native'; - -export function getSendValue(token?: Balance, sendTokenAmount?: number) { - if (token && sendTokenAmount) { - const price = token.price; - const totalValue = price * sendTokenAmount; - - return totalValue ? `$${UiUtil.formatNumberToLocalString(totalValue, 2)}` : 'Incorrect value'; - } - - return null; -} - -export function getMaxAmount(token?: Balance) { - if (token) { - return NumberUtil.roundNumber(Number(token.quantity.numeric), 6, 5); - } - - return null; -} diff --git a/packages/scaffold/src/partials/w3m-snackbar/index.tsx b/packages/scaffold/src/partials/w3m-snackbar/index.tsx deleted file mode 100644 index ccf004a7..00000000 --- a/packages/scaffold/src/partials/w3m-snackbar/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useEffect, useMemo } from 'react'; -import { Animated } from 'react-native'; -import { SnackController, type SnackControllerState } from '@reown/appkit-core-react-native'; -import { Snackbar as SnackbarComponent } from '@reown/appkit-ui-react-native'; - -import styles from './styles'; - -const getIcon = (variant: SnackControllerState['variant']) => { - if (variant === 'loading') return 'loading'; - - return variant === 'success' ? 'checkmark' : 'close'; -}; - -export function Snackbar() { - const { open, message, variant, long } = useSnapshot(SnackController.state); - const componentOpacity = useMemo(() => new Animated.Value(0), []); - - useEffect(() => { - if (open) { - Animated.timing(componentOpacity, { - toValue: 1, - duration: 150, - useNativeDriver: true - }).start(); - setTimeout( - () => { - Animated.timing(componentOpacity, { - toValue: 0, - duration: 300, - useNativeDriver: true - }).start(() => { - SnackController.hide(); - }); - }, - long ? 15000 : 2200 - ); - } - }, [open, long, componentOpacity]); - - return ( - - ); -} diff --git a/packages/scaffold/src/partials/w3m-snackbar/styles.ts b/packages/scaffold/src/partials/w3m-snackbar/styles.ts deleted file mode 100644 index c5765d09..00000000 --- a/packages/scaffold/src/partials/w3m-snackbar/styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - marginTop: Spacing.s, - justifyContent: 'center', - alignItems: 'center', - position: 'absolute', - alignSelf: 'center' - } -}); diff --git a/packages/scaffold/src/partials/w3m-swap-details/index.tsx b/packages/scaffold/src/partials/w3m-swap-details/index.tsx deleted file mode 100644 index dd97e87a..00000000 --- a/packages/scaffold/src/partials/w3m-swap-details/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useState } from 'react'; -import { ConstantsUtil, NetworkController, SwapController } from '@reown/appkit-core-react-native'; -import { - FlexView, - Text, - UiUtil, - Toggle, - useTheme, - Pressable, - Icon -} from '@reown/appkit-ui-react-native'; -import { NumberUtil } from '@reown/appkit-common-react-native'; - -import { InformationModal } from '../w3m-information-modal'; -import styles from './styles'; -import { getModalData } from './utils'; - -interface SwapDetailsProps { - initialOpen?: boolean; - canClose?: boolean; -} - -// -- Constants ----------------------------------------- // -const slippageRate = ConstantsUtil.CONVERT_SLIPPAGE_TOLERANCE; - -export function SwapDetails({ initialOpen, canClose }: SwapDetailsProps) { - const Theme = useTheme(); - const { - maxSlippage = 0, - sourceToken, - toToken, - gasPriceInUSD = 0, - priceImpact, - toTokenAmount - } = useSnapshot(SwapController.state); - - const [modalData, setModalData] = useState<{ title: string; description: string } | undefined>(); - - const toTokenSwappedAmount = - SwapController.state.sourceTokenPriceInUSD && SwapController.state.toTokenPriceInUSD - ? (1 / SwapController.state.toTokenPriceInUSD) * SwapController.state.sourceTokenPriceInUSD - : 0; - - const renderTitle = () => ( - - - 1 {SwapController.state.sourceToken?.symbol} = {''} - {UiUtil.formatNumberToLocalString(toTokenSwappedAmount, 3)}{' '} - {SwapController.state.toToken?.symbol} - - - ~$ - {UiUtil.formatNumberToLocalString(SwapController.state.sourceTokenPriceInUSD)} - - - ); - - const minimumReceive = NumberUtil.parseLocalStringToNumber(toTokenAmount) - maxSlippage; - const providerFee = SwapController.getProviderFeePrice(); - - const onPriceImpactPress = () => { - setModalData(getModalData('priceImpact')); - }; - - const onSlippagePress = () => { - const minimumString = UiUtil.formatNumberToLocalString( - minimumReceive, - minimumReceive < 1 ? 8 : 2 - ); - setModalData( - getModalData('slippage', { - minimumReceive: minimumString, - toTokenSymbol: SwapController.state.toToken?.symbol - }) - ); - }; - - const onNetworkCostPress = () => { - setModalData( - getModalData('networkCost', { - networkSymbol: SwapController.state.networkTokenSymbol, - networkName: NetworkController.state.caipNetwork?.name - }) - ); - }; - - return ( - <> - - - - - Network cost - - - - - - - ${UiUtil.formatNumberToLocalString(gasPriceInUSD, gasPriceInUSD < 1 ? 8 : 2)} - - - {!!priceImpact && ( - - - - Price impact - - - - - - - ~{UiUtil.formatNumberToLocalString(priceImpact, 3)}% - - - )} - {maxSlippage !== undefined && maxSlippage > 0 && !!sourceToken?.symbol && ( - - - - Max. slippage - - - - - - - {UiUtil.formatNumberToLocalString(maxSlippage, 6)} {toToken?.symbol}{' '} - - {slippageRate}% - - - - )} - - - Included provider fee - - - ${UiUtil.formatNumberToLocalString(providerFee, providerFee < 1 ? 8 : 2)} - - - - setModalData(undefined)} - /> - - ); -} diff --git a/packages/scaffold/src/partials/w3m-swap-details/styles.ts b/packages/scaffold/src/partials/w3m-swap-details/styles.ts deleted file mode 100644 index 92fe6b17..00000000 --- a/packages/scaffold/src/partials/w3m-swap-details/styles.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - container: { - width: '100%', - borderRadius: 16 - }, - titlePrice: { - marginLeft: Spacing['3xs'] - }, - detailTitle: { - marginRight: Spacing['3xs'] - }, - item: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: Spacing.s, - borderRadius: BorderRadius.xxs, - marginTop: Spacing['2xs'] - }, - infoIcon: { - borderRadius: BorderRadius.full - } -}); diff --git a/packages/scaffold/src/partials/w3m-swap-details/utils.ts b/packages/scaffold/src/partials/w3m-swap-details/utils.ts deleted file mode 100644 index fc834532..00000000 --- a/packages/scaffold/src/partials/w3m-swap-details/utils.ts +++ /dev/null @@ -1,33 +0,0 @@ -export interface ModalData { - detail: ModalDetail; - opts?: ModalDataOpts; -} - -export type ModalDetail = 'slippage' | 'networkCost' | 'priceImpact'; - -export interface ModalDataOpts { - networkSymbol?: string; - networkName?: string; - minimumReceive?: string; - toTokenSymbol?: string; -} - -export const getModalData = (detail: ModalDetail, opts?: ModalDataOpts) => { - switch (detail) { - case 'slippage': - return { - title: 'Max. slippage', - description: `Max slippage sets the minimum amount you must receive for the transaction to proceed. The transaction will be reversed if you receive less than ${opts?.minimumReceive} ${opts?.toTokenSymbol} due to price changes` - }; - case 'networkCost': - return { - title: 'Network cost', - description: `Network cost is paid in ${opts?.networkSymbol} on the ${opts?.networkName} network in order to execute the transaction` - }; - case 'priceImpact': - return { - title: 'Price impact', - description: 'Price impact reflects the change in market price due to your trade' - }; - } -}; diff --git a/packages/scaffold/src/partials/w3m-swap-input/index.tsx b/packages/scaffold/src/partials/w3m-swap-input/index.tsx deleted file mode 100644 index 16db2698..00000000 --- a/packages/scaffold/src/partials/w3m-swap-input/index.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { useRef } from 'react'; -import type BigNumber from 'bignumber.js'; -import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; -import { - FlexView, - useTheme, - TokenButton, - Shimmer, - Text, - UiUtil, - Link -} from '@reown/appkit-ui-react-native'; -import { type SwapTokenWithBalance } from '@reown/appkit-core-react-native'; - -import styles from './styles'; -import { NumberUtil } from '@reown/appkit-common-react-native'; - -export interface SwapInputProps { - token?: SwapTokenWithBalance; - value?: string; - gasPrice?: number; - style?: StyleProp; - loading?: boolean; - onTokenPress?: () => void; - onMaxPress?: () => void; - onChange?: (value: string) => void; - balance?: BigNumber; - marketValue?: number; - editable?: boolean; - autoFocus?: boolean; -} - -const MINIMUM_USD_VALUE_TO_CONVERT = 0.00005; - -export function SwapInput({ - token, - value, - style, - loading, - onTokenPress, - onMaxPress, - onChange, - marketValue, - editable, - autoFocus -}: SwapInputProps) { - const Theme = useTheme(); - const valueInputRef = useRef(null); - const isMarketValueGreaterThanZero = - !!marketValue && NumberUtil.bigNumber(marketValue).isGreaterThan('0'); - const maxAmount = UiUtil.formatNumberToLocalString(token?.quantity.numeric, 3); - const maxError = Number(value) > Number(token?.quantity.numeric); - const showMax = - onMaxPress && - !!token?.quantity.numeric && - NumberUtil.multiply(token?.quantity.numeric, token?.price).isGreaterThan( - MINIMUM_USD_VALUE_TO_CONVERT - ); - - const handleInputChange = (_value: string) => { - const formattedValue = _value.replace(/,/g, '.'); - - if (Number(formattedValue) >= 0 || formattedValue === '') { - onChange?.(formattedValue); - } - }; - - const handleMaxPress = () => { - if (valueInputRef.current) { - valueInputRef.current.blur(); - } - - onMaxPress?.(); - }; - - return ( - - {loading ? ( - - - - - ) : ( - <> - - - - - {(showMax || isMarketValueGreaterThanZero) && ( - - - {isMarketValueGreaterThanZero - ? `~$${UiUtil.formatNumberToLocalString(marketValue, 2)}` - : ''} - - {showMax && ( - - - {showMax ? maxAmount : ''} - - Max - - )} - - )} - - )} - - ); -} diff --git a/packages/scaffold/src/partials/w3m-swap-input/styles.ts b/packages/scaffold/src/partials/w3m-swap-input/styles.ts deleted file mode 100644 index e35dc185..00000000 --- a/packages/scaffold/src/partials/w3m-swap-input/styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - height: 100, - width: '100%', - borderRadius: BorderRadius.s, - borderWidth: StyleSheet.hairlineWidth - }, - input: { - fontSize: 32, - flex: 1, - marginRight: Spacing.xs - }, - sendValue: { - flex: 1, - marginRight: Spacing.xs - } -}); diff --git a/packages/scaffold/src/utils/UiUtil.ts b/packages/scaffold/src/utils/UiUtil.ts deleted file mode 100644 index 7288f410..00000000 --- a/packages/scaffold/src/utils/UiUtil.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - AssetUtil, - ConnectionController, - StorageUtil, - type WcWallet -} from '@reown/appkit-core-react-native'; -import { - LayoutAnimation, - type LayoutAnimationProperty, - type LayoutAnimationType -} from 'react-native'; - -export const UiUtil = { - TOTAL_VISIBLE_WALLETS: 4, - - createViewTransition: () => { - LayoutAnimation.configureNext(LayoutAnimation.create(200, 'easeInEaseOut', 'opacity')); - }, - - animateChange: ( - type: LayoutAnimationType = 'linear', - creationProp: LayoutAnimationProperty = 'scaleX' - ) => { - LayoutAnimation.configureNext(LayoutAnimation.create(150, type, creationProp)); - }, - - storeConnectedWallet: async ( - wcLinking: { name: string; href: string }, - pressedWallet?: WcWallet - ) => { - StorageUtil.setWalletConnectDeepLink(wcLinking); - - if (pressedWallet) { - const recentWallets = await StorageUtil.addRecentWallet(pressedWallet); - if (recentWallets) { - ConnectionController.setRecentWallets(recentWallets); - } - const url = AssetUtil.getWalletImage(pressedWallet); - ConnectionController.setConnectedWalletImageUrl(url); - } - } -}; diff --git a/packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx b/packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx deleted file mode 100644 index 43e3cd38..00000000 --- a/packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; -import { UpgradeWalletButton } from './upgrade-wallet-button'; -import { ListItem, ListSocial, Spacing, Text } from '@reown/appkit-ui-react-native'; -import type { SocialProvider } from '@reown/appkit-common-react-native'; - -export interface AuthButtonsProps { - onUpgradePress: () => void; - onPress: () => void; - socialProvider?: SocialProvider; - text: string; - style?: StyleProp; -} - -export function AuthButtons({ - onUpgradePress, - onPress, - socialProvider, - text, - style -}: AuthButtonsProps) { - return ( - <> - - {socialProvider ? ( - - - {text} - - - ) : ( - - - {text} - - - )} - - ); -} - -const styles = StyleSheet.create({ - actionButton: { - marginBottom: Spacing.xs - }, - upgradeButton: { - marginBottom: Spacing.s - }, - socialContainer: { - justifyContent: 'flex-start', - width: '100%' - }, - socialText: { - flex: 1, - marginLeft: Spacing.s - } -}); diff --git a/packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx b/packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx deleted file mode 100644 index e02c5a6a..00000000 --- a/packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Animated, Pressable, StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; -import { - BorderRadius, - FlexView, - Icon, - IconBox, - Spacing, - Text, - useTheme, - useAnimatedValue -} from '@reown/appkit-ui-react-native'; - -const AnimatedPressable = Animated.createAnimatedComponent(Pressable); - -export interface Props { - onPress: () => void; - style?: StyleProp; -} - -export function UpgradeWalletButton({ style, onPress }: Props) { - const Theme = useTheme(); - const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( - Theme['accent-glass-010'], - Theme['accent-glass-020'] - ); - - return ( - - - - - Upgrade your wallet - - - Transition to a self-custodial wallet - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - height: 75, - borderRadius: BorderRadius.s, - backgroundColor: 'red', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: Spacing.s - }, - textContainer: { - marginHorizontal: Spacing.m - }, - upgradeText: { - marginBottom: Spacing['3xs'] - }, - chevron: { - marginRight: Spacing['2xs'] - } -}); diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx deleted file mode 100644 index c352bf02..00000000 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ /dev/null @@ -1,333 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useState } from 'react'; -import { Linking, ScrollView } from 'react-native'; -import { - AccountController, - ApiController, - AssetUtil, - ConnectionController, - ConnectorController, - CoreHelperUtil, - EventsController, - ModalController, - NetworkController, - OptionsController, - RouterController, - SnackController, - type AppKitFrameProvider, - ConstantsUtil, - SwapController, - OnRampController -} from '@reown/appkit-core-react-native'; -import { - Avatar, - Button, - FlexView, - IconLink, - Text, - UiUtil, - Spacing, - ListItem -} from '@reown/appkit-ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; - -import styles from './styles'; -import { AuthButtons } from './components/auth-buttons'; - -export function AccountDefaultView() { - const { - address, - profileName, - profileImage, - balance, - balanceSymbol, - addressExplorerUrl, - preferredAccountType - } = useSnapshot(AccountController.state); - const { loading } = useSnapshot(ModalController.state); - const [disconnecting, setDisconnecting] = useState(false); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { connectedConnector } = useSnapshot(ConnectorController.state); - const { connectedSocialProvider } = useSnapshot(ConnectionController.state); - const { features, isOnRampEnabled } = useSnapshot(OptionsController.state); - const { history } = useSnapshot(RouterController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const showCopy = OptionsController.isClipboardAvailable(); - const isAuth = connectedConnector === 'AUTH'; - const showBalance = balance && !isAuth; - const showExplorer = addressExplorerUrl && !isAuth; - const showBack = history.length > 1; - const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); - const { padding } = useCustomDimensions(); - - async function onDisconnect() { - setDisconnecting(true); - // await ConnectionUtil.disconnect(); - setDisconnecting(false); - } - - const onSwitchAccountType = async () => { - try { - if (isAuth) { - ModalController.setLoading(true); - const accountType = - AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; - const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; - await provider?.setPreferredAccount(accountType); - EventsController.sendEvent({ - type: 'track', - event: 'SET_PREFERRED_ACCOUNT_TYPE', - properties: { - accountType, - network: NetworkController.state.caipNetwork?.id || '' - } - }); - } - } catch (error) { - ModalController.setLoading(false); - SnackController.showError('Error switching account type'); - } - }; - - const getUserEmail = () => { - const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; - if (!provider) return ''; - - return provider.getEmail(); - }; - - const getUsername = () => { - const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; - if (!provider) return ''; - - return provider.getUsername(); - }; - - const onExplorerPress = () => { - if (AccountController.state.addressExplorerUrl) { - Linking.openURL(AccountController.state.addressExplorerUrl); - } - }; - - const onCopyAddress = () => { - if (AccountController.state.profileName) { - OptionsController.copyToClipboard(AccountController.state.profileName); - SnackController.showSuccess('Name copied'); - } else if (AccountController.state.address) { - OptionsController.copyToClipboard( - AccountController.state.profileName ?? AccountController.state.address - ); - SnackController.showSuccess('Address copied'); - } - }; - - const onSwapPress = () => { - if ( - NetworkController.state.caipNetwork?.id && - !ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(`${NetworkController.state.caipNetwork.id}`) - ) { - RouterController.push('UnsupportedChain'); - } else { - SwapController.resetState(); - EventsController.sendEvent({ - type: 'track', - event: 'OPEN_SWAP', - properties: { - network: NetworkController.state.caipNetwork?.id || '', - isSmartAccount: false - } - }); - RouterController.push('Swap'); - } - }; - - const onBuyPress = () => { - EventsController.sendEvent({ - type: 'track', - event: 'SELECT_BUY_CRYPTO' - }); - - OnRampController.resetState(); - RouterController.push('OnRamp'); - }; - - const onActivityPress = () => { - RouterController.push('Transactions'); - }; - - const onNetworkPress = () => { - RouterController.push('Networks'); - - EventsController.sendEvent({ - type: 'track', - event: 'CLICK_NETWORKS' - }); - }; - - const onUpgradePress = () => { - EventsController.sendEvent({ type: 'track', event: 'EMAIL_UPGRADE_FROM_MODAL' }); - RouterController.push('UpgradeEmailWallet'); - }; - - const onEmailPress = () => { - if (ConnectionController.state.connectedSocialProvider) return; - RouterController.push('UpdateEmailWallet', { email: getUserEmail() }); - }; - - return ( - <> - {showBack && ( - - )} - - - - - - - {profileName - ? UiUtil.getTruncateString({ - string: profileName, - charsStart: 20, - charsEnd: 0, - truncate: 'end' - }) - : UiUtil.getTruncateString({ - string: address ?? '', - charsStart: 4, - charsEnd: 6, - truncate: 'middle' - })} - - {showCopy && ( - - )} - - {showBalance && ( - - {CoreHelperUtil.formatBalance(balance, balanceSymbol)} - - )} - {showExplorer && ( - - )} - - {isAuth && ( - - )} - - - {caipNetwork?.name} - - - {!isAuth && isOnRampEnabled && ( - - Buy crypto - - )} - {!isAuth && features?.swaps && ( - - Swap - - )} - {!isAuth && ( - - Activity - - )} - {showSwitchAccountType && ( - - {`Switch to your ${ - preferredAccountType === 'eoa' ? 'smart account' : 'EOA' - }`} - - )} - - Disconnect - - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-account-default-view/styles.ts b/packages/scaffold/src/views/w3m-account-default-view/styles.ts deleted file mode 100644 index 1d0389f0..00000000 --- a/packages/scaffold/src/views/w3m-account-default-view/styles.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - backIcon: { - alignSelf: 'flex-end', - position: 'absolute', - zIndex: 1, - top: Spacing.l, - left: Spacing.xl - }, - closeIcon: { - alignSelf: 'flex-end', - position: 'absolute', - zIndex: 1, - top: Spacing.l, - right: Spacing.xl - }, - copyButton: { - marginLeft: Spacing['4xs'] - }, - actionButton: { - marginBottom: Spacing.xs - }, - upgradeButton: { - marginBottom: Spacing.s - } -}); diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx deleted file mode 100644 index 14e19d85..00000000 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useEffect } from 'react'; -import { ScrollView } from 'react-native'; -import { - AccountPill, - FlexView, - Icon, - IconLink, - NetworkButton, - useTheme, - Promo -} from '@reown/appkit-ui-react-native'; -import { - AccountController, - ApiController, - AssetUtil, - ModalController, - NetworkController, - RouterController, - SendController -} from '@reown/appkit-core-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; -import styles from './styles'; - -export function AccountView() { - const Theme = useTheme(); - const { padding } = useCustomDimensions(); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { address, profileName, profileImage, preferredAccountType } = useSnapshot( - AccountController.state - ); - const showActivate = - preferredAccountType === 'eoa' && NetworkController.checkIfSmartAccountEnabled(); - - const onProfilePress = () => { - RouterController.push('AccountDefault'); - }; - - const onNetworkPress = () => { - RouterController.push('Networks'); - }; - - const onActivatePress = () => { - RouterController.push('UpgradeToSmartAccount'); - }; - - useEffect(() => { - AccountController.fetchTokenBalance(); - SendController.resetSend(); - }, []); - - useEffect(() => { - AccountController.fetchTokenBalance(); - - const balanceInterval = setInterval(() => { - AccountController.fetchTokenBalance(); - }, 10000); - - return () => { - clearInterval(balanceInterval); - }; - }, []); - - return ( - - - - - - - {showActivate && ( - - )} - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-account-view/styles.ts b/packages/scaffold/src/views/w3m-account-view/styles.ts deleted file mode 100644 index 0a256790..00000000 --- a/packages/scaffold/src/views/w3m-account-view/styles.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { Platform, StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - contentContainer: { - paddingBottom: Platform.select({ ios: Spacing.s }) - }, - networkIcon: { - alignSelf: 'flex-start', - position: 'absolute', - zIndex: 1, - top: Spacing.l, - left: Spacing.l - }, - closeIcon: { - alignSelf: 'flex-end', - position: 'absolute', - zIndex: 1, - top: Spacing.l, - right: Spacing.xl - }, - accountPill: { - alignSelf: 'center', - marginBottom: Spacing.s, - marginHorizontal: Spacing.s - }, - promoPill: { - marginTop: Spacing.xs, - marginBottom: Spacing['2xl'], - alignSelf: 'center' - } -}); diff --git a/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx b/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx deleted file mode 100644 index d59d3088..00000000 --- a/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { useState } from 'react'; -import { - ConnectionController, - ConnectorController, - EventsController, - RouterController, - type WcWallet -} from '@reown/appkit-core-react-native'; -import { FlexView, IconLink, SearchBar, Spacing, useTheme } from '@reown/appkit-ui-react-native'; - -import styles from './styles'; -import { useDebounceCallback } from '../../hooks/useDebounceCallback'; -import { AllWalletsList } from '../../partials/w3m-all-wallets-list'; -import { AllWalletsSearch } from '../../partials/w3m-all-wallets-search'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; - -export function AllWalletsView() { - const Theme = useTheme(); - const [searchQuery, setSearchQuery] = useState(''); - const { maxWidth } = useCustomDimensions(); - const numColumns = 4; - const usableWidth = maxWidth - Spacing.xs * 2; - const itemWidth = Math.abs(Math.trunc(usableWidth / numColumns)); - - const { debouncedCallback: onInputChange } = useDebounceCallback({ callback: setSearchQuery }); - - const onWalletPress = (wallet: WcWallet) => { - const connector = ConnectorController.state.connectors.find(c => c.explorerId === wallet.id); - if (connector) { - RouterController.push('ConnectingExternal', { connector, wallet }); - } else { - RouterController.push('ConnectingWalletConnect', { wallet }); - } - - EventsController.sendEvent({ - type: 'track', - event: 'SELECT_WALLET', - properties: { name: wallet.name ?? 'Unknown', platform: 'mobile', explorer_id: wallet.id } - }); - }; - - const onQrCodePress = () => { - ConnectionController.removePressedWallet(); - ConnectionController.removeWcLinking(); - RouterController.push('ConnectingWalletConnect'); - - EventsController.sendEvent({ - type: 'track', - event: 'SELECT_WALLET', - properties: { name: 'WalletConnect', platform: 'qrcode' } - }); - }; - - const headerTemplate = () => { - return ( - - - - - ); - }; - - const listTemplate = () => { - if (searchQuery) { - return ( - - ); - } - - return ( - - ); - }; - - return ( - <> - {headerTemplate()} - {listTemplate()} - - ); -} diff --git a/packages/scaffold/src/views/w3m-all-wallets-view/styles.ts b/packages/scaffold/src/views/w3m-all-wallets-view/styles.ts deleted file mode 100644 index fe5c1701..00000000 --- a/packages/scaffold/src/views/w3m-all-wallets-view/styles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Platform, StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - header: { - zIndex: 1, - alignSelf: 'center', - ...Platform.select({ - ios: { - shadowOpacity: 1, - shadowOffset: { width: 0, height: 6 } - } - }) - }, - icon: { - marginLeft: 8, - borderWidth: StyleSheet.hairlineWidth - }, - searchBar: { - flex: 1 - } -}); diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx b/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx deleted file mode 100644 index 228bc143..00000000 --- a/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useEffect } from 'react'; -import { useSnapshot } from 'valtio'; -import { ScrollView } from 'react-native'; -import { StringUtil, type SocialProvider } from '@reown/appkit-common-react-native'; -import { - ConnectionController, - EventsController, - OptionsController, - RouterController, - WebviewController -} from '@reown/appkit-core-react-native'; -import { FlexView, ListSocial, Text } from '@reown/appkit-ui-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function ConnectSocialsView() { - const { features } = useSnapshot(OptionsController.state); - const { padding } = useCustomDimensions(); - const socialProviders = (features?.socials ?? []) as SocialProvider[]; - - const onItemPress = (provider: SocialProvider) => { - ConnectionController.setSelectedSocialProvider(provider); - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_STARTED', - properties: { provider } - }); - if (provider === 'farcaster') { - RouterController.push('ConnectingFarcaster'); - } else { - RouterController.push('ConnectingSocial'); - } - }; - - useEffect(() => { - WebviewController.setConnecting(false); - }, []); - - return ( - - - {socialProviders.map(provider => ( - onItemPress(provider)} - style={styles.item} - > - - {StringUtil.capitalize(provider)} - - - ))} - - - ); -} diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts b/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts deleted file mode 100644 index be837b83..00000000 --- a/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - item: { - marginVertical: Spacing['3xs'], - justifyContent: 'flex-start' - }, - text: { - marginLeft: Spacing.s - } -}); diff --git a/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx deleted file mode 100644 index c9d0d076..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { type StyleProp, type ViewStyle } from 'react-native'; -import { useSnapshot } from 'valtio'; -import { - ApiController, - AssetUtil, - ConnectionController, - type ConnectionControllerState, - type WcWallet -} from '@reown/appkit-core-react-native'; -import { ListItemLoader, ListWallet } from '@reown/appkit-ui-react-native'; -import { UiUtil } from '../../../utils/UiUtil'; -import { filterOutRecentWallets } from '../utils'; - -interface Props { - itemStyle: StyleProp; - onWalletPress: (wallet: WcWallet) => void; - isWalletConnectEnabled: boolean; -} - -export function AllWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { - const { installed, featured, recommended, prefetchLoading } = useSnapshot(ApiController.state); - const { recentWallets } = useSnapshot(ConnectionController.state) as ConnectionControllerState; - const imageHeaders = ApiController._getApiHeaders(); - const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; - - const combinedWallets = [...installed, ...featured, ...recommended]; - - // Deduplicate by wallet ID - const uniqueWallets = Array.from( - new Map(combinedWallets.map(wallet => [wallet.id, wallet])).values() - ); - - const list = filterOutRecentWallets(recentWallets, uniqueWallets, RECENT_COUNT).slice( - 0, - UiUtil.TOTAL_VISIBLE_WALLETS - RECENT_COUNT - ); - - if (!isWalletConnectEnabled || !list?.length) { - return null; - } - - return prefetchLoading ? ( - <> - - - - ) : ( - list.map(wallet => ( - onWalletPress(wallet)} - style={itemStyle} - installed={!!installed.find(installedWallet => installedWallet.id === wallet.id)} - /> - )) - ); -} diff --git a/packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx b/packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx deleted file mode 100644 index 1e4c76ed..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/all-wallets-button.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { ApiController } from '@reown/appkit-core-react-native'; -import { ListWallet } from '@reown/appkit-ui-react-native'; -import type { StyleProp, ViewStyle } from 'react-native'; - -interface Props { - itemStyle: StyleProp; - onPress: () => void; - isWalletConnectEnabled: boolean; -} - -export function AllWalletsButton({ itemStyle, onPress, isWalletConnectEnabled }: Props) { - const { installed, count } = useSnapshot(ApiController.state); - - if (!isWalletConnectEnabled) { - return null; - } - - const total = installed.length + count; - const label = total > 10 ? `${Math.floor(total / 10) * 10}+` : total; - - return ( - - ); -} diff --git a/packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx b/packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx deleted file mode 100644 index 4ff60e7a..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/connect-email-input.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useState } from 'react'; -import { EmailInput, FlexView } from '@reown/appkit-ui-react-native'; -import { - ConnectorController, - CoreHelperUtil, - EventsController, - RouterController, - SnackController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; - -interface Props { - loading?: boolean; -} - -export function ConnectEmailInput({ loading }: Props) { - const { connectors } = useSnapshot(ConnectorController.state); - const [inputLoading, setInputLoading] = useState(false); - const [error, setError] = useState(''); - const [isValidEmail, setIsValidEmail] = useState(false); - const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; - - const onChangeText = (value: string) => { - setIsValidEmail(CoreHelperUtil.isValidEmail(value)); - setError(''); - }; - - const onEmailFocus = () => { - EventsController.sendEvent({ type: 'track', event: 'EMAIL_LOGIN_SELECTED' }); - }; - - const onEmailSubmit = async (email: string) => { - try { - if (email.length === 0) return; - - setInputLoading(true); - const response = await authProvider.connectEmail({ email }); - EventsController.sendEvent({ type: 'track', event: 'EMAIL_SUBMITTED' }); - if (response.action === 'VERIFY_DEVICE') { - RouterController.push('EmailVerifyDevice', { email }); - } else if (response.action === 'VERIFY_OTP') { - RouterController.push('EmailVerifyOtp', { email }); - } - } catch (e: any) { - const parsedError = CoreHelperUtil.parseError(e); - if (parsedError?.includes('valid email')) { - setError('Invalid email. Try again.'); - } else { - SnackController.showError(parsedError); - } - } finally { - setInputLoading(false); - } - }; - - return ( - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx deleted file mode 100644 index e1920354..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useSnapshot } from 'valtio'; -import type { StyleProp, ViewStyle } from 'react-native'; -import { - ConnectorController, - AssetUtil, - RouterController, - ApiController -} from '@reown/appkit-core-react-native'; - -import { ListWallet } from '@reown/appkit-ui-react-native'; -import type { ConnectorType } from '@reown/appkit-common-react-native'; - -interface Props { - itemStyle: StyleProp; - isWalletConnectEnabled: boolean; -} - -export function ConnectorList({ itemStyle, isWalletConnectEnabled }: Props) { - const { connectors } = useSnapshot(ConnectorController.state); - const excludeConnectors: ConnectorType[] = ['WALLET_CONNECT', 'AUTH']; - const imageHeaders = ApiController._getApiHeaders(); - - if (isWalletConnectEnabled) { - // use wallet from api list - excludeConnectors.push('COINBASE'); - } - - return connectors.map(connector => { - if (excludeConnectors.includes(connector.type)) { - return null; - } - - return ( - RouterController.push('ConnectingExternal', { connector })} - style={itemStyle} - installed={connector.installed} - /> - ); - }); -} diff --git a/packages/scaffold/src/views/w3m-connect-view/components/custom-wallet-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/custom-wallet-list.tsx deleted file mode 100644 index ca9a7f9c..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/custom-wallet-list.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useSnapshot } from 'valtio'; -import type { StyleProp, ViewStyle } from 'react-native'; -import { - OptionsController, - type CustomWallet, - type OptionsControllerState, - ApiController, - ConnectionController, - type ConnectionControllerState -} from '@reown/appkit-core-react-native'; -import { ListWallet } from '@reown/appkit-ui-react-native'; -import { filterOutRecentWallets } from '../utils'; - -interface Props { - itemStyle: StyleProp; - onWalletPress: (wallet: CustomWallet) => void; - isWalletConnectEnabled: boolean; -} - -export function CustomWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { - const { installed } = useSnapshot(ApiController.state); - const { recentWallets } = useSnapshot(ConnectionController.state) as ConnectionControllerState; - const { customWallets } = useSnapshot(OptionsController.state) as OptionsControllerState; - const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; - - if (!isWalletConnectEnabled || !customWallets?.length) { - return null; - } - - const list = filterOutRecentWallets(recentWallets, customWallets, RECENT_COUNT); - - return list.map(wallet => ( - onWalletPress(wallet)} - style={itemStyle} - /> - )); -} diff --git a/packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx deleted file mode 100644 index 81f011f7..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/recent-wallet-list.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { - ApiController, - AssetUtil, - type WcWallet, - ConnectionController -} from '@reown/appkit-core-react-native'; -import { ListWallet } from '@reown/appkit-ui-react-native'; -import type { StyleProp, ViewStyle } from 'react-native'; - -interface Props { - itemStyle: StyleProp; - onWalletPress: (wallet: WcWallet, installed: boolean) => void; - isWalletConnectEnabled: boolean; -} - -export function RecentWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { - const installed = ApiController.state.installed; - const { recentWallets } = useSnapshot(ConnectionController.state); - const imageHeaders = ApiController._getApiHeaders(); - const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; - - if (!isWalletConnectEnabled || !recentWallets?.length) { - return null; - } - - return recentWallets.slice(0, RECENT_COUNT).map(wallet => { - const isInstalled = !!installed.find(installedWallet => installedWallet.id === wallet.id); - - return ( - onWalletPress(wallet, isInstalled)} - tagLabel="Recent" - tagVariant="shade" - style={itemStyle} - installed={isInstalled} - /> - ); - }); -} diff --git a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx deleted file mode 100644 index ea2740db..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { FlexView, ListSocial, LogoSelect, Spacing, Text } from '@reown/appkit-ui-react-native'; -import { type SocialProvider, StringUtil } from '@reown/appkit-common-react-native'; -import { - ConnectionController, - EventsController, - RouterController, - WebviewController -} from '@reown/appkit-core-react-native'; - -export interface SocialLoginListProps { - options: readonly SocialProvider[]; - disabled?: boolean; -} - -const MAX_OPTIONS = 6; - -export function SocialLoginList({ options, disabled }: SocialLoginListProps) { - const showBigSocial = options?.length > 2 || options?.length === 1; - const showMoreButton = options?.length > MAX_OPTIONS; - const topSocial = showBigSocial ? options[0] : null; - let bottomSocials = showBigSocial ? options.slice(1) : options; - bottomSocials = showMoreButton ? bottomSocials.slice(0, MAX_OPTIONS - 2) : bottomSocials; - - const onItemPress = (provider: SocialProvider) => { - ConnectionController.setSelectedSocialProvider(provider); - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_STARTED', - properties: { provider } - }); - WebviewController.setConnecting(false); - - if (provider === 'farcaster') { - RouterController.push('ConnectingFarcaster'); - } else { - RouterController.push('ConnectingSocial'); - } - }; - - const onMorePress = () => { - RouterController.push('ConnectSocials'); - }; - - return ( - - {topSocial && ( - onItemPress(topSocial)}> - - {`Continue with ${StringUtil.capitalize(topSocial)}`} - - - )} - - {bottomSocials?.map((social: SocialProvider, index) => ( - onItemPress(social)} - style={[ - styles.socialItem, - index === 0 && styles.socialItemFirst, - !showMoreButton && index === bottomSocials.length - 1 && styles.socialItemLast - ]} - /> - ))} - {showMoreButton && ( - - )} - - - ); -} - -const styles = StyleSheet.create({ - topDescription: { - textAlign: 'center' - }, - socialItem: { - flex: 1, - marginHorizontal: Spacing['2xs'] - }, - socialItemFirst: { - marginLeft: 0 - }, - socialItemLast: { - marginRight: 0 - } -}); diff --git a/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx b/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx deleted file mode 100644 index 92fdcc2d..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { RouterController } from '@reown/appkit-core-react-native'; -import { Chip, FlexView, Link, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; -import { Linking, StyleSheet } from 'react-native'; - -export interface WalletGuideProps { - guide: 'explore' | 'get-started'; -} - -export function WalletGuide({ guide }: WalletGuideProps) { - const onExplorerPress = () => { - Linking.openURL('https://explorer.walletconnect.com'); - }; - - const onGetStartedPress = () => { - RouterController.push('Create'); - }; - - return guide === 'explore' ? ( - - - - Looking for a self-custody wallet? - - - - ) : ( - - Haven't got a wallet? - - Get started - - - ); -} - -const styles = StyleSheet.create({ - text: { - marginBottom: Spacing.xs - }, - socialSeparator: { - marginVertical: Spacing.l - } -}); diff --git a/packages/scaffold/src/views/w3m-connect-view/index.tsx b/packages/scaffold/src/views/w3m-connect-view/index.tsx deleted file mode 100644 index f1222cc8..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { Platform, ScrollView, View } from 'react-native'; -import { - ApiController, - ConnectorController, - EventUtil, - EventsController, - OptionsController, - RouterController, - type WcWallet -} from '@reown/appkit-core-react-native'; -import { FlexView, Icon, ListItem, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { ConnectEmailInput } from './components/connect-email-input'; -import { useKeyboard } from '../../hooks/useKeyboard'; -import { Placeholder } from '../../partials/w3m-placeholder'; -import { ConnectorList } from './components/connectors-list'; -import { CustomWalletList } from './components/custom-wallet-list'; -import { AllWalletsButton } from './components/all-wallets-button'; -import { AllWalletList } from './components/all-wallet-list'; -import { RecentWalletList } from './components/recent-wallet-list'; -import { SocialLoginList } from './components/social-login-list'; -import { WalletGuide } from './components/wallet-guide'; -import styles from './styles'; - -export function ConnectView() { - const connectors = ConnectorController.state.connectors; - const { authLoading } = useSnapshot(ConnectorController.state); - const { prefetchError } = useSnapshot(ApiController.state); - const { features } = useSnapshot(OptionsController.state); - const { padding } = useCustomDimensions(); - const { keyboardShown, keyboardHeight } = useKeyboard(); - - const isWalletConnectEnabled = connectors.some(c => c.type === 'WALLET_CONNECT'); - const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); - const isCoinbaseEnabled = connectors.some(c => c.type === 'COINBASE'); - const isEmailEnabled = isAuthEnabled && features?.email; - const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; - const showConnectWalletsButton = - isWalletConnectEnabled && isAuthEnabled && !features?.emailShowWallets; - const showSeparator = - isAuthEnabled && - (isEmailEnabled || isSocialEnabled) && - (isWalletConnectEnabled || isCoinbaseEnabled); - const showLoadingError = !showConnectWalletsButton && prefetchError; - const showList = !showConnectWalletsButton && !showLoadingError; - - const paddingBottom = Platform.select({ - android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], - default: Spacing['2xl'] - }); - - const onWalletPress = (wallet: WcWallet, isInstalled?: boolean) => { - const connector = connectors.find(c => c.explorerId === wallet.id); - if (connector) { - RouterController.push('ConnectingExternal', { connector, wallet }); - } else { - RouterController.push('ConnectingWalletConnect', { wallet }); - } - - const platform = EventUtil.getWalletPlatform(wallet, isInstalled); - EventsController.sendEvent({ - type: 'track', - event: 'SELECT_WALLET', - properties: { - name: wallet.name ?? connector?.name ?? 'Unknown', - platform, - explorer_id: wallet.id - } - }); - }; - - const onViewAllPress = () => { - RouterController.push('AllWallets'); - EventsController.sendEvent({ type: 'track', event: 'CLICK_ALL_WALLETS' }); - }; - - return ( - - - {isEmailEnabled && } - {isSocialEnabled && } - {showSeparator && } - - - {showConnectWalletsButton && ( - - - Continue with a wallet - - - )} - {showLoadingError && ( - - - - - )} - {showList && ( - <> - - - - - - - )} - {isAuthEnabled && } - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-connect-view/styles.ts b/packages/scaffold/src/views/w3m-connect-view/styles.ts deleted file mode 100644 index 819b007d..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/styles.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - item: { - marginVertical: Spacing['3xs'] - }, - socialSeparator: { - marginVertical: Spacing.xs - }, - connectWalletButton: { - justifyContent: 'space-between' - }, - connectWalletEmpty: { - height: 20, - width: 20 - } -}); diff --git a/packages/scaffold/src/views/w3m-connect-view/utils.ts b/packages/scaffold/src/views/w3m-connect-view/utils.ts deleted file mode 100644 index 94811382..00000000 --- a/packages/scaffold/src/views/w3m-connect-view/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { WcWallet } from '@reown/appkit-core-react-native'; - -export const filterOutRecentWallets = ( - recentWallets?: WcWallet[], - wallets?: WcWallet[], - resentCount?: number -) => { - const recentIds = recentWallets?.slice(0, resentCount ?? 1).map(wallet => wallet.id); - if (!recentIds?.length) return wallets ?? []; - - const filtered = wallets?.filter(wallet => !recentIds.includes(wallet.id)) || []; - - return filtered; -}; diff --git a/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx deleted file mode 100644 index e5df102e..00000000 --- a/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { ScrollView } from 'react-native'; -import { - RouterController, - ApiController, - AssetUtil, - ConnectionController, - ModalController, - EventsController, - StorageUtil, - type WcWallet -} from '@reown/appkit-core-react-native'; -import { - Button, - FlexView, - IconBox, - LoadingThumbnail, - WalletImage -} from '@reown/appkit-ui-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { ConnectingBody, getMessage, type BodyErrorType } from '../../partials/w3m-connecting-body'; -import styles from './styles'; - -export function ConnectingExternalView() { - const { data } = RouterController.state; - const connector = data?.connector; - const { maxWidth: width } = useCustomDimensions(); - const [errorType, setErrorType] = useState(); - const bodyMessage = getMessage({ walletName: data?.wallet?.name, errorType }); - - const onRetryPress = () => { - setErrorType(undefined); - onConnect(); - }; - - const storeConnectedWallet = useCallback( - async (wallet?: WcWallet) => { - if (wallet) { - const recentWallets = await StorageUtil.addRecentWallet(wallet); - if (recentWallets) { - ConnectionController.setRecentWallets(recentWallets); - } - } - if (connector) { - const url = AssetUtil.getConnectorImage(connector); - ConnectionController.setConnectedWalletImageUrl(url); - } - }, - [connector] - ); - - const onConnect = useCallback(async () => { - try { - if (connector) { - await ConnectionController.connectExternal(connector); - storeConnectedWallet(data?.wallet); - ModalController.close(); - EventsController.sendEvent({ - type: 'track', - event: 'CONNECT_SUCCESS', - properties: { - name: data.wallet?.name ?? 'Unknown', - method: 'mobile', - explorer_id: data.wallet?.id - } - }); - } - } catch (error) { - if (/(Wallet not found)/i.test((error as Error).message)) { - setErrorType('not_installed'); - } else if (/(rejected)/i.test((error as Error).message)) { - setErrorType('declined'); - } else { - setErrorType('default'); - } - EventsController.sendEvent({ - type: 'track', - event: 'CONNECT_ERROR', - properties: { message: (error as Error)?.message ?? 'Unknown' } - }); - } - }, [connector, storeConnectedWallet, data?.wallet]); - - useEffect(() => { - onConnect(); - }, [onConnect]); - - return ( - - - - - {errorType && ( - - )} - - - {errorType !== 'not_installed' && ( - - )} - - - ); -} diff --git a/packages/scaffold/src/views/w3m-connecting-external-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-external-view/styles.ts deleted file mode 100644 index ccc94c03..00000000 --- a/packages/scaffold/src/views/w3m-connecting-external-view/styles.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - paddingBottom: Spacing['3xl'] - }, - retryButton: { - marginTop: Spacing.m - }, - retryIcon: { - transform: [{ rotateY: '180deg' }] - }, - errorIcon: { - position: 'absolute', - bottom: 5, - right: 5, - zIndex: 2 - } -}); diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx deleted file mode 100644 index fd7cb308..00000000 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Linking } from 'react-native'; -import { useCallback, useEffect, useState } from 'react'; -import { - ConnectionController, - ConnectorController, - EventsController, - ModalController, - OptionsController, - SnackController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; -import { - FlexView, - LoadingThumbnail, - IconBox, - Logo, - Text, - Link -} from '@reown/appkit-ui-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function ConnectingFarcasterView() { - const { maxWidth: width } = useCustomDimensions(); - const authConnector = ConnectorController.getAuthConnector(); - const [error, setError] = useState(false); - const [processing, setProcessing] = useState(false); - const [url, setUrl] = useState(); - const showCopy = OptionsController.isClipboardAvailable(); - const provider = authConnector?.provider as AppKitFrameProvider; - - const onConnect = useCallback(async () => { - try { - if (provider && authConnector) { - setError(false); - const { url: farcasterUrl } = await provider.getFarcasterUri(); - setUrl(farcasterUrl); - Linking.openURL(farcasterUrl); - - await provider.connectFarcaster(); - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', - properties: { provider: 'farcaster' } - }); - setProcessing(true); - await ConnectionController.connectExternal(authConnector); - ConnectionController.setConnectedSocialProvider('farcaster'); - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_SUCCESS', - properties: { provider: 'farcaster' } - }); - - setProcessing(false); - ModalController.close(); - } - } catch (e) { - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_ERROR', - properties: { provider: 'farcaster' } - }); - // TODO: remove this once Farcaster session refresh is implemented - // @ts-expect-error - provider?.webviewRef?.current?.reload(); - SnackController.showError('Something went wrong'); - setError(true); - setProcessing(false); - } - }, [provider, authConnector]); - - const onCopyUrl = () => { - if (url) { - OptionsController.copyToClipboard(url); - SnackController.showSuccess('Link copied'); - } - }; - - useEffect(() => { - return () => { - // TODO: remove this once Farcaster session refresh is implemented - if (!ModalController.state.open) { - // @ts-expect-error - provider.webviewRef?.current?.reload(); - } - }; - // @ts-expect-error - }, [provider.webviewRef]); - - useEffect(() => { - onConnect(); - }, [onConnect]); - - return ( - - <> - - - {error && ( - - )} - - - {processing ? 'Loading user data' : 'Continue in Farcaster'} - - - {processing - ? 'Please wait a moment while we load your data' - : 'Connect in the Farcaster app'} - - {showCopy && ( - - Copy link - - )} - - - ); -} diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts deleted file mode 100644 index 542a8ad2..00000000 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - errorIcon: { - position: 'absolute', - bottom: 8, - right: 8, - zIndex: 2 - }, - continueText: { - marginTop: Spacing.m, - marginBottom: Spacing.xs - }, - copyButton: { - marginTop: Spacing.m - } -}); diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx deleted file mode 100644 index dc7b0aaa..00000000 --- a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useCallback, useEffect, useState } from 'react'; -import { Platform } from 'react-native'; -import { - ConnectionController, - ConnectorController, - EventsController, - ModalController, - RouterController, - SnackController, - WebviewController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; -import { FlexView, LoadingThumbnail, IconBox, Logo, Text } from '@reown/appkit-ui-react-native'; -import { StringUtil } from '@reown/appkit-common-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function ConnectingSocialView() { - const { maxWidth: width } = useCustomDimensions(); - const { processingAuth } = useSnapshot(WebviewController.state); - const { selectedSocialProvider } = useSnapshot(ConnectionController.state); - const authConnector = ConnectorController.getAuthConnector(); - const [error, setError] = useState(false); - const provider = authConnector?.provider as AppKitFrameProvider; - - const onConnect = useCallback(async () => { - try { - if ( - !WebviewController.state.connecting && - provider && - ConnectionController.state.selectedSocialProvider - ) { - const { uri } = await provider.getSocialRedirectUri({ - provider: ConnectionController.state.selectedSocialProvider - }); - WebviewController.setWebviewUrl(uri); - - const isNativeApple = - ConnectionController.state.selectedSocialProvider === 'apple' && Platform.OS === 'ios'; - - WebviewController.setWebviewVisible(!isNativeApple); - WebviewController.setConnecting(true); - WebviewController.setConnectingProvider(ConnectionController.state.selectedSocialProvider); - } - } catch (e) { - WebviewController.setWebviewVisible(false); - WebviewController.setWebviewUrl(undefined); - WebviewController.setConnecting(false); - WebviewController.setConnectingProvider(undefined); - SnackController.showError('Something went wrong'); - setError(true); - } - }, [provider]); - - const socialMessageHandler = useCallback( - async (url: string) => { - try { - if ( - url.includes('/sdk/oauth') && - ConnectionController.state.selectedSocialProvider && - authConnector && - !WebviewController.state.processingAuth - ) { - WebviewController.setProcessingAuth(true); - WebviewController.setWebviewVisible(false); - const parsedUrl = new URL(url); - - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', - properties: { provider: ConnectionController.state.selectedSocialProvider } - }); - - await provider?.connectSocial(parsedUrl.search); - await ConnectionController.connectExternal(authConnector); - ConnectionController.setConnectedSocialProvider( - ConnectionController.state.selectedSocialProvider - ); - WebviewController.setConnecting(false); - - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_SUCCESS', - properties: { provider: ConnectionController.state.selectedSocialProvider } - }); - - ModalController.close(); - WebviewController.reset(); - } - } catch (e) { - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_ERROR', - properties: { provider: ConnectionController.state.selectedSocialProvider! } - }); - WebviewController.reset(); - RouterController.goBack(); - SnackController.showError('Something went wrong'); - } - }, - [authConnector, provider] - ); - - useEffect(() => { - onConnect(); - }, [onConnect]); - - useEffect(() => { - if (!provider) return; - - const unsubscribe = provider?.getEventEmitter().addListener('social', socialMessageHandler); - - return () => { - unsubscribe.removeListener('social', socialMessageHandler); - }; - }, [socialMessageHandler, provider]); - - return ( - - - - {error && ( - - )} - - - {processingAuth - ? 'Loading user data' - : `Continue with ${StringUtil.capitalize(selectedSocialProvider)}`} - - - {processingAuth - ? 'Please wait a moment while we load your data' - : 'Connect in the provider window'} - - - ); -} diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts deleted file mode 100644 index dcb555e8..00000000 --- a/packages/scaffold/src/views/w3m-connecting-social-view/styles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - errorIcon: { - position: 'absolute', - bottom: 8, - right: 8, - zIndex: 2 - }, - continueText: { - marginTop: Spacing.m, - marginBottom: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-connecting-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-view/index.tsx deleted file mode 100644 index 44ee537c..00000000 --- a/packages/scaffold/src/views/w3m-connecting-view/index.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useEffect, useState } from 'react'; -import { - AccountController, - ConnectionController, - ConstantsUtil, - CoreHelperUtil, - ModalController, - RouterController, - SnackController, - type Platform, - OptionsController, - ApiController, - EventsController, - ConnectorController -} from '@reown/appkit-core-react-native'; -import { SIWEController } from '@reown/appkit-siwe-react-native'; - -import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; -import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; -import { ConnectingWeb } from '../../partials/w3m-connecting-web'; -import { ConnectingHeader } from '../../partials/w3m-connecting-header'; -import { UiUtil } from '../../utils/UiUtil'; - -export function ConnectingView() { - const { installed } = useSnapshot(ApiController.state); - const { data } = RouterController.state; - const [lastRetry, setLastRetry] = useState(Date.now()); - const isQr = !data?.wallet; - const isInstalled = !!installed?.find(wallet => wallet.id === data?.wallet?.id); - - const [platform, setPlatform] = useState(); - const [platforms, setPlatforms] = useState([]); - - const onRetry = () => { - if (CoreHelperUtil.isAllowedRetry(lastRetry)) { - setLastRetry(Date.now()); - ConnectionController.clearUri(); - initializeConnection(true); - } else { - SnackController.showError('Please wait a second before retrying'); - } - }; - - const initializeConnection = async (retry = false) => { - try { - const { wcPairingExpiry } = ConnectionController.state; - const { data: routeData } = RouterController.state; - if (retry || CoreHelperUtil.isPairingExpired(wcPairingExpiry)) { - ConnectionController.setWcError(false); - ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); - await ConnectionController.state.wcPromise; - ConnectorController.setConnectedConnector('WALLET_CONNECT'); - AccountController.setIsConnected(true); - - if (OptionsController.state.isSiweEnabled) { - if (SIWEController.state.status === 'success') { - ModalController.close(); - } else { - RouterController.push('ConnectingSiwe'); - } - } else { - ModalController.close(); - } - } - } catch (error) { - ConnectionController.setWcError(true); - ConnectionController.clearUri(); - SnackController.showError('Declined'); - if (isQr && CoreHelperUtil.isAllowedRetry(lastRetry)) { - setLastRetry(Date.now()); - initializeConnection(true); - } - EventsController.sendEvent({ - type: 'track', - event: 'CONNECT_ERROR', - properties: { - message: (error as Error)?.message ?? 'Unknown' - } - }); - } - }; - - const onCopyUri = (uri?: string) => { - if (OptionsController.isClipboardAvailable() && uri) { - OptionsController.copyToClipboard(uri); - SnackController.showSuccess('Link copied'); - } - }; - - const onSelectPlatform = (tab: Platform) => { - UiUtil.createViewTransition(); - setPlatform(tab); - }; - - const headerTemplate = () => { - if (isQr) return null; - - if (platforms.length > 1) { - return ; - } - - return null; - }; - - const platformTemplate = () => { - if (isQr) { - return ; - } - - switch (platform) { - case 'mobile': - return ( - - ); - case 'web': - return ; - default: - return undefined; - } - }; - - useEffect(() => { - const _platforms: Platform[] = []; - if (data?.wallet?.mobile_link) { - _platforms.push('mobile'); - } - if (data?.wallet?.webapp_link && !isInstalled) { - _platforms.push('web'); - } - - setPlatforms(_platforms); - setPlatform(_platforms[0]); - }, [data, isInstalled]); - - useEffect(() => { - initializeConnection(); - let _interval: NodeJS.Timeout; - - // Check if the pairing expired every 10 seconds. If expired, it will create a new uri. - if (isQr) { - _interval = setInterval(initializeConnection, ConstantsUtil.TEN_SEC_MS); - } - - return () => clearInterval(_interval); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isQr]); - - return ( - <> - {headerTemplate()} - {platformTemplate()} - - ); -} diff --git a/packages/scaffold/src/views/w3m-create-view/index.tsx b/packages/scaffold/src/views/w3m-create-view/index.tsx deleted file mode 100644 index dcfa0965..00000000 --- a/packages/scaffold/src/views/w3m-create-view/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Platform, ScrollView } from 'react-native'; -import { useSnapshot } from 'valtio'; -import { FlexView, Spacing } from '@reown/appkit-ui-react-native'; -import { ConnectEmailInput } from '../w3m-connect-view/components/connect-email-input'; -import { SocialLoginList } from '../w3m-connect-view/components/social-login-list'; -import { WalletGuide } from '../w3m-connect-view/components/wallet-guide'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { ConnectorController, OptionsController } from '@reown/appkit-core-react-native'; -import { useKeyboard } from '../../hooks/useKeyboard'; - -export function CreateView() { - const connectors = ConnectorController.state.connectors; - const { authLoading } = useSnapshot(ConnectorController.state); - const { features } = useSnapshot(OptionsController.state); - const { padding } = useCustomDimensions(); - const { keyboardShown, keyboardHeight } = useKeyboard(); - const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); - const isEmailEnabled = isAuthEnabled && features?.email; - const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; - - const paddingBottom = Platform.select({ - android: keyboardShown ? keyboardHeight + Spacing.xl : Spacing.xl, - default: Spacing.xl - }); - - return ( - - - {isEmailEnabled && } - {isSocialEnabled && } - {isAuthEnabled && } - - - ); -} diff --git a/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx b/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx deleted file mode 100644 index 510e75ea..00000000 --- a/packages/scaffold/src/views/w3m-email-verify-device-view/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { View } from 'react-native'; -import { useEffect, useState } from 'react'; -import { FlexView, Icon, Link, Text, useTheme } from '@reown/appkit-ui-react-native'; -import { - ConnectorController, - CoreHelperUtil, - EventsController, - RouterController, - SnackController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; -import useTimeout from '../../hooks/useTimeout'; -import styles from './styles'; - -export function EmailVerifyDeviceView() { - const Theme = useTheme(); - const { connectors } = useSnapshot(ConnectorController.state); - const { data } = RouterController.state; - const { timeLeft, startTimer } = useTimeout(0); - const [loading, setLoading] = useState(false); - const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; - - const listenForDeviceApproval = async () => { - if (authProvider && data?.email) { - try { - await authProvider.connectDevice(); - EventsController.sendEvent({ type: 'track', event: 'DEVICE_REGISTERED_FOR_EMAIL' }); - EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_SENT' }); - RouterController.replace('EmailVerifyOtp', { email: data.email }); - } catch (error: any) { - RouterController.goBack(); - } - } - }; - - const onResendEmail = async () => { - try { - if (!data?.email || !authProvider) return; - setLoading(true); - authProvider?.connectEmail({ email: data.email }); - listenForDeviceApproval(); - SnackController.showSuccess('Link resent'); - startTimer(30); - setLoading(false); - } catch (e) { - const parsedError = CoreHelperUtil.parseError(e); - SnackController.showError(parsedError); - } - }; - - useEffect(() => { - listenForDeviceApproval(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - - - - - - Register this device to continue - - - Check the instructions sent to{' '} - - {data?.email ?? 'your email'} - - The link expires in 20 minutes - - - Didn't receive it? - 0 || loading}> - {timeLeft > 0 ? `Resend in ${timeLeft}s` : 'Resend link'} - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-email-verify-device-view/styles.ts b/packages/scaffold/src/views/w3m-email-verify-device-view/styles.ts deleted file mode 100644 index 9e5db141..00000000 --- a/packages/scaffold/src/views/w3m-email-verify-device-view/styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - iconContainer: { - height: 64, - width: 64, - justifyContent: 'center', - alignItems: 'center', - borderRadius: Spacing.xl, - marginBottom: Spacing['2xl'] - }, - headingText: { - marginBottom: Spacing.s - }, - expiryText: { - marginVertical: Spacing.l - } -}); diff --git a/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx b/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx deleted file mode 100644 index 4a7fbdf3..00000000 --- a/packages/scaffold/src/views/w3m-email-verify-otp-view/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { useState } from 'react'; -import { - ConnectionController, - ConnectorController, - CoreHelperUtil, - EventsController, - ModalController, - RouterController, - SnackController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; -import useTimeout from '../../hooks/useTimeout'; -import { OtpCodeView } from '../../partials/w3m-otp-code'; - -export function EmailVerifyOtpView() { - const { timeLeft, startTimer } = useTimeout(0); - const { data } = RouterController.state; - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const authConnector = ConnectorController.getAuthConnector(); - - const onOtpResend = async () => { - try { - if (!data?.email || !authConnector) return; - setLoading(true); - const provider = authConnector?.provider as AppKitFrameProvider; - await provider.connectEmail({ email: data.email }); - SnackController.showSuccess('Code resent'); - startTimer(30); - setLoading(false); - } catch (e) { - const parsedError = CoreHelperUtil.parseError(e); - SnackController.showError(parsedError); - setLoading(false); - } - }; - - const onOtpSubmit = async (otp: string) => { - if (!authConnector) return; - setLoading(true); - setError(''); - try { - const provider = authConnector?.provider as AppKitFrameProvider; - await provider.connectOtp({ otp }); - EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_PASS' }); - await ConnectionController.connectExternal(authConnector); - ModalController.close(); - EventsController.sendEvent({ - type: 'track', - event: 'CONNECT_SUCCESS', - properties: { method: 'email', name: authConnector.name || 'Unknown' } - }); - } catch (e) { - EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_FAIL' }); - const parsedError = CoreHelperUtil.parseError(e); - if (parsedError?.includes('Invalid code')) { - setError('Invalid code. Try again.'); - } else { - SnackController.showError(parsedError); - } - } - setLoading(false); - }; - - return ( - - ); -} diff --git a/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx deleted file mode 100644 index 3cc7478a..00000000 --- a/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Linking, Platform, ScrollView } from 'react-native'; -import { FlexView, ListWallet } from '@reown/appkit-ui-react-native'; -import { ApiController, AssetUtil, type WcWallet } from '@reown/appkit-core-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function GetWalletView() { - const { padding } = useCustomDimensions(); - const imageHeaders = ApiController._getApiHeaders(); - - const onWalletPress = (wallet: WcWallet) => { - const storeUrl = - Platform.select({ ios: wallet.app_store, android: wallet.play_store }) || wallet.homepage; - if (storeUrl) { - Linking.openURL(storeUrl); - } - }; - - const listTemplate = () => { - return ApiController.state.recommended.map(wallet => ( - onWalletPress(wallet)} - style={styles.item} - /> - )); - }; - - return ( - - - {listTemplate()} - Linking.openURL('https://walletconnect.com/explorer?type=wallet')} - /> - - - ); -} diff --git a/packages/scaffold/src/views/w3m-get-wallet-view/styles.ts b/packages/scaffold/src/views/w3m-get-wallet-view/styles.ts deleted file mode 100644 index 9ce25737..00000000 --- a/packages/scaffold/src/views/w3m-get-wallet-view/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - item: { - marginBottom: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx b/packages/scaffold/src/views/w3m-network-switch-view/index.tsx deleted file mode 100644 index 8adf0455..00000000 --- a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx +++ /dev/null @@ -1,138 +0,0 @@ -/* eslint-disable valtio/state-snapshot-rule */ -import { useSnapshot } from 'valtio'; -import { useEffect, useState } from 'react'; -import { - ApiController, - AssetUtil, - ConnectionController, - ConnectorController, - EventsController, - NetworkController, - RouterController, - RouterUtil -} from '@reown/appkit-core-react-native'; -import { - Button, - FlexView, - IconBox, - LoadingHexagon, - NetworkImage, - Text -} from '@reown/appkit-ui-react-native'; -import styles from './styles'; - -export function NetworkSwitchView() { - const { data } = useSnapshot(RouterController.state); - const { recentWallets } = useSnapshot(ConnectionController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH'; - const [error, setError] = useState(false); - const [showRetry, setShowRetry] = useState(false); - const network = data?.network!; - const wallet = recentWallets?.[0]; - - const onSwitchNetwork = async () => { - try { - setError(false); - await NetworkController.switchActiveNetwork(network); - EventsController.sendEvent({ - type: 'track', - event: 'SWITCH_NETWORK', - properties: { - network: network.id - } - }); - } catch { - setError(true); - setShowRetry(true); - } - }; - - useEffect(() => { - onSwitchNetwork(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - // Go back if network is already switched - if (caipNetwork?.id === network?.id) { - RouterUtil.navigateAfterNetworkSwitch(); - } - }, [caipNetwork?.id, network?.id]); - - const retryTemplate = () => { - if (!showRetry) return null; - - return ( - - ); - }; - - const textTemplate = () => { - const walletName = wallet?.name ?? 'wallet'; - if (error) { - return ( - <> - - Switch declined - - - Switch can be declined if chain is not supported by a wallet or previous request is - still active - - - ); - } - - if (isAuthConnected) { - return ( - - Switching to {network.name} network - - ); - } - - return ( - <> - {`Approve in ${walletName}`} - - Accept switch request in your wallet - - - ); - }; - - return ( - - - - {error && ( - - )} - - {textTemplate()} - {retryTemplate()} - - ); -} diff --git a/packages/scaffold/src/views/w3m-network-switch-view/styles.ts b/packages/scaffold/src/views/w3m-network-switch-view/styles.ts deleted file mode 100644 index 924d6704..00000000 --- a/packages/scaffold/src/views/w3m-network-switch-view/styles.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - descriptionText: { - marginHorizontal: Spacing['3xl'] - }, - errorIcon: { - position: 'absolute', - bottom: 12, - right: 20, - zIndex: 2 - }, - retryButton: { - marginTop: Spacing.xl - }, - retryIcon: { - transform: [{ rotateY: '180deg' }] - }, - text: { - marginVertical: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-networks-view/index.tsx b/packages/scaffold/src/views/w3m-networks-view/index.tsx deleted file mode 100644 index 41f98b3b..00000000 --- a/packages/scaffold/src/views/w3m-networks-view/index.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { ScrollView, View } from 'react-native'; -import { - CardSelect, - CardSelectWidth, - FlexView, - Link, - Separator, - Spacing, - Text -} from '@reown/appkit-ui-react-native'; -import { - ApiController, - AssetUtil, - NetworkController, - RouterController, - EventsController, - CoreHelperUtil, - NetworkUtil -} from '@reown/appkit-core-react-native'; -import type { CaipNetwork } from '@reown/appkit-common-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function NetworksView() { - const { caipNetwork, requestedCaipNetworks, approvedCaipNetworkIds, supportsAllNetworks } = - NetworkController.state; - const imageHeaders = ApiController._getApiHeaders(); - const { maxWidth: width, padding } = useCustomDimensions(); - const numColumns = 4; - const usableWidth = width - Spacing.xs * 2 - Spacing['4xs']; - const itemWidth = Math.abs(Math.trunc(usableWidth / numColumns)); - const itemGap = Math.abs( - Math.trunc((usableWidth - numColumns * CardSelectWidth) / numColumns) / 2 - ); - - const onHelpPress = () => { - RouterController.push('WhatIsANetwork'); - EventsController.sendEvent({ type: 'track', event: 'CLICK_NETWORK_HELP' }); - }; - - const networksTemplate = () => { - const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); - - const onNetworkPress = async (network: CaipNetwork) => { - const result = await NetworkUtil.handleNetworkSwitch(network); - if (result?.type === 'SWITCH_NETWORK') { - EventsController.sendEvent({ - type: 'track', - event: 'SWITCH_NETWORK', - properties: { - network: network.id - } - }); - } - }; - - return networks.map(network => ( - - onNetworkPress(network)} - /> - - )); - }; - - return ( - <> - - - {networksTemplate()} - - - - - - Your connected wallet may not support some of the networks available for this dApp - - - What is a network? - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-networks-view/styles.ts b/packages/scaffold/src/views/w3m-networks-view/styles.ts deleted file mode 100644 index aa4204c1..00000000 --- a/packages/scaffold/src/views/w3m-networks-view/styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - itemContainer: { - alignItems: 'center', - justifyContent: 'center' - }, - helpButton: { - marginTop: Spacing.s - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-checkout-view/index.tsx b/packages/scaffold/src/views/w3m-onramp-checkout-view/index.tsx deleted file mode 100644 index a3085027..00000000 --- a/packages/scaffold/src/views/w3m-onramp-checkout-view/index.tsx +++ /dev/null @@ -1,266 +0,0 @@ -import { - AssetUtil, - NetworkController, - OnRampController, - RouterController, - ThemeController -} from '@reown/appkit-core-react-native'; -import { - BorderRadius, - Button, - FlexView, - Image, - Separator, - Spacing, - Text, - Toggle, - useTheme -} from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; -import { useSnapshot } from 'valtio'; -import { NumberUtil, StringUtil } from '@reown/appkit-common-react-native'; - -export function OnRampCheckoutView() { - const Theme = useTheme(); - const { themeMode } = useSnapshot(ThemeController.state); - const { selectedQuote, selectedPaymentMethod, purchaseCurrency } = useSnapshot( - OnRampController.state - ); - - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - - const value = NumberUtil.roundNumber(selectedQuote?.destinationAmount ?? 0, 6, 5); - const symbol = selectedQuote?.destinationCurrencyCode; - const paymentLogo = selectedPaymentMethod?.logos[themeMode ?? 'light']; - const providerImage = OnRampController.getServiceProviderImage( - selectedQuote?.serviceProvider ?? '' - ); - - const showNetworkFee = selectedQuote?.networkFee != null; - const showTransactionFee = selectedQuote?.transactionFee != null; - const showTotalFee = selectedQuote?.totalFee != null; - const showFees = showNetworkFee || showTransactionFee || showTotalFee; - - const onConfirm = () => { - RouterController.push('OnRampLoading'); - }; - - return ( - - - You Buy - - {value} - - {symbol?.split('_')[0] ?? symbol ?? ''} - - - - via - {providerImage && } - {StringUtil.capitalize(selectedQuote?.serviceProvider)} - - - - - You Pay - - {selectedQuote?.sourceAmount} {selectedQuote?.sourceCurrencyCode} - - - - You Receive - - - {value} {symbol?.split('_')[0] ?? ''} - - {purchaseCurrency?.symbolImageUrl && ( - - )} - - - - Network - - {purchaseCurrency?.chainName} - - - - Pay with - - {paymentLogo && ( - - )} - - {selectedPaymentMethod?.name} - - - - - {showFees && ( - - - Fees{' '} - {showTotalFee && ( - - {selectedQuote?.totalFee} {selectedQuote?.sourceCurrencyCode} - - )} - - - } - style={[styles.feesToggle, { backgroundColor: Theme['gray-glass-002'] }]} - contentContainerStyle={styles.feesToggleContent} - > - {showNetworkFee && ( - - - Network Fees - - - {networkImage && ( - - )} - - {selectedQuote?.networkFee} {selectedQuote?.sourceCurrencyCode} - - - - )} - {showTransactionFee && ( - - - Transaction Fees - - - {selectedQuote.transactionFee} {selectedQuote?.sourceCurrencyCode} - - - )} - - )} - - - - - - ); -} - -const styles = StyleSheet.create({ - amount: { - fontSize: 38, - marginRight: Spacing['3xs'] - }, - separator: { - marginVertical: Spacing.m - }, - feesToggle: { - borderRadius: BorderRadius.xs - }, - feesToggleContent: { - paddingHorizontal: Spacing.xs, - paddingBottom: Spacing.xs - }, - toggleItem: { - padding: Spacing.s, - borderRadius: BorderRadius.xxs - }, - paymentMethodImage: { - width: 14, - height: 14, - marginRight: Spacing['3xs'] - }, - confirmButton: { - marginLeft: Spacing.s, - flex: 3 - }, - cancelButton: { - flex: 1 - }, - providerImage: { - height: 16, - width: 16, - marginRight: 2 - }, - tokenImage: { - height: 20, - width: 20, - marginLeft: 4, - borderRadius: BorderRadius.full, - borderWidth: 1 - }, - networkImage: { - height: 16, - width: 16, - marginRight: 4, - borderRadius: BorderRadius.full, - borderWidth: 1 - }, - paymentMethodContainer: { - borderWidth: StyleSheet.hairlineWidth, - borderRadius: BorderRadius.full, - padding: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-loading-view/index.tsx b/packages/scaffold/src/views/w3m-onramp-loading-view/index.tsx deleted file mode 100644 index f2351aef..00000000 --- a/packages/scaffold/src/views/w3m-onramp-loading-view/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { useCallback, useEffect } from 'react'; -import { useSnapshot } from 'valtio'; -import { Linking, ScrollView } from 'react-native'; -import { - RouterController, - OnRampController, - OptionsController, - EventsController -} from '@reown/appkit-core-react-native'; -import { FlexView, DoubleImageLoader, IconLink, Button, Text } from '@reown/appkit-ui-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { ConnectingBody } from '../../partials/w3m-connecting-body'; -import styles from './styles'; -import { StringUtil } from '@reown/appkit-common-react-native'; - -export function OnRampLoadingView() { - const { maxWidth: width } = useCustomDimensions(); - const { error } = useSnapshot(OnRampController.state); - const providerName = StringUtil.capitalize( - OnRampController.state.selectedQuote?.serviceProvider.toLowerCase() - ); - - const serviceProvideLogo = OnRampController.getServiceProviderImage( - OnRampController.state.selectedQuote?.serviceProvider ?? '' - ); - - const handleGoBack = () => { - if (EventsController.state.data.event === 'BUY_SUBMITTED') { - // Send event only if the onramp url was already created - EventsController.sendEvent({ - type: 'track', - event: 'BUY_CANCEL' - }); - } - - RouterController.goBack(); - }; - - const onConnect = useCallback(async () => { - if (OnRampController.state.selectedQuote) { - OnRampController.clearError(); - const response = await OnRampController.generateWidget({ - quote: OnRampController.state.selectedQuote - }); - if (response?.widgetUrl) { - Linking.openURL(response?.widgetUrl); - } - } - }, []); - - useEffect(() => { - const unsubscribe = Linking.addEventListener('url', ({ url }) => { - const metadata = OptionsController.state.metadata; - - if ( - (metadata?.redirect?.universal && url.startsWith(metadata?.redirect?.universal)) || - (metadata?.redirect?.native && url.startsWith(metadata?.redirect?.native)) - ) { - const parsedUrl = new URL(url); - const searchParams = new URLSearchParams(parsedUrl.search); - const asset = searchParams.get('cryptoCurrency'); - const network = searchParams.get('network'); - const purchaseAmount = searchParams.get('cryptoAmount'); - const amount = searchParams.get('fiatAmount'); - const currency = searchParams.get('fiatCurrency'); - const orderId = searchParams.get('orderId'); - const status = searchParams.get('status'); - - EventsController.sendEvent({ - type: 'track', - event: 'BUY_SUCCESS', - properties: { - asset, - network, - amount, - currency, - orderId - } - }); - - RouterController.reset('OnRampTransaction', { - onrampResult: { - purchaseCurrency: asset, - purchaseAmount, - purchaseImageUrl: OnRampController.state.purchaseCurrency?.symbolImageUrl ?? '', - paymentCurrency: currency, - paymentAmount: amount, - network: network, - status: status - } - }); - } - }); - - return () => unsubscribe.remove(); - }, []); - - useEffect(() => { - onConnect(); - }, [onConnect]); - - return ( - - - - - {error ? ( - - - There was an error while connecting with {providerName} - - - - ) : ( - - )} - - - ); -} diff --git a/packages/scaffold/src/views/w3m-onramp-loading-view/styles.ts b/packages/scaffold/src/views/w3m-onramp-loading-view/styles.ts deleted file mode 100644 index b4f0bab9..00000000 --- a/packages/scaffold/src/views/w3m-onramp-loading-view/styles.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - container: { - paddingBottom: Spacing['3xl'] - }, - backButton: { - alignSelf: 'flex-start' - }, - imageContainer: { - marginBottom: Spacing.s - }, - retryButton: { - marginTop: Spacing.m - }, - retryIcon: { - transform: [{ rotateY: '180deg' }] - }, - errorText: { - marginHorizontal: Spacing['4xl'] - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/components/Country.tsx b/packages/scaffold/src/views/w3m-onramp-settings-view/components/Country.tsx deleted file mode 100644 index c5a3c3e6..00000000 --- a/packages/scaffold/src/views/w3m-onramp-settings-view/components/Country.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import type { OnRampCountry } from '@reown/appkit-core-react-native'; -import { - Pressable, - FlexView, - Spacing, - Text, - Icon, - BorderRadius -} from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; -import { SvgUri } from 'react-native-svg'; - -interface Props { - onPress: (item: OnRampCountry) => void; - item: OnRampCountry; - selected: boolean; -} - -export const ITEM_HEIGHT = 60; - -export function Country({ onPress, item, selected }: Props) { - const handlePress = () => { - onPress(item); - }; - - return ( - - - - {item.flagImageUrl && SvgUri && } - - - - {item.name} - - - {item.countryCode} - - - {selected && ( - - )} - - - ); -} - -const styles = StyleSheet.create({ - container: { - borderRadius: BorderRadius.s, - height: ITEM_HEIGHT, - justifyContent: 'center' - }, - imageContainer: { - borderRadius: BorderRadius.full, - overflow: 'hidden', - marginRight: Spacing.xs - }, - textContainer: { - flex: 1 - }, - checkmark: { - marginRight: Spacing['2xs'] - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/index.tsx b/packages/scaffold/src/views/w3m-onramp-settings-view/index.tsx deleted file mode 100644 index 1f2063bd..00000000 --- a/packages/scaffold/src/views/w3m-onramp-settings-view/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { memo, useState } from 'react'; -import { SvgUri } from 'react-native-svg'; -import { FlexView, ListItem, Text, useTheme, Icon, Image } from '@reown/appkit-ui-react-native'; -import { - OnRampController, - type OnRampCountry, - type OnRampFiatCurrency -} from '@reown/appkit-core-react-native'; - -import { SelectorModal } from '../../partials/w3m-selector-modal'; -import { Country } from './components/Country'; -import { Currency } from '../w3m-onramp-view/components/Currency'; -import { - getModalTitle, - getItemHeight, - getModalItems, - getModalItemKey, - getModalSearchPlaceholder -} from './utils'; -import { styles } from './styles'; - -type ModalType = 'country' | 'paymentCurrency'; - -const MemoizedCountry = memo(Country); -const MemoizedCurrency = memo(Currency); - -export function OnRampSettingsView() { - const { paymentCurrency, selectedCountry } = useSnapshot(OnRampController.state); - const Theme = useTheme(); - const [modalType, setModalType] = useState(); - const [searchValue, setSearchValue] = useState(''); - - const onCountryPress = () => { - setModalType('country'); - }; - - const onPaymentCurrencyPress = () => { - setModalType('paymentCurrency'); - }; - - const onPressModalItem = async (item: any) => { - setModalType(undefined); - setSearchValue(''); - if (modalType === 'country') { - await OnRampController.setSelectedCountry(item as OnRampCountry); - } else if (modalType === 'paymentCurrency') { - OnRampController.setPaymentCurrency(item as OnRampFiatCurrency); - } - }; - - const renderModalItem = ({ item }: { item: any }) => { - if (modalType === 'country') { - const parsedItem = item as OnRampCountry; - - return ( - - ); - } - - const parsedItem = item as OnRampFiatCurrency; - - return ( - - ); - }; - - return ( - <> - - - - - {selectedCountry?.flagImageUrl && SvgUri ? ( - - ) : undefined} - - - - Select Country - {selectedCountry?.name && ( - - {selectedCountry?.name} - - )} - - - - - - {paymentCurrency?.symbolImageUrl ? ( - - ) : ( - - )} - - - - Select Currency - {paymentCurrency?.name && ( - - {paymentCurrency?.name} - - )} - - - - setModalType(undefined)} - items={getModalItems(modalType, searchValue, true)} - selectedItem={modalType === 'country' ? selectedCountry : paymentCurrency} - onSearch={setSearchValue} - renderItem={renderModalItem} - keyExtractor={(item: any, index: number) => getModalItemKey(modalType, index, item)} - title={getModalTitle(modalType)} - itemHeight={getItemHeight(modalType)} - searchPlaceholder={getModalSearchPlaceholder(modalType)} - /> - - ); -} diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/styles.ts b/packages/scaffold/src/views/w3m-onramp-settings-view/styles.ts deleted file mode 100644 index 8d0a6d4a..00000000 --- a/packages/scaffold/src/views/w3m-onramp-settings-view/styles.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - itemContent: { - paddingLeft: 0 - }, - firstItem: { - marginBottom: Spacing.xs - }, - image: { - height: 20, - width: 20 - }, - imageContainer: { - borderRadius: BorderRadius.full, - height: 36, - width: 36, - marginRight: Spacing.s - }, - imageBorder: { - borderRadius: BorderRadius.full, - overflow: 'hidden' - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-settings-view/utils.ts b/packages/scaffold/src/views/w3m-onramp-settings-view/utils.ts deleted file mode 100644 index 4106dd28..00000000 --- a/packages/scaffold/src/views/w3m-onramp-settings-view/utils.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ITEM_HEIGHT as COUNTRY_ITEM_HEIGHT } from './components/Country'; -import { ITEM_HEIGHT as CURRENCY_ITEM_HEIGHT } from '../w3m-onramp-view/components/Currency'; -import { - OnRampController, - type OnRampCountry, - type OnRampFiatCurrency -} from '@reown/appkit-core-react-native'; - -// -------------------------- Types -------------------------- -type ModalType = 'country' | 'paymentCurrency'; - -// -------------------------- Constants -------------------------- -const MODAL_TITLES: Record = { - country: 'Select Country', - paymentCurrency: 'Select Currency' -}; - -const MODAL_SEARCH_PLACEHOLDERS: Record = { - country: 'Search country', - paymentCurrency: 'Search currency' -}; - -const ITEM_HEIGHTS: Record = { - country: COUNTRY_ITEM_HEIGHT, - paymentCurrency: CURRENCY_ITEM_HEIGHT -}; - -const KEY_EXTRACTORS: Record string> = { - country: (item: OnRampCountry) => item.countryCode, - paymentCurrency: (item: OnRampFiatCurrency) => item.currencyCode -}; - -// -------------------------- Utils -------------------------- -export const getItemHeight = (type?: ModalType) => { - return type ? ITEM_HEIGHTS[type] : 0; -}; - -export const getModalTitle = (type?: ModalType) => { - return type ? MODAL_TITLES[type] : undefined; -}; - -export const getModalSearchPlaceholder = (type?: ModalType) => { - return type ? MODAL_SEARCH_PLACEHOLDERS[type] : undefined; -}; - -const searchFilter = ( - item: { name: string; currencyCode?: string; countryCode?: string }, - searchValue: string -) => { - const search = searchValue.toLowerCase(); - - return ( - item.name.toLowerCase().includes(search) || - (item.currencyCode?.toLowerCase().includes(search) ?? false) || - (item.countryCode?.toLowerCase().includes(search) ?? false) - ); -}; - -export const getModalItemKey = (type: ModalType | undefined, index: number, item: any) => { - return type ? KEY_EXTRACTORS[type](item) : index.toString(); -}; - -export const getModalItems = ( - type?: Exclude, - searchValue?: string, - filterSelected?: boolean -) => { - const items = { - country: () => - filterSelected - ? OnRampController.state.countries.filter( - c => c.countryCode !== OnRampController.state.selectedCountry?.countryCode - ) - : OnRampController.state.countries, - paymentCurrency: () => - filterSelected - ? OnRampController.state.paymentCurrencies?.filter( - pc => pc.currencyCode !== OnRampController.state.paymentCurrency?.currencyCode - ) - : OnRampController.state.paymentCurrencies - }; - - const result = items[type!]?.() || []; - - return searchValue - ? result.filter((item: { name: string; currencyCode?: string }) => - searchFilter(item, searchValue) - ) - : result; -}; diff --git a/packages/scaffold/src/views/w3m-onramp-transaction-view/index.tsx b/packages/scaffold/src/views/w3m-onramp-transaction-view/index.tsx deleted file mode 100644 index 45e6d4f8..00000000 --- a/packages/scaffold/src/views/w3m-onramp-transaction-view/index.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useEffect } from 'react'; -import { - AccountController, - ConnectorController, - OnRampController, - RouterController -} from '@reown/appkit-core-react-native'; -import { StringUtil } from '@reown/appkit-common-react-native'; -import { Button, FlexView, IconBox, Image, Text, useTheme } from '@reown/appkit-ui-react-native'; -import styles from './styles'; - -export function OnRampTransactionView() { - const Theme = useTheme(); - const { data } = useSnapshot(RouterController.state); - - const onClose = () => { - const isAuth = ConnectorController.state.connectedConnector === 'AUTH'; - RouterController.replace(isAuth ? 'Account' : 'AccountDefault'); - }; - - const showNetwork = !!data?.onrampResult?.network; - const showStatus = !!data?.onrampResult?.status; - - useEffect(() => { - return () => { - OnRampController.resetState(); - AccountController.fetchTokenBalance(); - }; - }, []); - - return ( - - - - - - You successfully bought {data?.onrampResult?.purchaseCurrency} - - - - - - You Paid - - - {data?.onrampResult?.paymentAmount} {data?.onrampResult?.paymentCurrency} - - - - - You Bought - - - - {data?.onrampResult?.purchaseAmount}{' '} - {data?.onrampResult?.purchaseCurrency?.split('_')[0] ?? ''} - - {data?.onrampResult?.purchaseImageUrl && ( - - )} - - - {showNetwork && ( - - - Network - - - {StringUtil.capitalize(data?.onrampResult?.network)} - - - )} - {showStatus && ( - - - Status - - - {StringUtil.capitalize(data?.onrampResult?.status)} - - - )} - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-onramp-transaction-view/styles.ts b/packages/scaffold/src/views/w3m-onramp-transaction-view/styles.ts deleted file mode 100644 index 2e73f68a..00000000 --- a/packages/scaffold/src/views/w3m-onramp-transaction-view/styles.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - icon: { - marginBottom: Spacing.m - }, - card: { - borderRadius: BorderRadius.s - }, - tokenImage: { - height: 16, - width: 16, - marginLeft: 4, - borderRadius: BorderRadius.full, - borderWidth: 1 - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/Currency.tsx b/packages/scaffold/src/views/w3m-onramp-view/components/Currency.tsx deleted file mode 100644 index 9492dfa3..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/components/Currency.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { - type OnRampFiatCurrency, - type OnRampCryptoCurrency -} from '@reown/appkit-core-react-native'; -import { - Pressable, - FlexView, - Spacing, - Text, - useTheme, - Icon, - Image, - BorderRadius -} from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export const ITEM_HEIGHT = 60; - -interface Props { - onPress: (item: OnRampFiatCurrency | OnRampCryptoCurrency) => void; - item: OnRampFiatCurrency | OnRampCryptoCurrency; - selected: boolean; - title: string; - subtitle: string; - testID?: string; -} - -export function Currency({ onPress, item, selected, title, subtitle, testID }: Props) { - const Theme = useTheme(); - - const handlePress = () => { - onPress(item); - }; - - return ( - - - - - - - {title} - - - {subtitle} - - - - {selected && ( - - )} - - - ); -} - -const styles = StyleSheet.create({ - container: { - justifyContent: 'center', - height: ITEM_HEIGHT, - borderRadius: BorderRadius.s - }, - logo: { - width: 36, - height: 36, - borderRadius: BorderRadius.full, - marginRight: Spacing.xs - }, - checkmark: { - marginRight: Spacing['2xs'] - }, - selected: { - borderWidth: 1 - }, - text: { - flex: 1 - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/CurrencyInput.tsx b/packages/scaffold/src/views/w3m-onramp-view/components/CurrencyInput.tsx deleted file mode 100644 index 7fe03cf3..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/components/CurrencyInput.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; -import { - Button, - FlexView, - useTheme, - Text, - LoadingSpinner, - NumericKeyboard, - Separator, - Spacing, - BorderRadius -} from '@reown/appkit-ui-react-native'; -import { useEffect, useState } from 'react'; -import { useRef } from 'react'; - -export interface InputTokenProps { - style?: StyleProp; - value?: string; - symbol?: string; - loading?: boolean; - error?: string; - isAmountError?: boolean; - purchaseValue?: string; - onValueChange?: (value: number) => void; - onSuggestedValuePress?: (value: number) => void; - suggestedValues?: number[]; -} - -export function CurrencyInput({ - value, - loading, - error, - isAmountError, - purchaseValue, - onValueChange, - onSuggestedValuePress, - symbol, - style, - suggestedValues -}: InputTokenProps) { - const Theme = useTheme(); - const [displayValue, setDisplayValue] = useState(value?.toString() || '0'); - const isInternalChange = useRef(false); - const amountColor = isAmountError ? 'error-100' : value ? 'fg-100' : 'fg-200'; - - const handleKeyPress = (key: string) => { - isInternalChange.current = true; - - if (key === 'erase') { - setDisplayValue(prev => { - const newDisplay = prev.slice(0, -1) || '0'; - - // If the previous value does not end with a comma, convert to numeric value - if (!prev?.endsWith(',')) { - const numericValue = Number(newDisplay.replace(',', '.')); - onValueChange?.(numericValue); - } - - return newDisplay; - }); - } else if (key === ',') { - setDisplayValue(prev => { - if (prev.includes(',')) return prev; // Don't add multiple commas - const newDisplay = prev + ','; - - return newDisplay; - }); - } else { - setDisplayValue(prev => { - const newDisplay = prev === '0' ? key : prev + key; - - // Convert to numeric value - const numericValue = Number(newDisplay.replace(',', '.')); - onValueChange?.(numericValue); - - return newDisplay; - }); - } - }; - - useEffect(() => { - // Handle external value changes - if (!isInternalChange.current && value !== undefined) { - setDisplayValue(value.toString()); - } - isInternalChange.current = false; - }, [value]); - - return ( - - - - {displayValue} - - {symbol || ''} - - - - {loading ? ( - - ) : error ? ( - - {error} - - ) : ( - - {purchaseValue} - - )} - - - - {suggestedValues?.map((suggestion: number) => { - const isSelected = suggestion.toString() === value; - - return ( - - ); - })} - - - - - ); -} - -const styles = StyleSheet.create({ - input: { - fontSize: 38, - marginRight: Spacing['3xs'] - }, - bottomContainer: { - height: 20 - }, - separator: { - marginTop: 16 - }, - suggestedValue: { - flex: 1, - borderRadius: BorderRadius.xxs, - marginRight: Spacing.xs, - height: 40 - }, - selectedValue: { - borderWidth: StyleSheet.hairlineWidth - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/Header.tsx b/packages/scaffold/src/views/w3m-onramp-view/components/Header.tsx deleted file mode 100644 index 064c91a6..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/components/Header.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { ModalController, RouterController } from '@reown/appkit-core-react-native'; -import { IconLink, Text } from '@reown/appkit-ui-react-native'; -import { FlexView } from '@reown/appkit-ui-react-native'; - -interface HeaderProps { - onSettingsPress: () => void; -} - -export function Header({ onSettingsPress }: HeaderProps) { - const handleGoBack = () => { - if (RouterController.state.history.length > 1) { - RouterController.goBack(); - } else { - ModalController.close(); - } - }; - - return ( - - - - Buy crypto - - - - ); -} - -const styles = StyleSheet.create({ - icon: { - height: 40, - width: 40 - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/LoadingView.tsx b/packages/scaffold/src/views/w3m-onramp-view/components/LoadingView.tsx deleted file mode 100644 index 3260f959..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/components/LoadingView.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { FlexView, Text, Shimmer } from '@reown/appkit-ui-react-native'; -import { Dimensions, ScrollView } from 'react-native'; -import { Header } from './Header'; -import styles from '../styles'; - -export function LoadingView() { - const windowWidth = Dimensions.get('window').width; - - return ( - <> -
{}} /> - - - - - You Buy - - - - - {/* Currency Input Area */} - - - - - {/* Payment Method Button */} - - - {/* Action Buttons */} - - - - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/PaymentMethod.tsx b/packages/scaffold/src/views/w3m-onramp-view/components/PaymentMethod.tsx deleted file mode 100644 index 1996246e..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/components/PaymentMethod.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { ThemeController, type OnRampPaymentMethod } from '@reown/appkit-core-react-native'; -import { - Pressable, - FlexView, - Spacing, - Text, - useTheme, - Image, - BorderRadius, - IconBox -} from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export const ITEM_SIZE = 85; - -interface Props { - onPress: (item: OnRampPaymentMethod) => void; - item: OnRampPaymentMethod; - selected: boolean; - testID?: string; -} - -export function PaymentMethod({ onPress, item, selected, testID }: Props) { - const Theme = useTheme(); - const { themeMode } = useSnapshot(ThemeController.state); - - const handlePress = () => { - onPress(item); - }; - - return ( - - - - {selected && ( - - )} - - - {item.name} - - - ); -} - -const styles = StyleSheet.create({ - container: { - height: ITEM_SIZE, - width: ITEM_SIZE, - justifyContent: 'center', - alignItems: 'center' - }, - logoContainer: { - width: 60, - height: 60, - borderRadius: BorderRadius.full, - marginBottom: Spacing['4xs'] - }, - logo: { - width: 22, - height: 22 - }, - checkmark: { - borderRadius: BorderRadius.full, - position: 'absolute', - bottom: 0, - right: -10 - }, - text: { - marginTop: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/Quote.tsx b/packages/scaffold/src/views/w3m-onramp-view/components/Quote.tsx deleted file mode 100644 index 97372fd0..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/components/Quote.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { NumberUtil } from '@reown/appkit-common-react-native'; -import { type OnRampQuote } from '@reown/appkit-core-react-native'; -import { - FlexView, - Image, - Spacing, - Text, - Tag, - useTheme, - BorderRadius, - Icon, - Pressable -} from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -interface Props { - item: OnRampQuote; - isBestDeal?: boolean; - tagText?: string; - logoURL?: string; - onQuotePress: (item: OnRampQuote) => void; - selected?: boolean; -} - -export const ITEM_HEIGHT = 64; - -export function Quote({ item, logoURL, onQuotePress, selected, tagText }: Props) { - const Theme = useTheme(); - - return ( - onQuotePress(item)} - > - - - {logoURL ? ( - - ) : ( - - )} - - - - {item.serviceProvider?.toLowerCase()} - - {tagText && ( - - {tagText} - - )} - - - {NumberUtil.roundNumber(item.destinationAmount, 6, 5)} {item.destinationCurrencyCode} - - - - {selected && } - - - ); -} - -const styles = StyleSheet.create({ - container: { - borderRadius: BorderRadius.xs, - borderWidth: 1, - borderColor: 'transparent', - height: ITEM_HEIGHT, - justifyContent: 'center' - }, - logo: { - height: 40, - width: 40, - borderRadius: BorderRadius['3xs'], - marginRight: Spacing.xs - }, - providerText: { - textTransform: 'capitalize' - }, - tag: { - padding: Spacing['3xs'], - marginLeft: Spacing['2xs'] - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx b/packages/scaffold/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx deleted file mode 100644 index eac3c426..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/components/SelectPaymentModal.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useRef, useState, useMemo } from 'react'; -import Modal from 'react-native-modal'; -import { Dimensions, FlatList, StyleSheet, View } from 'react-native'; -import { - FlexView, - IconLink, - Spacing, - Text, - useTheme, - Separator, - LoadingSpinner, - BorderRadius -} from '@reown/appkit-ui-react-native'; -import { - OnRampController, - type OnRampPaymentMethod, - type OnRampQuote -} from '@reown/appkit-core-react-native'; -import { Placeholder } from '../../../partials/w3m-placeholder'; -import { Quote, ITEM_HEIGHT as QUOTE_ITEM_HEIGHT } from './Quote'; -import { PaymentMethod, ITEM_SIZE } from './PaymentMethod'; - -interface SelectPaymentModalProps { - title?: string; - visible: boolean; - onClose: () => void; -} - -const SEPARATOR_HEIGHT = Spacing.s; - -export function SelectPaymentModal({ title, visible, onClose }: SelectPaymentModalProps) { - const Theme = useTheme(); - const { selectedQuote, quotes } = useSnapshot(OnRampController.state); - const paymentMethodsRef = useRef(null); - const [paymentMethods, setPaymentMethods] = useState( - OnRampController.state.paymentMethods - ); - - const sortedQuotes = useMemo(() => { - if (!selectedQuote) { - return quotes; - } - - return [ - selectedQuote, - // eslint-disable-next-line valtio/state-snapshot-rule - ...(quotes?.filter(quote => quote.serviceProvider !== selectedQuote.serviceProvider) ?? []) - ]; - }, [quotes, selectedQuote]); - - const renderSeparator = () => { - return ; - }; - - const handleQuotePress = (quote: OnRampQuote) => { - if (quote.serviceProvider !== OnRampController.state.selectedQuote?.serviceProvider) { - OnRampController.setSelectedQuote(quote); - } - onClose(); - }; - - const handlePaymentMethodPress = (paymentMethod: OnRampPaymentMethod) => { - if ( - paymentMethod.paymentMethod !== OnRampController.state.selectedPaymentMethod?.paymentMethod - ) { - OnRampController.setSelectedPaymentMethod(paymentMethod); - } - - const visibleItemsCount = Math.round(Dimensions.get('window').width / ITEM_SIZE); - - // Switch payment method to the top if there are more than visibleItemsCount payment methods - if (OnRampController.state.paymentMethods.length > visibleItemsCount) { - const paymentIndex = paymentMethods.findIndex( - method => method.paymentMethod === paymentMethod.paymentMethod - ); - - // Switch payment if its not visible - if (paymentIndex + 1 > visibleItemsCount - 1) { - const realIndex = OnRampController.state.paymentMethods.findIndex( - method => method.paymentMethod === paymentMethod.paymentMethod - ); - - const newPaymentMethods = [ - paymentMethod, - ...OnRampController.state.paymentMethods.slice(0, realIndex), - ...OnRampController.state.paymentMethods.slice(realIndex + 1) - ]; - setPaymentMethods(newPaymentMethods); - } - } - paymentMethodsRef.current?.scrollToIndex({ - index: 0, - animated: true - }); - }; - - const renderQuote = ({ item }: { item: OnRampQuote }) => { - const logoURL = OnRampController.getServiceProviderImage(item.serviceProvider); - const selected = item.serviceProvider === OnRampController.state.selectedQuote?.serviceProvider; - const isBestDeal = - OnRampController.state.quotes?.findIndex( - quote => quote.serviceProvider === item.serviceProvider - ) === 0; - const tagText = isBestDeal ? 'Best Deal' : item.lowKyc ? 'Low KYC' : undefined; - - return ( - handleQuotePress(item)} - tagText={tagText} - /> - ); - }; - - const renderEmpty = () => { - return OnRampController.state.quotesLoading ? ( - - - - ) : ( - - ); - }; - - const renderPaymentMethod = ({ item }: { item: OnRampPaymentMethod }) => { - const parsedItem = item as OnRampPaymentMethod; - const selected = - parsedItem.paymentMethod === OnRampController.state.selectedPaymentMethod?.paymentMethod; - - return ( - handlePaymentMethodPress(parsedItem)} - selected={selected} - testID={`payment-method-item-${parsedItem.paymentMethod}`} - /> - ); - }; - - return ( - - - - - {!!title && {title}} - - - - Pay with - - - item.paymentMethod} - horizontal - showsHorizontalScrollIndicator={false} - /> - - - - Providers - - `${item.serviceProvider}-${item.paymentMethodType}`} - getItemLayout={(_, index) => ({ - length: QUOTE_ITEM_HEIGHT + SEPARATOR_HEIGHT, - offset: (QUOTE_ITEM_HEIGHT + SEPARATOR_HEIGHT) * index, - index - })} - /> - - - ); -} -const styles = StyleSheet.create({ - modal: { - margin: 0, - justifyContent: 'flex-end' - }, - header: { - marginBottom: Spacing.l, - paddingHorizontal: Spacing.m, - paddingTop: Spacing.m - }, - container: { - height: '80%', - borderTopLeftRadius: BorderRadius.l, - borderTopRightRadius: BorderRadius.l - }, - separator: { - width: undefined, - marginVertical: Spacing.m, - marginHorizontal: Spacing.m - }, - listContent: { - paddingTop: Spacing['3xs'], - paddingBottom: Spacing['4xl'], - paddingHorizontal: Spacing.m - }, - iconPlaceholder: { - height: 32, - width: 32 - }, - subtitle: { - marginBottom: Spacing.xs, - marginHorizontal: Spacing.m - }, - emptyContainer: { - height: 150 - }, - paymentMethodsContainer: { - paddingHorizontal: Spacing['3xs'] - }, - paymentMethodsContent: { - paddingLeft: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/index.tsx b/packages/scaffold/src/views/w3m-onramp-view/index.tsx deleted file mode 100644 index 74a76291..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/index.tsx +++ /dev/null @@ -1,293 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { memo, useCallback, useEffect, useState } from 'react'; -import { ScrollView } from 'react-native'; -import { - OnRampController, - type OnRampCryptoCurrency, - ThemeController, - RouterController, - type OnRampControllerState, - NetworkController, - AssetUtil, - SnackController, - ConstantsUtil -} from '@reown/appkit-core-react-native'; -import { - Button, - FlexView, - Image, - ListItem, - Text, - TokenButton, - useTheme -} from '@reown/appkit-ui-react-native'; -import { NumberUtil, StringUtil } from '@reown/appkit-common-react-native'; -import { SelectorModal } from '../../partials/w3m-selector-modal'; -import { Currency } from './components/Currency'; -import { getPurchaseCurrencies, getCurrencySuggestedValues } from './utils'; -import { CurrencyInput } from './components/CurrencyInput'; -import { SelectPaymentModal } from './components/SelectPaymentModal'; -import { ITEM_HEIGHT as CURRENCY_ITEM_HEIGHT } from './components/Currency'; -import { Header } from './components/Header'; -import { UiUtil } from '../../utils/UiUtil'; -import { LoadingView } from './components/LoadingView'; -import styles from './styles'; - -const MemoizedCurrency = memo(Currency); - -export function OnRampView() { - const { themeMode } = useSnapshot(ThemeController.state); - const Theme = useTheme(); - - const { - purchaseCurrency, - paymentCurrency, - paymentMethods, - selectedPaymentMethod, - paymentAmount, - quotesLoading, - selectedQuote, - error, - loading, - initialLoading - } = useSnapshot(OnRampController.state) as OnRampControllerState; - const { caipNetwork } = useSnapshot(NetworkController.state); - const [searchValue, setSearchValue] = useState(''); - const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); - const [isPaymentMethodModalVisible, setIsPaymentMethodModalVisible] = useState(false); - const providerImage = OnRampController.getServiceProviderImage(selectedQuote?.serviceProvider); - const suggestedValues = getCurrencySuggestedValues(paymentCurrency); - const purchaseCurrencyCode = - purchaseCurrency?.currencyCode?.split('_')[0] ?? purchaseCurrency?.currencyCode; - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - - const getQuotes = useCallback(() => { - if (OnRampController.canGenerateQuote()) { - OnRampController.getQuotes(); - } - }, []); - - const getProviderButtonText = () => { - if (selectedQuote) { - return 'via '; - } - - if (!paymentAmount) { - return 'Enter an amount'; - } - - if (!paymentMethods?.length) { - return 'No payment methods available'; - } - - return 'Select a provider'; - }; - - const onValueChange = (value: number) => { - UiUtil.animateChange(); - if (!value) { - OnRampController.abortGetQuotes(); - OnRampController.setPaymentAmount(0); - OnRampController.setSelectedQuote(undefined); - OnRampController.clearError(); - - return; - } - - OnRampController.setPaymentAmount(value); - OnRampController.getQuotesDebounced(); - }; - - const onSuggestedValuePress = (value: number) => { - UiUtil.animateChange(); - OnRampController.setPaymentAmount(value); - getQuotes(); - }; - - const handleSearch = (value: string) => { - setSearchValue(value); - }; - - const handleContinue = async () => { - if (OnRampController.state.selectedQuote) { - RouterController.push('OnRampCheckout'); - } - }; - - const renderCurrencyItem = ({ item }: { item: OnRampCryptoCurrency }) => { - return ( - - ); - }; - - const onPressPurchaseCurrency = (item: any) => { - setIsCurrencyModalVisible(false); - setIsPaymentMethodModalVisible(false); - setSearchValue(''); - OnRampController.setPurchaseCurrency(item as OnRampCryptoCurrency); - getQuotes(); - }; - - const onModalClose = () => { - setSearchValue(''); - setIsCurrencyModalVisible(false); - setIsPaymentMethodModalVisible(false); - }; - - useEffect(() => { - getQuotes(); - }, [selectedPaymentMethod, getQuotes]); - - useEffect(() => { - if (error?.type === ConstantsUtil.ONRAMP_ERROR_TYPES.FAILED_TO_LOAD) { - SnackController.showInternalError({ - shortMessage: 'Failed to load data. Please try again later.', - longMessage: error?.message - }); - RouterController.goBack(); - } - }, [error]); - - useEffect(() => { - if (OnRampController.state.countries.length === 0) { - OnRampController.loadOnRampData(); - } - }, []); - - if (initialLoading || OnRampController.state.countries.length === 0) { - return ; - } - - return ( - <> -
RouterController.push('OnRampSettings')} /> - - - - - You Buy - - setIsCurrencyModalVisible(true)} - testID="currency-selector" - chevron - renderClip={ - networkImage ? ( - - ) : null - } - /> - - - setIsPaymentMethodModalVisible(true)} - style={styles.paymentMethodButton} - imageSrc={selectedPaymentMethod?.logos[themeMode ?? 'light']} - imageStyle={styles.paymentMethodImage} - imageContainerStyle={[ - styles.paymentMethodImageContainer, - { backgroundColor: Theme['gray-glass-010'] } - ]} - disabled={!selectedPaymentMethod || !paymentAmount} - testID="payment-method-button" - > - - {selectedPaymentMethod?.name && ( - - {selectedPaymentMethod.name} - - )} - {getProviderButtonText() && ( - - - {getProviderButtonText()} - - {selectedQuote && ( - <> - {providerImage && ( - - )} - - {StringUtil.capitalize(selectedQuote?.serviceProvider)} - - - )} - - )} - - - - - - - - item.currencyCode} - title="Select token" - itemHeight={CURRENCY_ITEM_HEIGHT} - showNetwork - /> - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-onramp-view/styles.ts b/packages/scaffold/src/views/w3m-onramp-view/styles.ts deleted file mode 100644 index cd77e1ec..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/styles.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - continueButton: { - marginLeft: Spacing.m, - flex: 3 - }, - cancelButton: { - flex: 1 - }, - paymentMethodButton: { - borderRadius: BorderRadius.s, - height: 64 - }, - paymentMethodImage: { - width: 20, - height: 20, - borderRadius: 0 - }, - paymentMethodImageContainer: { - width: 40, - height: 40, - borderWidth: 0, - borderRadius: BorderRadius['3xs'] - }, - currencyInput: { - marginBottom: Spacing.m - }, - providerImage: { - height: 16, - width: 16, - marginRight: 2 - }, - networkImage: { - height: 14, - width: 14, - borderRadius: BorderRadius.full, - borderWidth: 1 - } -}); diff --git a/packages/scaffold/src/views/w3m-onramp-view/utils.ts b/packages/scaffold/src/views/w3m-onramp-view/utils.ts deleted file mode 100644 index 520b11fb..00000000 --- a/packages/scaffold/src/views/w3m-onramp-view/utils.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - OnRampController, - NetworkController, - type OnRampFiatCurrency, - ConstantsUtil -} from '@reown/appkit-core-react-native'; - -// -------------------------- Utils -------------------------- -export const getPurchaseCurrencies = (searchValue?: string, filterSelected?: boolean) => { - const networkId = NetworkController.state.caipNetwork?.id?.split(':')[1]; - let networkTokens = - OnRampController.state.purchaseCurrencies?.filter(c => c.chainId === networkId) ?? []; - - if (filterSelected) { - networkTokens = networkTokens?.filter( - c => c.currencyCode !== OnRampController.state.purchaseCurrency?.currencyCode - ); - } - - return searchValue - ? networkTokens.filter( - item => - item.name.toLowerCase().includes(searchValue) || - item.currencyCode.toLowerCase().includes(searchValue) - ) - : networkTokens; -}; - -// Helper function to generate values based on limits and default value -function generateValuesFromLimits( - minAmount: number, - maxAmount: number, - defaultAmount?: number | null -): number[] { - // Use default amount if provided, otherwise calculate a reasonable default - const baseAmount = defaultAmount || Math.min(maxAmount, Math.max(minAmount * 5, 50)); - - // Generate two values less than the default and the default itself - const value1 = Math.max(minAmount, baseAmount * 0.5); - const value2 = Math.max(minAmount, baseAmount * 0.75); - const value3 = baseAmount; - - // Ensure all values are within the maximum limit - const safeValue1 = Math.min(value1, maxAmount); - const safeValue2 = Math.min(value2, maxAmount); - const safeValue3 = Math.min(value3, maxAmount); - - // Round all values to nice numbers - return [safeValue1, safeValue2, safeValue3].map(v => roundToNiceNumber(v)); -} - -// Helper function to round to nice numbers -function roundToNiceNumber(value: number): number { - if (value < 10) return Math.ceil(value); - - if (value < 100) { - // Round to nearest 10 - return Math.ceil(value / 10) * 10; - } else if (value < 1000) { - // Round to nearest 50 - return Math.ceil(value / 50) * 50; - } else if (value < 10000) { - // Round to nearest 100 - return Math.ceil(value / 100) * 100; - } else if (value < 100000) { - // Round to nearest 1000 - return Math.ceil(value / 1000) * 1000; - } else if (value < 1000000) { - // Round to nearest 10000 - return Math.ceil(value / 10000) * 10000; - } else { - // Round to nearest 100000 - return Math.ceil(value / 100000) * 100000; - } -} - -export const getCurrencySuggestedValues = (currency?: OnRampFiatCurrency) => { - if (!currency) return []; - - const limit = OnRampController.getCurrencyLimit(currency); - - // If we have predefined values for this currency, use them - if ( - ConstantsUtil.CURRENCY_SUGGESTED_VALUES[ - currency.currencyCode as keyof typeof ConstantsUtil.CURRENCY_SUGGESTED_VALUES - ] - ) { - const suggestedValues = - ConstantsUtil.CURRENCY_SUGGESTED_VALUES[ - currency.currencyCode as keyof typeof ConstantsUtil.CURRENCY_SUGGESTED_VALUES - ]; - - // Ensure values are within limits - if (limit) { - const minAmount = limit.minimumAmount ?? 0; - const maxAmount = limit.maximumAmount ?? Infinity; - - // Filter values that are within limits - const validValues = suggestedValues?.filter( - (value: number) => value >= minAmount && value <= maxAmount - ); - - // If we have valid values, return them - if (validValues?.length) { - return validValues; - } - - // If no valid values, generate new ones based on limits and default - return generateValuesFromLimits(minAmount, maxAmount, limit?.defaultAmount); - } - - return suggestedValues; - } - - // Fallback to generating values from limits - if (limit) { - const minAmount = limit.minimumAmount ?? 0; - const maxAmount = limit.maximumAmount ?? Infinity; - - return generateValuesFromLimits(minAmount, maxAmount, limit?.defaultAmount); - } - - return []; -}; diff --git a/packages/scaffold/src/views/w3m-swap-preview-view/index.tsx b/packages/scaffold/src/views/w3m-swap-preview-view/index.tsx deleted file mode 100644 index f3c8f77f..00000000 --- a/packages/scaffold/src/views/w3m-swap-preview-view/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useEffect } from 'react'; -import { Platform, ScrollView } from 'react-native'; -import { NumberUtil } from '@reown/appkit-common-react-native'; -import { RouterController, SwapController } from '@reown/appkit-core-react-native'; -import { - Button, - FlexView, - Icon, - Spacing, - Text, - TokenButton, - UiUtil -} from '@reown/appkit-ui-react-native'; -import { SwapDetails } from '../../partials/w3m-swap-details'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { useKeyboard } from '../../hooks/useKeyboard'; -import styles from './styles'; - -export function SwapPreviewView() { - const { padding } = useCustomDimensions(); - const { keyboardShown, keyboardHeight } = useKeyboard(); - const { - sourceToken, - sourceTokenAmount, - sourceTokenPriceInUSD, - toToken, - toTokenAmount, - toTokenPriceInUSD, - loadingQuote, - loadingBuildTransaction, - loadingTransaction, - loadingApprovalTransaction - } = useSnapshot(SwapController.state); - - const sourceTokenMarketValue = - NumberUtil.parseLocalStringToNumber(sourceTokenAmount) * sourceTokenPriceInUSD; - const toTokenMarketValue = NumberUtil.parseLocalStringToNumber(toTokenAmount) * toTokenPriceInUSD; - - const paddingBottom = Platform.select({ - android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], - default: Spacing['2xl'] - }); - - const loading = - loadingQuote || loadingBuildTransaction || loadingTransaction || loadingApprovalTransaction; - - const onCancel = () => { - RouterController.goBack(); - }; - - const onSwap = () => { - if (SwapController.state.approvalTransaction) { - SwapController.sendTransactionForApproval(SwapController.state.approvalTransaction); - } else { - SwapController.sendTransactionForSwap(SwapController.state.swapTransaction); - } - }; - - useEffect(() => { - function refreshTransaction() { - if (!SwapController.state.loadingApprovalTransaction) { - SwapController.getTransaction(); - } - } - - SwapController.getTransaction(); - - const interval = setInterval(refreshTransaction, 10000); - - return () => { - clearInterval(interval); - }; - }, []); - - return ( - - - - - - Send - - - ${UiUtil.formatNumberToLocalString(sourceTokenMarketValue, 2)} - - - - - - - - - Receive - - - ${UiUtil.formatNumberToLocalString(toTokenMarketValue, 2)} - - - - - - - - - Review transaction carefully - - - - - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-swap-preview-view/styles.ts b/packages/scaffold/src/views/w3m-swap-preview-view/styles.ts deleted file mode 100644 index 7af1b350..00000000 --- a/packages/scaffold/src/views/w3m-swap-preview-view/styles.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - swapIcon: { - marginVertical: Spacing.xs - }, - reviewIcon: { - marginRight: Spacing['3xs'] - }, - cancelButton: { - flex: 1 - }, - sendButton: { - marginLeft: Spacing.s, - flex: 3 - } -}); diff --git a/packages/scaffold/src/views/w3m-swap-select-token-view/index.tsx b/packages/scaffold/src/views/w3m-swap-select-token-view/index.tsx deleted file mode 100644 index 0a716800..00000000 --- a/packages/scaffold/src/views/w3m-swap-select-token-view/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { useState } from 'react'; -import { useSnapshot } from 'valtio'; -import { ScrollView, SectionList, type SectionListData } from 'react-native'; -import { - FlexView, - InputText, - ListToken, - ListTokenTotalHeight, - Separator, - Text, - TokenButton, - useTheme -} from '@reown/appkit-ui-react-native'; - -import { - AssetUtil, - NetworkController, - RouterController, - SwapController, - type SwapTokenWithBalance -} from '@reown/appkit-core-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { Placeholder } from '../../partials/w3m-placeholder'; -import styles from './styles'; -import { createSections } from './utils'; - -export function SwapSelectTokenView() { - const { padding } = useCustomDimensions(); - const Theme = useTheme(); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { sourceToken, suggestedTokens } = useSnapshot(SwapController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const [tokenSearch, setTokenSearch] = useState(''); - const isSourceToken = RouterController.state.data?.swapTarget === 'sourceToken'; - - const [filteredTokens, setFilteredTokens] = useState(createSections(isSourceToken, tokenSearch)); - const suggestedList = suggestedTokens - ?.filter(token => token.address !== SwapController.state.sourceToken?.address) - .slice(0, 8); - - const onSearchChange = (value: string) => { - setTokenSearch(value); - setFilteredTokens(createSections(isSourceToken, value)); - }; - - const onTokenPress = (token: SwapTokenWithBalance) => { - if (isSourceToken) { - SwapController.setSourceToken(token); - } else { - SwapController.setToToken(token); - if (SwapController.state.sourceToken && SwapController.state.sourceTokenAmount) { - SwapController.swapTokens(); - } - } - RouterController.goBack(); - }; - - return ( - - - - {!isSourceToken && ( - - {suggestedList?.map((token, index) => ( - onTokenPress(token)} - style={index !== suggestedList.length - 1 ? styles.suggestedToken : undefined} - /> - ))} - - )} - - - []} - bounces={false} - fadingEdgeLength={20} - contentContainerStyle={styles.tokenList} - renderSectionHeader={({ section: { title } }) => ( - - {title} - - )} - ListEmptyComponent={ - - } - getItemLayout={(_, index) => ({ - length: ListTokenTotalHeight, - offset: ListTokenTotalHeight * index, - index - })} - renderItem={({ item }) => ( - onTokenPress(item)} - disabled={item.address === sourceToken?.address} - /> - )} - /> - - ); -} diff --git a/packages/scaffold/src/views/w3m-swap-select-token-view/styles.ts b/packages/scaffold/src/views/w3m-swap-select-token-view/styles.ts deleted file mode 100644 index ffc103fa..00000000 --- a/packages/scaffold/src/views/w3m-swap-select-token-view/styles.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - container: { - minHeight: 250, - maxHeight: 600 - }, - title: { - paddingTop: Spacing['2xs'] - }, - tokenList: { - paddingHorizontal: Spacing.m - }, - input: { - marginHorizontal: Spacing.xs - }, - suggestedList: { - marginTop: Spacing.xs - }, - suggestedListContent: { - paddingHorizontal: Spacing.s - }, - suggestedToken: { - marginRight: Spacing.s - }, - suggestedSeparator: { - marginVertical: Spacing.s - } -}); diff --git a/packages/scaffold/src/views/w3m-swap-select-token-view/utils.ts b/packages/scaffold/src/views/w3m-swap-select-token-view/utils.ts deleted file mode 100644 index 978d2bb6..00000000 --- a/packages/scaffold/src/views/w3m-swap-select-token-view/utils.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { SwapController, type SwapTokenWithBalance } from '@reown/appkit-core-react-native'; - -export function filterTokens(tokens: SwapTokenWithBalance[], searchValue?: string) { - if (!searchValue) { - return tokens; - } - - return tokens.filter( - token => - token.name.toLowerCase().includes(searchValue.toLowerCase()) || - token.symbol.toLowerCase().includes(searchValue.toLowerCase()) - ); -} - -export function createSections(isSourceToken: boolean, searchValue: string) { - const myTokensFiltered = filterTokens( - SwapController.state.myTokensWithBalance ?? [], - searchValue - ); - const popularFiltered = isSourceToken - ? [] - : filterTokens(SwapController.getFilteredPopularTokens() ?? [], searchValue); - - const sections = []; - if (myTokensFiltered.length > 0) { - sections.push({ title: 'Your tokens', data: myTokensFiltered }); - } - if (popularFiltered.length > 0) { - sections.push({ title: 'Popular tokens', data: popularFiltered }); - } - - return sections; -} diff --git a/packages/scaffold/src/views/w3m-swap-view/index.tsx b/packages/scaffold/src/views/w3m-swap-view/index.tsx deleted file mode 100644 index a8778841..00000000 --- a/packages/scaffold/src/views/w3m-swap-view/index.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useCallback, useEffect } from 'react'; -import { Platform, ScrollView } from 'react-native'; -import { - AccountController, - EventsController, - NetworkController, - RouterController, - SwapController -} from '@reown/appkit-core-react-native'; -import { Button, FlexView, IconLink, Spacing, useTheme } from '@reown/appkit-ui-react-native'; -import { NumberUtil } from '@reown/appkit-common-react-native'; - -import { useKeyboard } from '../../hooks/useKeyboard'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { SwapInput } from '../../partials/w3m-swap-input'; -import { useDebounceCallback } from '../../hooks/useDebounceCallback'; -import { SwapDetails } from '../../partials/w3m-swap-details'; -import styles from './styles'; - -export function SwapView() { - const { - initializing, - sourceToken, - toToken, - sourceTokenAmount, - toTokenAmount, - loadingPrices, - loadingQuote, - sourceTokenPriceInUSD, - toTokenPriceInUSD, - myTokensWithBalance, - inputError - } = useSnapshot(SwapController.state); - const Theme = useTheme(); - const { padding } = useCustomDimensions(); - const { keyboardShown, keyboardHeight } = useKeyboard(); - const showDetails = !!sourceToken && !!toToken && !inputError; - - const showSwitch = - myTokensWithBalance && - myTokensWithBalance.findIndex( - token => token.address === SwapController.state.toToken?.address - ) >= 0; - - const paddingBottom = Platform.select({ - android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], - default: Spacing['2xl'] - }); - - const getActionButtonState = () => { - if (!SwapController.state.sourceToken || !SwapController.state.toToken) { - return { text: 'Select token', disabled: true }; - } - - if (!SwapController.state.sourceTokenAmount || !SwapController.state.toTokenAmount) { - return { text: 'Enter amount', disabled: true }; - } - - if (SwapController.state.inputError) { - return { text: SwapController.state.inputError, disabled: true }; - } - - return { text: 'Review swap', disabled: false }; - }; - - const actionState = getActionButtonState(); - const actionLoading = initializing || loadingPrices || loadingQuote; - - const { debouncedCallback: onDebouncedSwap } = useDebounceCallback({ - callback: SwapController.swapTokens.bind(SwapController), - delay: 400 - }); - - const onSourceTokenChange = (value: string) => { - SwapController.setSourceTokenAmount(value); - onDebouncedSwap(); - }; - - const onToTokenChange = (value: string) => { - SwapController.setToTokenAmount(value); - onDebouncedSwap(); - }; - - const onSourceTokenPress = () => { - RouterController.push('SwapSelectToken', { swapTarget: 'sourceToken' }); - }; - - const onReviewPress = () => { - EventsController.sendEvent({ - type: 'track', - event: 'INITIATE_SWAP', - properties: { - network: NetworkController.state.caipNetwork?.id || '', - swapFromToken: SwapController.state.sourceToken?.symbol || '', - swapToToken: SwapController.state.toToken?.symbol || '', - swapFromAmount: SwapController.state.sourceTokenAmount || '', - swapToAmount: SwapController.state.toTokenAmount || '', - isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' - } - }); - RouterController.push('SwapPreview'); - }; - - const onSourceMaxPress = () => { - const isNetworkToken = - SwapController.state.sourceToken?.address === - NetworkController.getActiveNetworkTokenAddress(); - - const _gasPriceInUSD = SwapController.state.gasPriceInUSD; - const _sourceTokenPriceInUSD = SwapController.state.sourceTokenPriceInUSD; - const _balance = SwapController.state.sourceToken?.quantity.numeric; - - if (_balance) { - if (!_gasPriceInUSD) { - return SwapController.setSourceTokenAmount(_balance); - } - - const amountOfTokenGasRequires = NumberUtil.bigNumber(_gasPriceInUSD.toFixed(5)).dividedBy( - _sourceTokenPriceInUSD - ); - - const maxValue = isNetworkToken - ? NumberUtil.bigNumber(_balance).minus(amountOfTokenGasRequires) - : NumberUtil.bigNumber(_balance); - - SwapController.setSourceTokenAmount(maxValue.isGreaterThan(0) ? maxValue.toFixed(20) : '0'); - SwapController.swapTokens(); - } - }; - - const onToTokenPress = () => { - RouterController.push('SwapSelectToken', { swapTarget: 'toToken' }); - }; - - const onSwitchPress = () => { - SwapController.switchTokens(); - }; - - const watchTokens = useCallback(() => { - SwapController.getNetworkTokenPrice(); - SwapController.getMyTokensWithBalance(); - SwapController.swapTokens(); - }, []); - - useEffect(() => { - SwapController.initializeState(); - - const interval = setInterval(watchTokens, 10000); - - return () => { - clearInterval(interval); - }; - }, [watchTokens]); - - return ( - - - - - - {showSwitch && ( - - )} - - {showDetails && } - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-swap-view/styles.ts b/packages/scaffold/src/views/w3m-swap-view/styles.ts deleted file mode 100644 index 99c07ce4..00000000 --- a/packages/scaffold/src/views/w3m-swap-view/styles.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - bottomInputContainer: { - width: '100%' - }, - arrowIcon: { - position: 'absolute', - top: -30, - borderRadius: BorderRadius.xs, - borderWidth: 4, - height: 50, - width: 50 - }, - tokenInput: { - marginBottom: Spacing.xs - }, - actionButton: { - marginTop: Spacing.xs, - width: '100%' - } -}); diff --git a/packages/scaffold/src/views/w3m-transactions-view/index.tsx b/packages/scaffold/src/views/w3m-transactions-view/index.tsx deleted file mode 100644 index b2f66295..00000000 --- a/packages/scaffold/src/views/w3m-transactions-view/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; -import { AccountActivity } from '../../partials/w3m-account-activity'; - -export function TransactionsView() { - return ; -} - -const styles = StyleSheet.create({ - container: { - paddingHorizontal: Spacing.l, - marginTop: Spacing.s - } -}); diff --git a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx b/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx deleted file mode 100644 index 38cbc626..00000000 --- a/packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useState } from 'react'; -import { FlatList } from 'react-native'; -import { Icon, ListItem, Separator, Text } from '@reown/appkit-ui-react-native'; -import { - ApiController, - AssetUtil, - CoreHelperUtil, - EventsController, - NetworkController, - NetworkUtil, - type NetworkControllerState -} from '@reown/appkit-core-react-native'; -import type { CaipNetwork } from '@reown/appkit-common-react-native'; - -import styles from './styles'; - -export function UnsupportedChainView() { - const { caipNetwork, supportsAllNetworks, approvedCaipNetworkIds, requestedCaipNetworks } = - useSnapshot(NetworkController.state) as NetworkControllerState; - - const [disconnecting, setDisconnecting] = useState(false); - const networks = CoreHelperUtil.sortNetworks(approvedCaipNetworkIds, requestedCaipNetworks); - const imageHeaders = ApiController._getApiHeaders(); - - const onNetworkPress = async (network: CaipNetwork) => { - const result = await NetworkUtil.handleNetworkSwitch(network); - if (result?.type === 'SWITCH_NETWORK') { - EventsController.sendEvent({ - type: 'track', - event: 'SWITCH_NETWORK', - properties: { - network: network.id - } - }); - } - }; - - const onDisconnect = async () => { - setDisconnecting(true); - // await ConnectionUtil.disconnect(); - setDisconnecting(false); - }; - - return ( - - The swap feature doesn't support your current network. Switch to an available option to - continue. - - } - contentContainerStyle={styles.contentContainer} - renderItem={({ item }) => ( - onNetworkPress(item)} - testID="button-network" - style={styles.networkItem} - contentStyle={styles.networkItemContent} - disabled={!supportsAllNetworks && !approvedCaipNetworkIds?.includes(item.id)} - > - - {item.name ?? 'Unknown'} - - {item.id === caipNetwork?.id && } - - )} - ListFooterComponent={ - <> - - - Disconnect - - - } - /> - ); -} diff --git a/packages/scaffold/src/views/w3m-unsupported-chain-view/styles.ts b/packages/scaffold/src/views/w3m-unsupported-chain-view/styles.ts deleted file mode 100644 index 0c07dc9c..00000000 --- a/packages/scaffold/src/views/w3m-unsupported-chain-view/styles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - contentContainer: { - padding: Spacing.s, - paddingBottom: Spacing.xl - }, - header: { - marginBottom: Spacing.s - }, - networkItem: { - marginVertical: Spacing['3xs'] - }, - networkItemContent: { - justifyContent: 'space-between' - }, - separator: { - marginBottom: Spacing['2xs'] - } -}); diff --git a/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx b/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx deleted file mode 100644 index 4a6297f0..00000000 --- a/packages/scaffold/src/views/w3m-update-email-primary-otp-view/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useState } from 'react'; - -import { - ConnectorController, - CoreHelperUtil, - EventsController, - RouterController, - SnackController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; - -import { OtpCodeView } from '../../partials/w3m-otp-code'; - -export function UpdateEmailPrimaryOtpView() { - const { data } = RouterController.state; - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const authProvider = ConnectorController.getAuthConnector()?.provider as - | AppKitFrameProvider - | undefined; - - const onOtpSubmit = async (value: string) => { - if (!authProvider || loading) return; - setLoading(true); - setError(''); - try { - await authProvider.updateEmailPrimaryOtp({ otp: value }); - EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_PASS' }); - RouterController.replace('UpdateEmailSecondaryOtp', data); - } catch (e) { - EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_FAIL' }); - const parsedError = CoreHelperUtil.parseError(e); - if (parsedError?.includes('Invalid Otp')) { - setError('Invalid code. Try again.'); - } else { - SnackController.showError(parsedError); - } - } - setLoading(false); - }; - - return ( - - ); -} diff --git a/packages/scaffold/src/views/w3m-update-email-secondary-otp-view/index.tsx b/packages/scaffold/src/views/w3m-update-email-secondary-otp-view/index.tsx deleted file mode 100644 index a0102f33..00000000 --- a/packages/scaffold/src/views/w3m-update-email-secondary-otp-view/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { useState } from 'react'; - -import { - ConnectorController, - CoreHelperUtil, - RouterController, - SnackController, - EventsController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; - -import { OtpCodeView } from '../../partials/w3m-otp-code'; - -export function UpdateEmailSecondaryOtpView() { - const { data } = useSnapshot(RouterController.state); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const authConnector = ConnectorController.getAuthConnector(); - - const onOtpSubmit = async (value: string) => { - if (!authConnector) return; - setLoading(true); - setError(''); - try { - const provider = authConnector?.provider as AppKitFrameProvider; - await provider.updateEmailSecondaryOtp({ otp: value }); - EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_PASS' }); - EventsController.sendEvent({ type: 'track', event: 'EMAIL_EDIT_COMPLETE' }); - RouterController.reset('Account'); - } catch (e) { - EventsController.sendEvent({ type: 'track', event: 'EMAIL_VERIFICATION_CODE_FAIL' }); - const parsedError = CoreHelperUtil.parseError(e); - if (parsedError?.includes('Invalid Otp')) { - setError('Invalid code. Try again.'); - } else { - SnackController.showError(parsedError); - } - } - setLoading(false); - }; - - return ( - - ); -} diff --git a/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx deleted file mode 100644 index 3d8dbeb4..00000000 --- a/packages/scaffold/src/views/w3m-update-email-wallet-view/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { useState } from 'react'; -import { Platform } from 'react-native'; -import { - ConnectorController, - CoreHelperUtil, - RouterController, - SnackController, - EventsController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; -import { Button, EmailInput, FlexView, Spacing, Text } from '@reown/appkit-ui-react-native'; -import { useKeyboard } from '../../hooks/useKeyboard'; - -import styles from './styles'; - -export function UpdateEmailWalletView() { - const { data } = RouterController.state; - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [email, setEmail] = useState(data?.email || ''); - const [isValidNewEmail, setIsValidNewEmail] = useState(false); - const authConnector = ConnectorController.getAuthConnector(); - const { keyboardShown, keyboardHeight } = useKeyboard(); - const paddingBottom = Platform.select({ - android: keyboardShown ? keyboardHeight + Spacing.l : Spacing.l, - default: Spacing.l - }); - - const onChangeText = (value: string) => { - setIsValidNewEmail(data?.email !== value && CoreHelperUtil.isValidEmail(value)); - setEmail(value); - setError(''); - }; - - const onEmailSubmit = async (value: string) => { - if (!authConnector) return; - - const provider = authConnector.provider as AppKitFrameProvider; - setLoading(true); - setError(''); - - try { - const response = await provider.updateEmail({ email: value }); - EventsController.sendEvent({ type: 'track', event: 'EMAIL_EDIT' }); - if (response.action === 'VERIFY_SECONDARY_OTP') { - RouterController.push('UpdateEmailSecondaryOtp', { email: data?.email, newEmail: value }); - } else { - RouterController.push('UpdateEmailPrimaryOtp', { email: data?.email, newEmail: value }); - } - } catch (e) { - const parsedError = CoreHelperUtil.parseError(e); - if (parsedError?.includes('Invalid email')) { - setError('Invalid email. Try again.'); - } else { - SnackController.showError(parsedError); - } - } finally { - setLoading(false); - } - }; - - return ( - - - - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-update-email-wallet-view/styles.ts b/packages/scaffold/src/views/w3m-update-email-wallet-view/styles.ts deleted file mode 100644 index d456fc24..00000000 --- a/packages/scaffold/src/views/w3m-update-email-wallet-view/styles.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - justifyContent: 'center', - alignItems: 'center' - }, - emailInput: { - marginBottom: Spacing.s - }, - cancelButton: { - flex: 1, - height: 48, - marginRight: Spacing['2xs'], - borderRadius: BorderRadius.xs - }, - saveButton: { - flex: 1, - height: 48, - marginLeft: Spacing['2xs'], - borderRadius: BorderRadius.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx deleted file mode 100644 index 6b66cf5f..00000000 --- a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { Linking, StyleSheet } from 'react-native'; -import { Chip, FlexView, Spacing, Text } from '@reown/appkit-ui-react-native'; -import { ConnectorController, type AppKitFrameProvider } from '@reown/appkit-core-react-native'; - -export function UpgradeEmailWalletView() { - const { connectors } = useSnapshot(ConnectorController.state); - const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; - - const onLinkPress = () => { - const link = authProvider.getSecureSiteDashboardURL(); - Linking.canOpenURL(link).then(supported => { - if (supported) Linking.openURL(link); - }); - }; - - return ( - - Follow the instructions on - - - You will have to reconnect for security reasons - - - ); -} - -const styles = StyleSheet.create({ - chip: { - marginVertical: Spacing.m - } -}); diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx deleted file mode 100644 index ce042b10..00000000 --- a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Linking } from 'react-native'; -import { useEffect, useState } from 'react'; -import { useSnapshot } from 'valtio'; -import { Button, FlexView, IconLink, Link, Text, Visual } from '@reown/appkit-ui-react-native'; -import { - AccountController, - ConnectorController, - EventsController, - ModalController, - NetworkController, - RouterController, - SnackController, - type AppKitFrameProvider -} from '@reown/appkit-core-react-native'; -import styles from './styles'; - -export function UpgradeToSmartAccountView() { - const { address } = useSnapshot(AccountController.state); - const { loading } = useSnapshot(ModalController.state); - const [initialAddress] = useState(address); - - const onSwitchAccountType = async () => { - try { - ModalController.setLoading(true); - const accountType = - AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; - const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; - await provider?.setPreferredAccount(accountType); - EventsController.sendEvent({ - type: 'track', - event: 'SET_PREFERRED_ACCOUNT_TYPE', - properties: { - accountType, - network: NetworkController.state.caipNetwork?.id || '' - } - }); - } catch (error) { - ModalController.setLoading(false); - SnackController.showError('Error switching account type'); - } - }; - - const onClose = () => { - ModalController.close(); - ModalController.setLoading(false); - }; - - const onGoBack = () => { - RouterController.goBack(); - ModalController.setLoading(false); - }; - - const onLearnMorePress = () => { - Linking.openURL('https://reown.com/faq'); - }; - - useEffect(() => { - // Go back if the address has changed - if (address && initialAddress !== address) { - RouterController.goBack(); - } - }, [initialAddress, address]); - - return ( - <> - - - - - - - - - - Discover Smart Accounts - - - Access advanced brand new features as username, improved security and a smoother user - experience! - - - - - - - Learn more - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts deleted file mode 100644 index f2d23ef0..00000000 --- a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - justifyContent: 'center', - alignItems: 'center' - }, - title: { - marginTop: Spacing.xl, - marginVertical: Spacing.s - }, - button: { - width: 110 - }, - cancelButton: { - marginRight: Spacing.m - }, - middleIcon: { - marginHorizontal: Spacing.s - }, - closeButton: { - alignSelf: 'flex-end', - right: Spacing.xl, - top: Spacing.l, - position: 'absolute', - zIndex: 2 - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx deleted file mode 100644 index 3695bc47..00000000 --- a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { ScrollView } from 'react-native'; -import { useSnapshot } from 'valtio'; -import { FlexView, Text, Banner, NetworkImage } from '@reown/appkit-ui-react-native'; -import { - AccountController, - ApiController, - AssetUtil, - NetworkController -} from '@reown/appkit-core-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function WalletCompatibleNetworks() { - const { padding } = useCustomDimensions(); - const { preferredAccountType } = useSnapshot(AccountController.state); - const isSmartAccount = - preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); - const approvedNetworks = isSmartAccount - ? NetworkController.getSmartAccountEnabledNetworks() - : NetworkController.getApprovedCaipNetworks(); - const imageHeaders = ApiController._getApiHeaders(); - - return ( - - - - {approvedNetworks.map((network, index) => ( - - - - {network?.name ?? 'Unknown Network'} - - - ))} - - - ); -} diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts deleted file mode 100644 index de669ca6..00000000 --- a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - image: { - marginRight: Spacing.s - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx deleted file mode 100644 index ae41a517..00000000 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { ScrollView, StyleSheet } from 'react-native'; -import { - Chip, - CompatibleNetwork, - FlexView, - QrCode, - Spacing, - Text, - UiUtil -} from '@reown/appkit-ui-react-native'; -import { - AccountController, - ApiController, - AssetUtil, - NetworkController, - OptionsController, - RouterController, - SnackController -} from '@reown/appkit-core-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; - -export function WalletReceiveView() { - const { address, profileName, preferredAccountType } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const { padding } = useCustomDimensions(); - const canCopy = OptionsController.isClipboardAvailable(); - const isSmartAccount = - preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); - const networks = isSmartAccount - ? NetworkController.getSmartAccountEnabledNetworks() - : NetworkController.getApprovedCaipNetworks(); - - const imagesArray = networks - .filter(network => network?.imageId) - .slice(0, 5) - .map(AssetUtil.getNetworkImage) - .filter(Boolean) as string[]; - - const label = UiUtil.getTruncateString({ - string: profileName ?? address ?? '', - charsStart: profileName ? 30 : 4, - charsEnd: profileName ? 0 : 4, - truncate: profileName ? 'end' : 'middle' - }); - - const onNetworkPress = () => { - RouterController.push('WalletCompatibleNetworks'); - }; - - const onCopyAddress = () => { - if (canCopy && AccountController.state.address) { - OptionsController.copyToClipboard(AccountController.state.address); - SnackController.showSuccess('Address copied'); - } - }; - - if (!address) return; - - return ( - - - - - - {canCopy ? 'Copy your address or scan this QR code' : 'Scan this QR code'} - - - - - ); -} - -const styles = StyleSheet.create({ - qrContainer: { - marginVertical: Spacing.xl - }, - networksButton: { - marginTop: Spacing.l - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts deleted file mode 100644 index c866ab3d..00000000 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - justifyContent: 'center', - alignItems: 'center' - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx deleted file mode 100644 index d71df174..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { AssetUtil, type CaipNetwork } from '@reown/appkit-core-react-native'; -import { - BorderRadius, - FlexView, - NetworkImage, - Spacing, - Text, - UiUtil, - useTheme -} from '@reown/appkit-ui-react-native'; -import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; - -export interface PreviewSendDetailsProps { - address?: string; - name?: string; - caipNetwork?: CaipNetwork; - networkFee?: number; - style?: StyleProp; -} - -export function PreviewSendDetails({ - address, - name, - caipNetwork, - networkFee, - style -}: PreviewSendDetailsProps) { - const Theme = useTheme(); - - const formattedName = UiUtil.getTruncateString({ - string: name ?? '', - charsStart: 20, - charsEnd: 0, - truncate: 'end' - }); - - const formattedAddress = UiUtil.getTruncateString({ - string: address || '', - charsStart: 6, - charsEnd: 8, - truncate: 'middle' - }); - - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - - return ( - - - Details - - - - Network cost - - - ${UiUtil.formatNumberToLocalString(networkFee, 2)} - - - - - {formattedName || 'Address'} - - - {formattedAddress} - - - - - Network - - - - - ); -} - -const styles = StyleSheet.create({ - container: { - justifyContent: 'center', - borderRadius: BorderRadius.xxs - }, - title: { - marginBottom: Spacing.xs - }, - item: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: Spacing.s, - borderRadius: BorderRadius.xxs, - marginTop: Spacing['2xs'] - }, - networkImage: { - height: 24, - width: 24, - borderRadius: BorderRadius.full - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx deleted file mode 100644 index ea085ecd..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { BorderRadius, FlexView, Text, useTheme } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export interface PreviewSendPillProps { - text: string; - children: React.ReactNode; -} - -export function PreviewSendPill({ text, children }: PreviewSendPillProps) { - const Theme = useTheme(); - - return ( - - - {text} - - {children} - - ); -} - -const styles = StyleSheet.create({ - pill: { - borderRadius: BorderRadius.full, - borderWidth: StyleSheet.hairlineWidth - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx deleted file mode 100644 index 8b9e7f41..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { useSnapshot } from 'valtio'; -import { ScrollView } from 'react-native'; -import { Avatar, Button, FlexView, Icon, Image, Text, UiUtil } from '@reown/appkit-ui-react-native'; -import { NumberUtil } from '@reown/appkit-common-react-native'; -import { - NetworkController, - RouterController, - SendController -} from '@reown/appkit-core-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { PreviewSendPill } from './components/preview-send-pill'; -import styles from './styles'; -import { PreviewSendDetails } from './components/preview-send-details'; - -export function WalletSendPreviewView() { - const { padding } = useCustomDimensions(); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { - token, - receiverAddress, - receiverProfileName, - receiverProfileImageUrl, - gasPriceInUSD, - loading - } = useSnapshot(SendController.state); - - const getSendValue = () => { - if (SendController.state.token && SendController.state.sendTokenAmount) { - const price = SendController.state.token.price; - const totalValue = price * SendController.state.sendTokenAmount; - - return totalValue.toFixed(2); - } - - return null; - }; - - const getTokenAmount = () => { - const value = SendController.state.sendTokenAmount - ? NumberUtil.roundNumber(SendController.state.sendTokenAmount, 6, 5) - : 'unknown'; - - return `${value} ${SendController.state.token?.symbol}`; - }; - - const formattedAddress = receiverProfileName - ? UiUtil.getTruncateString({ - string: receiverProfileName, - charsStart: 20, - charsEnd: 0, - truncate: 'end' - }) - : UiUtil.getTruncateString({ - string: receiverAddress || '', - charsStart: 4, - charsEnd: 4, - truncate: 'middle' - }); - - const onSend = () => { - SendController.sendToken(); - }; - - const onCancel = () => { - RouterController.goBack(); - SendController.setLoading(false); - }; - - return ( - - - - - - Send - - - ${getSendValue()} - - - - {token?.iconUrl ? ( - - ) : ( - - )} - - - - - - To - - - - - - - - - - Review transaction carefully - - - - - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts deleted file mode 100644 index 432a72c3..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - container: { - justifyContent: 'center', - alignItems: 'center' - }, - tokenLogo: { - height: 32, - width: 32, - borderRadius: BorderRadius.full, - marginLeft: Spacing.xs - }, - arrow: { - marginVertical: Spacing.xs - }, - avatar: { - marginLeft: Spacing.xs - }, - details: { - marginTop: Spacing['2xl'], - marginBottom: Spacing.s - }, - reviewIcon: { - marginRight: Spacing['3xs'] - }, - cancelButton: { - flex: 1 - }, - sendButton: { - marginLeft: Spacing.s, - flex: 3 - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx deleted file mode 100644 index 73a065e3..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useState } from 'react'; -import { useSnapshot } from 'valtio'; -import { ScrollView } from 'react-native'; -import { FlexView, InputText, ListToken, Text } from '@reown/appkit-ui-react-native'; -import { - AccountController, - AssetUtil, - NetworkController, - RouterController, - SendController -} from '@reown/appkit-core-react-native'; -import type { Balance } from '@reown/appkit-common-react-native'; - -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { Placeholder } from '../../partials/w3m-placeholder'; -import styles from './styles'; - -export function WalletSendSelectTokenView() { - const { padding } = useCustomDimensions(); - const { tokenBalance } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); - const { token } = useSnapshot(SendController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const [tokenSearch, setTokenSearch] = useState(''); - const [filteredTokens, setFilteredTokens] = useState(tokenBalance ?? []); - - const onSearchChange = (value: string) => { - setTokenSearch(value); - const filtered = AccountController.state.tokenBalance?.filter(_token => - _token.name.toLowerCase().includes(value.toLowerCase()) - ); - setFilteredTokens(filtered ?? []); - }; - - const onTokenPress = (_token: Balance) => { - SendController.setToken(_token); - SendController.setTokenAmount(undefined); - RouterController.goBack(); - }; - - return ( - - - - - - - Your tokens - - {filteredTokens.length ? ( - filteredTokens.map((_token, index) => ( - onTokenPress(_token)} - disabled={_token.address === token?.address} - /> - )) - ) : ( - - )} - - - ); -} diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts deleted file mode 100644 index 23c2e7c5..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - container: { - minHeight: 250, - maxHeight: 600 - }, - title: { - marginBottom: Spacing.xs - }, - tokenList: { - paddingHorizontal: Spacing.m - } -}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx deleted file mode 100644 index aecdeb05..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { useCallback, useEffect } from 'react'; -import { Platform, ScrollView } from 'react-native'; -import { useSnapshot } from 'valtio'; -import { - AccountController, - CoreHelperUtil, - RouterController, - SendController, - SwapController -} from '@reown/appkit-core-react-native'; -import { Button, FlexView, IconBox, Spacing } from '@reown/appkit-ui-react-native'; -import { SendInputToken } from '../../partials/w3m-send-input-token'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { useKeyboard } from '../../hooks/useKeyboard'; -import { SendInputAddress } from '../../partials/w3m-send-input-address'; -import styles from './styles'; - -export function WalletSendView() { - const { padding } = useCustomDimensions(); - const { keyboardShown, keyboardHeight } = useKeyboard(); - const { token, sendTokenAmount, receiverAddress, receiverProfileName, loading, gasPrice } = - useSnapshot(SendController.state); - const { tokenBalance } = useSnapshot(AccountController.state); - - const paddingBottom = Platform.select({ - android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], - default: Spacing['2xl'] - }); - - const fetchNetworkPrice = useCallback(async () => { - await SwapController.getNetworkTokenPrice(); - const gas = await SwapController.getInitialGasPrice(); - if (gas?.gasPrice && gas?.gasPriceInUSD) { - SendController.setGasPrice(gas.gasPrice); - SendController.setGasPriceInUsd(gas.gasPriceInUSD); - } - }, []); - - const onSendPress = () => { - RouterController.push('WalletSendPreview'); - }; - - const getActionText = () => { - if (!SendController.state.token) { - return 'Select token'; - } - - if ( - SendController.state.sendTokenAmount && - SendController.state.token && - SendController.state.sendTokenAmount > Number(SendController.state.token.quantity.numeric) - ) { - return 'Insufficient funds'; - } - - if (!SendController.state.sendTokenAmount) { - return 'Add amount'; - } - - if (SendController.state.sendTokenAmount && SendController.state.token?.price) { - const value = SendController.state.sendTokenAmount * SendController.state.token.price; - if (!value) { - return 'Incorrect value'; - } - } - - if ( - SendController.state.receiverAddress && - !CoreHelperUtil.isAddress(SendController.state.receiverAddress) - ) { - return 'Invalid address'; - } - - if (!SendController.state.receiverAddress) { - return 'Add address'; - } - - return 'Preview send'; - }; - - useEffect(() => { - if (!token) { - SendController.setToken(tokenBalance?.[0]); - } - fetchNetworkPrice(); - }, [token, tokenBalance, fetchNetworkPrice]); - - const actionText = getActionText(); - - return ( - - - RouterController.push('WalletSendSelectToken')} - /> - - - - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts deleted file mode 100644 index a3cdd0f4..00000000 --- a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { BorderRadius, Spacing } from '@reown/appkit-ui-react-native'; -import { StyleSheet } from 'react-native'; - -export default StyleSheet.create({ - sendButton: { - width: '100%', - marginTop: Spacing.xl, - borderRadius: BorderRadius.xs - }, - tokenInput: { - marginBottom: Spacing.xs - }, - arrowIcon: { - position: 'absolute', - top: -30, - borderRadius: BorderRadius.s - }, - addressContainer: { - width: '100%' - } -}); diff --git a/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx b/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx deleted file mode 100644 index cb8cca52..00000000 --- a/packages/scaffold/src/views/w3m-what-is-a-network-view/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Linking, ScrollView } from 'react-native'; -import { Button, FlexView, Text, Visual } from '@reown/appkit-ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function WhatIsANetworkView() { - const { padding } = useCustomDimensions(); - const onLearnMorePress = () => { - Linking.openURL('https://ethereum.org/en/developers/docs/networks/'); - }; - - return ( - - - - - - - - - The system’s nuts and bolts - - - A network is what brings the blockchain to life, as this technical infrastructure allows - apps to access the ledger and smart contract services. - - - - - - - - Designed for different uses - - - Each network is designed differently, and may therefore suit certain apps and experiences. - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-what-is-a-network-view/styles.ts b/packages/scaffold/src/views/w3m-what-is-a-network-view/styles.ts deleted file mode 100644 index 593afd3b..00000000 --- a/packages/scaffold/src/views/w3m-what-is-a-network-view/styles.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - learnButton: { - marginTop: Spacing.xl - }, - visual: { - marginHorizontal: Spacing.s - }, - text: { - marginVertical: Spacing.xs - } -}); diff --git a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx deleted file mode 100644 index 17cdcc55..00000000 --- a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { ScrollView } from 'react-native'; -import { Button, FlexView, Text, Visual } from '@reown/appkit-ui-react-native'; -import { EventsController, RouterController } from '@reown/appkit-core-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; - -export function WhatIsAWalletView() { - const { padding } = useCustomDimensions(); - - const onGetWalletPress = () => { - RouterController.push('GetWallet'); - EventsController.sendEvent({ type: 'track', event: 'CLICK_GET_WALLET' }); - }; - - return ( - - - - - - - - - Your web3 account - - - Create a wallet with your email or by choosing a wallet provider. - - - - - - - - The home for your digital assets - - - Store, send, and receive digital assets like crypto and NFTs. - - - - - - - - Your gateway to web3 apps - - - Connect your wallet to start exploring DeFi, DAOs, and much more. - - - - - ); -} diff --git a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/styles.ts b/packages/scaffold/src/views/w3m-what-is-a-wallet-view/styles.ts deleted file mode 100644 index 40f3f31b..00000000 --- a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/styles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StyleSheet } from 'react-native'; -import { Spacing } from '@reown/appkit-ui-react-native'; - -export default StyleSheet.create({ - getWalletButton: { - marginTop: Spacing.xl, - marginBottom: Spacing.m - }, - visual: { - marginHorizontal: Spacing.s - }, - text: { - marginVertical: Spacing.xs - } -}); diff --git a/packages/scaffold/tsconfig.json b/packages/scaffold/tsconfig.json deleted file mode 100644 index b8a49a4b..00000000 --- a/packages/scaffold/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src", "src/index.ts"], - "exclude": ["lib", "node_modules"] -} From 33f77f11cf357283aa09ec6ed1427a4fac240a6a Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 9 May 2025 12:43:46 -0300 Subject: [PATCH 23/91] chore: added wagmi connector + added common methods to blockchain adapter --- apps/native/babel.config.js | 16 +- package.json | 1 + packages/appkit/package.json | 2 +- .../src/connectors/WalletConnectConnector.ts | 3 +- packages/appkit/src/utils/HelpersUtil.ts | 45 +- packages/common/src/utils/TypeUtil.ts | 40 ++ packages/wagmi/package.json | 1 - packages/wagmi/src/adapter.ts | 135 +++- packages/wagmi/src/client.ts | 644 ------------------ .../src/connectors/WalletConnectConnector.ts | 576 +++++----------- packages/wagmi/src/index.tsx | 122 ---- .../wagmi/src/utils/defaultWagmiConfig.ts | 53 -- packages/wagmi/src/utils/helpers.ts | 64 +- 13 files changed, 353 insertions(+), 1349 deletions(-) delete mode 100644 packages/wagmi/src/client.ts delete mode 100644 packages/wagmi/src/utils/defaultWagmiConfig.ts diff --git a/apps/native/babel.config.js b/apps/native/babel.config.js index 425262ac..cb69af52 100644 --- a/apps/native/babel.config.js +++ b/apps/native/babel.config.js @@ -1,11 +1,14 @@ const path = require('path'); const uipack = require('../../packages/ui/package.json'); const corepack = require('../../packages/core/package.json'); -const scaffoldpack = require('../../packages/scaffold/package.json'); const wagmipack = require('../../packages/wagmi/package.json'); +const etherspack = require('../../packages/ethers/package.json'); +const bitcoinpack = require('../../packages/bitcoin/package.json'); +const solanapack = require('../../packages/solana/package.json'); const authpack = require('../../packages/auth-wagmi/package.json'); const commonpack = require('../../packages/common/package.json'); const siwepack = require('../../packages/siwe/package.json'); +const appkitpack = require('../../packages/appkit/package.json'); module.exports = function (api) { api.cache(true); @@ -21,15 +24,14 @@ module.exports = function (api) { // For development, we want to alias the packages to the source [uipack.name]: path.join(__dirname, '../../packages/ui', uipack.source), [corepack.name]: path.join(__dirname, '../../packages/core', corepack.source), - [scaffoldpack.name]: path.join( - __dirname, - '../../packages/scaffold', - scaffoldpack.source - ), + [etherspack.name]: path.join(__dirname, '../../packages/ethers', etherspack.source), + [bitcoinpack.name]: path.join(__dirname, '../../packages/bitcoin', bitcoinpack.source), + [solanapack.name]: path.join(__dirname, '../../packages/solana', solanapack.source), [wagmipack.name]: path.join(__dirname, '../../packages/wagmi', wagmipack.source), [authpack.name]: path.join(__dirname, '../../packages/auth-wagmi', authpack.source), [commonpack.name]: path.join(__dirname, '../../packages/common', commonpack.source), - [siwepack.name]: path.join(__dirname, '../../packages/siwe', siwepack.source) + [siwepack.name]: path.join(__dirname, '../../packages/siwe', siwepack.source), + [appkitpack.name]: path.join(__dirname, '../../packages/appkit', appkitpack.source) } } ] diff --git a/package.json b/package.json index e2a1873b..54fba8f5 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "packages/ethers", "packages/solana", "packages/bitcoin", + "packages/wagmi", "apps/*" ], "scripts": { diff --git a/packages/appkit/package.json b/packages/appkit/package.json index 9dd8f22a..516a1ce4 100644 --- a/packages/appkit/package.json +++ b/packages/appkit/package.json @@ -41,7 +41,7 @@ "@reown/appkit-core-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", "@reown/appkit-ui-react-native": "1.2.3", - "@walletconnect/universal-provider": "2.19.2", + "@walletconnect/universal-provider": "2.20.2", "valtio": "^1.13.2" }, "peerDependencies": { diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 486b1fda..0727d270 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -59,7 +59,8 @@ export class WalletConnectConnector extends WalletConnector { this.provider.on('display_uri', onUri); - const session = await this.provider.connect({ + const session = await (this.provider as IUniversalProvider).connect({ + namespaces: {}, optionalNamespaces: opts.namespaces }); diff --git a/packages/appkit/src/utils/HelpersUtil.ts b/packages/appkit/src/utils/HelpersUtil.ts index 3fe3489c..33bd4db5 100644 --- a/packages/appkit/src/utils/HelpersUtil.ts +++ b/packages/appkit/src/utils/HelpersUtil.ts @@ -1,6 +1,11 @@ import type { Namespace, NamespaceConfig } from '@walletconnect/universal-provider'; -import type { AppKitNetwork, ChainNamespace } from '@reown/appkit-common-react-native'; +import type { + AppKitNetwork, + CaipNetworkId, + ChainNamespace +} from '@reown/appkit-common-react-native'; +import { solana, solanaDevnet } from '../networks/solana'; // import { EnsController, type OptionsControllerState } from '@reown/appkit-controllers' // import { solana, solanaDevnet } from '../networks/index.js' @@ -62,7 +67,7 @@ export const WcHelpersUtil = { applyNamespaceOverrides( baseNamespaces: NamespaceConfig, - overrides?: any //TODO: fix this + overrides?: any //TODO: add OptionsControllerState['universalProviderConfigOverride'] ): NamespaceConfig { if (!overrides) { return { ...baseNamespaces }; @@ -169,26 +174,28 @@ export const WcHelpersUtil = { acc[chainNamespace] = this.createDefaultNamespace(chainNamespace); } - const caipNetworkId = `${chainNamespace}:${id}`; + const caipNetworkId: CaipNetworkId = `${chainNamespace}:${id}`; const namespace = acc[chainNamespace]; - //@ts-ignore - namespace.chains.push(caipNetworkId); - - // Workaround for wallets that only support deprecated Solana network ID - // switch (caipNetworkId) { - // case solana.caipNetworkId: - // namespace.chains.push(solana.deprecatedCaipNetworkId) - // break - // case solanaDevnet.caipNetworkId: - // namespace.chains.push(solanaDevnet.deprecatedCaipNetworkId) - // break - // default: - // } - - if (namespace?.rpcMap && rpcUrl) { - namespace.rpcMap[id] = rpcUrl; + if (namespace) { + //@ts-ignore + namespace.chains.push(caipNetworkId); + + // Workaround for wallets that only support deprecated Solana network ID + switch (caipNetworkId) { + case solana.caipNetworkId: + namespace.chains.push(solana.deprecatedCaipNetworkId as string); + break; + case solanaDevnet.caipNetworkId: + namespace.chains.push(solanaDevnet.deprecatedCaipNetworkId as string); + break; + default: + } + + if (namespace?.rpcMap && rpcUrl) { + namespace.rpcMap[id] = rpcUrl; + } } return acc; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 8ec994d1..daf1c277 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -159,6 +159,7 @@ export abstract class BlockchainAdapter extends EventEmitter { setConnector(connector: WalletConnector) { this.connector = connector; + this.subscribeToEvents(); } removeConnector() { @@ -171,6 +172,45 @@ export abstract class BlockchainAdapter extends EventEmitter { return this.connector.getProvider(); } + onChainChanged(chainId: string): void { + this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + } + + onAccountsChanged(accounts: string[]): void { + const _accounts = this.getAccounts(); + const shouldEmit = _accounts?.some(account => { + const accountAddress = account.split(':')[2]; + + return accountAddress !== undefined && accounts.includes(accountAddress); + }); + + if (shouldEmit) { + this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + } + } + + onDisconnect(): void { + this.emit('disconnect', { namespace: this.getSupportedNamespace() }); + + const provider = this.connector?.getProvider(); + if (provider) { + provider.off('chainChanged', this.onChainChanged.bind(this)); + provider.off('accountsChanged', this.onAccountsChanged.bind(this)); + provider.off('disconnect', this.onDisconnect.bind(this)); + } + + this.connector = undefined; + } + + subscribeToEvents(): void { + const provider = this.connector?.getProvider(); + if (!provider) return; + + provider.on('chainChanged', this.onChainChanged.bind(this)); + provider.on('accountsChanged', this.onAccountsChanged.bind(this)); + provider.on('disconnect', this.onDisconnect.bind(this)); + } + abstract disconnect(): Promise; abstract request(method: string, params?: any[]): Promise; abstract getSupportedNamespace(): ChainNamespace; diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index 652e59ff..5635649a 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -41,7 +41,6 @@ "dependencies": { "@reown/appkit-common-react-native": "1.2.3", "@reown/appkit-react-native": "1.2.3", - "@reown/appkit-scaffold-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3" }, diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index 9da99703..18782d00 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -10,21 +10,29 @@ import { import { type Config, type CreateConfigParameters, - type CreateConnectorFn, - createConfig + createConfig, + getBalance as getBalanceWagmi, + switchChain as switchChainWagmi, + disconnect as disconnectWagmiCore, + connect as connectWagmi, + type Connector } from '@wagmi/core'; import type { Chain } from 'wagmi/chains'; import { getTransport } from './utils/helpers'; +import { formatUnits, type Hex } from 'viem'; +import { WalletConnectConnector } from './connectors/WalletConnectConnector'; type ConfigParams = Partial & { - networks: [Chain, ...Chain[]]; + networks: [Chain, ...Chain[]]; // Use Wagmi's Chain type projectId: string; + connectors?: Connector[]; }; export class WagmiAdapter extends EVMAdapter { private static supportedNamespace: ChainNamespace = 'eip155'; - public wagmiChains: readonly [Chain, ...Chain[]] | undefined; + public wagmiChains: readonly Chain[] | undefined; // Use Wagmi's Chain type public wagmiConfig!: Config; + private appKitWagmiConnector?: Connector; // Store the created connector instance constructor(configParams: ConfigParams) { super({ @@ -32,58 +40,114 @@ export class WagmiAdapter extends EVMAdapter { supportedNamespace: WagmiAdapter.supportedNamespace }); this.wagmiChains = configParams.networks; - this.wagmiConfig = this.createConfig(configParams); + this.wagmiConfig = this.createWagmiInternalConfig(configParams); } - private createConfig(configParams: ConfigParams) { - const connectors: CreateConnectorFn[] = []; + private createWagmiInternalConfig(configParams: ConfigParams): Config { + // Connectors are typically added via wagmiConfig.connectors, but here AppKit manages the connection. + // We'll use the `connect` action with our dynamically created connector instance. + // So, the `connectors` array for createConfig can be empty and is added later. + const initialConnectors: (() => Connector)[] = []; const transportsArr = configParams.networks.map(chain => [ chain.id, getTransport({ chainId: chain.id, projectId: configParams.projectId }) ]); - const transports = Object.fromEntries(transportsArr); - // const storage = createStorage({ storage: StorageUtil }); - return createConfig({ chains: configParams.networks, - connectors, + connectors: initialConnectors, // Empty, as we connect programmatically transports, - // storage, multiInjectedProviderDiscovery: false - // ...wagmiConfig }); } async switchNetwork(network: AppKitNetwork): Promise { - console.log('WagmiAdapter - switchNetwork', network); - throw new Error('Method not implemented.'); + if (!this.appKitWagmiConnector) { + throw new Error('WagmiAdapter: AppKit connector not set or not connected via Wagmi.'); + } + + await switchChainWagmi(this.wagmiConfig, { chainId: network.id as number }); } async getBalance(params: GetBalanceParams): Promise { - if (!this.connector) throw new Error('No active connector'); - const address = params.address || this.getAccounts()?.[0]; + const { network, address } = params; + + if (!this.connector) throw new Error('No active AppKit connector (EVMAdapter.connector)'); + if (!network) throw new Error('No network provided'); + + if (!this.appKitWagmiConnector) { + // Ensure our Wagmi connector wrapper is also active + throw new Error('WagmiAdapter: AppKit connector not properly configured with Wagmi.'); + } + + const balanceAddress = + address || + this.getAccounts()?.find((acc: CaipAddress) => acc.includes(network.id.toString())); + + if (!balanceAddress) { + return Promise.resolve({ amount: '0.00', symbol: network.nativeCurrency.symbol || 'ETH' }); + } + + const accountHex = balanceAddress.split(':')[2] as Hex; - console.log('WagmiAdapter - getBalance', address); + const token = + network?.caipNetworkId && (params.tokens?.[network.caipNetworkId]?.address as Hex); - return Promise.resolve({ amount: '0.00', symbol: 'ETH' }); + const balance = await getBalanceWagmi(this.wagmiConfig, { + address: accountHex, + chainId: network.id as number, + token + }); + + const formattedBalance = { + amount: formatUnits(balance.value, balance.decimals), + symbol: balance.symbol, + contractAddress: token ? (`${network.caipNetworkId}:${token}` as CaipAddress) : undefined + }; + + this.emit('balanceChanged', { + namespace: this.getSupportedNamespace(), + address: balanceAddress, + balance: formattedBalance + }); + + return Promise.resolve(formattedBalance); } getAccounts(): CaipAddress[] | undefined { - if (!this.connector) throw new Error('No active connector'); + if (!this.connector) { + return undefined; + } + const namespaces = this.connector.getNamespaces(); + if (!namespaces) { + return undefined; + } + + const supportedNamespaceKey = this.getSupportedNamespace(); + const accountsForNamespace = namespaces[supportedNamespaceKey]; - return namespaces[this.getSupportedNamespace()]?.accounts; + return accountsForNamespace?.accounts; } - disconnect(): Promise { - throw new Error('Method not implemented.'); + async disconnect(): Promise { + if (this.appKitWagmiConnector) { + await disconnectWagmiCore(this.wagmiConfig, { connector: this.appKitWagmiConnector }); + this.appKitWagmiConnector = undefined; + } else if (this.connector) { + await this.connector.disconnect(); + } + + const evmAdapterInstance = this as any; + if ('connector' in evmAdapterInstance) { + evmAdapterInstance.connector = undefined; + } } async request(method: string, params?: any[]) { - if (!this.connector) throw new Error('No active connector'); + if (!this.connector) throw new Error('WagmiAdapter: No active AppKit connector'); const provider = this.connector.getProvider(); return provider.request({ method, params }); @@ -93,8 +157,25 @@ export class WagmiAdapter extends EVMAdapter { return WagmiAdapter.supportedNamespace; } - override setConnector(connector: WalletConnector): void { - super.setConnector(connector); - // this.wagmiConfig.connectors = [connector]; + override setConnector(newAppKitConnector: WalletConnector): void { + super.setConnector(newAppKitConnector); + + if (newAppKitConnector && this.wagmiChains) { + if (!this.appKitWagmiConnector) { + // Manually add the connector to the wagmiConfig + const connector = this.wagmiConfig._internal.connectors.setup( + WalletConnectConnector(newAppKitConnector) + ); + this.wagmiConfig._internal.connectors.setState(prev => [...prev, connector]); + + this.appKitWagmiConnector = connector as unknown as Connector; + + try { + connectWagmi(this.wagmiConfig, { connector }); + } catch (error) { + this.appKitWagmiConnector = undefined; // Clear if connection fails + } + } + } } } diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts deleted file mode 100644 index 85730a94..00000000 --- a/packages/wagmi/src/client.ts +++ /dev/null @@ -1,644 +0,0 @@ -import { formatUnits, type Hex, parseUnits } from 'viem'; -import { - type GetAccountReturnType, - type GetEnsAddressReturnType, - type Connector as WagmiConnector, - connect, - reconnect, - disconnect, - signMessage, - getAccount, - switchChain, - watchAccount, - watchConnectors, - getEnsName, - getEnsAvatar as wagmiGetEnsAvatar, - getEnsAddress as wagmiGetEnsAddress, - getBalance, - prepareTransactionRequest, - estimateGas as wagmiEstimateGas, - sendTransaction as wagmiSendTransaction, - waitForTransactionReceipt, - writeContract as wagmiWriteContract -} from '@wagmi/core'; -import { normalize } from 'viem/ens'; -import { mainnet, type Chain } from '@wagmi/core/chains'; -import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; -import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; -import { - type ConnectionControllerClient, - type Connector, - type LibraryOptions, - type NetworkControllerClient, - type PublicStateControllerState, - type SendTransactionArgs, - AppKitScaffold, - type WriteContractArgs, - type AppKitFrameProvider, - type EstimateGasTransactionArgs -} from '@reown/appkit-scaffold-react-native'; -import { HelpersUtil, StorageUtil } from '@reown/appkit-scaffold-utils-react-native'; -import { - NetworkUtil, - NamesUtil, - ErrorUtil, - ConstantsUtil, - PresetsUtil, - type ConnectorType, - type CaipAddress, - type CaipNetwork, - type CaipNetworkId - type Token, -} from '@reown/appkit-common-react-native'; -import { - SIWEController, - getDidChainId, - getDidAddress, - type AppKitSIWEClient -} from '@reown/appkit-siwe-react-native'; -import { - getCaipDefaultChain, - getAuthCaipNetworks, - getWalletConnectCaipNetworks, - requireCaipAddress -} from './utils/helpers'; -import { defaultWagmiConfig } from './utils/defaultWagmiConfig'; - -// -- Types --------------------------------------------------------------------- -type WagmiConfig = ReturnType; - -export interface AppKitClientOptions extends Omit { - wagmiConfig: WagmiConfig; - siweConfig?: AppKitSIWEClient; - defaultChain?: Chain; - chainImages?: Record; - connectorImages?: Record; - tokens?: Record; -} - -export type AppKitOptions = Omit; - -// @ts-expect-error: Overriden state type is correct -interface AppKitState extends PublicStateControllerState { - selectedNetworkId: number | undefined; -} - -// -- Client -------------------------------------------------------------------- -export class AppKit extends AppKitScaffold { - private hasSyncedConnectedAccount = false; - - private options: AppKitClientOptions | undefined = undefined; - - private wagmiConfig: WagmiConfig; - - public constructor(options: AppKitClientOptions) { - const { wagmiConfig, siweConfig, defaultChain, tokens, _sdkVersion, ...appKitOptions } = - options; - - if (!wagmiConfig) { - throw new Error('appkit:constructor - wagmiConfig is undefined'); - } - - if (!appKitOptions.projectId) { - throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); - } - - const networkControllerClient: NetworkControllerClient = { - switchCaipNetwork: async caipNetwork => { - const chainId = NetworkUtil.caipNetworkIdToNumber(caipNetwork?.id); - if (chainId) { - await switchChain(wagmiConfig, { chainId }); - } - }, - - async getApprovedCaipNetworksData() { - const walletChoice = await StorageUtil.getConnectedConnector(); - const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; - - if (walletChoice?.includes(walletConnectType)) { - const connector = wagmiConfig.connectors.find( - c => c.id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID - ); - - return getWalletConnectCaipNetworks(connector); - } else if (authType) { - return getAuthCaipNetworks(); - } - - return { approvedCaipNetworkIds: undefined, supportsAllNetworks: true }; - } - }; - - const connectionControllerClient: ConnectionControllerClient = { - connectWalletConnect: async (onUri, walletUniversalLink) => { - const connector = wagmiConfig.connectors.find( - c => c.id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID - ); - if (!connector) { - throw new Error( - 'connectionControllerClient:getWalletConnectUri - connector is undefined' - ); - } - - const provider = (await connector.getProvider()) as Awaited< - ReturnType<(typeof EthereumProvider)['init']> - >; - - provider.on('display_uri', data => { - onUri(data); - }); - - // When connecting through walletconnect, we need to set the clientId in the store - const clientId = await provider.signer?.client?.core?.crypto?.getClientId(); - if (clientId) { - this.setClientId(clientId); - } - - const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); - - // SIWE - const siweParams = await siweConfig?.getMessageParams?.(); - // Make sure client uses ethereum provider version that supports `authenticate` - if ( - siweConfig?.options?.enabled && - typeof provider?.authenticate === 'function' && - siweParams && - Object.keys(siweParams || {}).length > 0 - ) { - // @ts-expect-error - setting requested chains beforehand avoids wagmi auto disconnecting the session when `connect` is called because it things chains are stale - await connector.setRequestedChainsIds(siweParams.chains); - const result = await provider.authenticate( - { - nonce: await siweConfig.getNonce(), - methods: [...OPTIONAL_METHODS], - ...siweParams - }, - walletUniversalLink - ); - - // Auths is an array of signed CACAO objects https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-74.md - const signedCacao = result?.auths?.[0]; - if (signedCacao) { - const { p, s } = signedCacao; - const cacaoChainId = getDidChainId(p.iss) || ''; - const address = getDidAddress(p.iss); - try { - // Kicks off verifyMessage and populates external states - const message = provider.signer.client.formatAuthMessage({ - request: p, - iss: p.iss - }); - - await SIWEController.verifyMessage({ - message, - signature: s.s, - cacao: signedCacao - }); - - if (address && chainId) { - const session = { - address, - chainId: parseInt(cacaoChainId, 10) - }; - - SIWEController.setSession(session); - SIWEController.onSignIn?.(session); - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Error verifying message', error); - // eslint-disable-next-line no-console - await provider.disconnect().catch(console.error); - // eslint-disable-next-line no-console - await SIWEController.signOut().catch(console.error); - throw error; - } - /* - * Unassign the connector from the wagmiConfig and allow connect() to reassign it in the next step - * this avoids case where wagmi throws because the connector is already connected - * what we need connect() to do is to only setup internal event listeners - */ - this.wagmiConfig.state.current = ''; - } - } - - await connect(this.wagmiConfig, { connector, chainId }); - }, - - connectExternal: async ({ id }) => { - const connector = wagmiConfig.connectors.find(c => c.id === id); - if (!connector) { - throw new Error('connectionControllerClient:connectExternal - connector is undefined'); - } - - // If connecting with something else than walletconnect, we need to clear the clientId in the store - this.setClientId(null); - - const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); - await connect(this.wagmiConfig, { connector, chainId }); - }, - - signMessage: async message => signMessage(this.wagmiConfig, { message }), - - disconnect: async () => { - await disconnect(this.wagmiConfig); - this.setClientId(null); - - if (siweConfig?.options?.signOutOnDisconnect) { - await SIWEController.signOut(); - } - }, - - sendTransaction: async (data: SendTransactionArgs) => { - const { chainId } = getAccount(this.wagmiConfig); - - const txParams = { - account: data.address, - to: data.to, - value: data.value, - gas: data.gas, - gasPrice: data.gasPrice, - data: data.data, - chainId, - type: 'legacy' as const - }; - - await prepareTransactionRequest(this.wagmiConfig, txParams); - const tx = await wagmiSendTransaction(this.wagmiConfig, txParams); - - await waitForTransactionReceipt(this.wagmiConfig, { hash: tx, timeout: 25000 }); - - return tx; - }, - - writeContract: async (data: WriteContractArgs) => { - const caipAddress = this.getCaipAddress() || ''; - const account = requireCaipAddress(caipAddress); - const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); - - const tx = await wagmiWriteContract(wagmiConfig, { - chainId, - address: data.tokenAddress, - account, - abi: data.abi, - functionName: data.method, - args: [data.receiverAddress, data.tokenAmount] - }); - - return tx; - }, - - estimateGas: async ({ - address, - to, - data, - chainNamespace - }: EstimateGasTransactionArgs): Promise => { - if (chainNamespace && chainNamespace !== 'eip155') { - throw new Error('estimateGas - chainNamespace is not eip155'); - } - - try { - const result = await wagmiEstimateGas(this.wagmiConfig, { - account: address as Hex, - to: to as Hex, - data: data as Hex, - type: 'legacy' - }); - - return result; - } catch (error) { - throw new Error('WagmiAdapter:estimateGas - error estimating gas'); - } - }, - - parseUnits, - - formatUnits, - - getEnsAddress: async (value: string) => { - try { - if (!this.wagmiConfig) { - throw new Error( - 'networkControllerClient:getApprovedCaipNetworksData - wagmiConfig is undefined' - ); - } - const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); - let ensName: boolean | GetEnsAddressReturnType = false; - let wcName: boolean | string = false; - if (NamesUtil.isReownName(value)) { - wcName = (await this.resolveReownName(value)) || false; - } - if (chainId === 1) { - ensName = await wagmiGetEnsAddress(this.wagmiConfig, { - name: normalize(value), - chainId - }); - } - - return ensName || wcName || false; - } catch { - return false; - } - }, - getEnsAvatar: async (value: string) => { - const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id)); - if (chainId !== mainnet.id) { - return false; - } - const avatar = await wagmiGetEnsAvatar(this.wagmiConfig, { - name: normalize(value), - chainId - }); - - return avatar || false; - } - }; - - super({ - networkControllerClient, - connectionControllerClient, - siweControllerClient: siweConfig, - defaultChain: getCaipDefaultChain(defaultChain), - tokens: HelpersUtil.getCaipTokens(tokens), - _sdkVersion: _sdkVersion ?? `react-native-wagmi-${ConstantsUtil.VERSION}`, - ...appKitOptions - }); - - this.options = options; - this.wagmiConfig = wagmiConfig; - - this.syncRequestedNetworks([...wagmiConfig.chains]); - this.syncConnectors([...wagmiConfig.connectors]); - - watchConnectors(wagmiConfig, { - onChange: connectors => this.syncConnectors([...connectors]) - }); - - watchAccount(wagmiConfig, { - onChange: (accountData, prevAccountData) => { - this.syncAccount({ ...accountData }); - - if (accountData.status === 'disconnected' && prevAccountData.status === 'connected') { - this.close(); - } - } - }); - } - - // -- Public ------------------------------------------------------------------ - - // @ts-expect-error: Overriden state type is correct - public override getState() { - const state = super.getState(); - - return { - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }; - } - - // @ts-expect-error: Overriden state type is correct - public override subscribeState(callback: (state: AppKitState) => void) { - return super.subscribeState(state => - callback({ - ...state, - selectedNetworkId: NetworkUtil.caipNetworkIdToNumber(state.selectedNetworkId) - }) - ); - } - - // -- Private ----------------------------------------------------------------- - private syncRequestedNetworks(chains: Chain[]) { - const requestedCaipNetworks = chains?.map( - chain => - ({ - id: `${ConstantsUtil.EIP155}:${chain.id}`, - name: chain.name, - imageId: PresetsUtil.NetworkImageIds[chain.id], - imageUrl: this.options?.chainImages?.[chain.id] - }) as CaipNetwork - ); - this.setRequestedCaipNetworks(requestedCaipNetworks ?? []); - } - - private async syncAccount({ - address, - isConnected, - chainId, - connector, - isConnecting, - isReconnecting - }: Pick< - GetAccountReturnType, - 'address' | 'isConnected' | 'chainId' | 'connector' | 'isConnecting' | 'isReconnecting' - >) { - this.syncNetwork(address, chainId, isConnected); - this.setLoading(!!connector && (isConnecting || isReconnecting)); - - if (isConnected && address && chainId) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; - this.setIsConnected(isConnected); - this.setCaipAddress(caipAddress); - await Promise.all([ - this.syncProfile(address, chainId), - this.syncBalance(address, chainId), - this.syncConnectedWalletInfo(connector), - this.getApprovedCaipNetworksData() - ]); - this.hasSyncedConnectedAccount = true; - } else if (!isConnected && !isConnecting && !isReconnecting && this.hasSyncedConnectedAccount) { - this.resetAccount(); - this.resetWcConnection(); - this.resetNetwork(); - } - } - - private async syncNetwork(address?: Hex, chainId?: number, isConnected?: boolean) { - const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId); - - if (chain || chainId) { - const name = chain?.name ?? chainId?.toString(); - const id = Number(chain?.id ?? chainId); - const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${id}`; - this.setCaipNetwork({ - id: caipChainId, - name, - imageId: PresetsUtil.NetworkImageIds[id], - imageUrl: this.options?.chainImages?.[id] - }); - if (isConnected && address && chainId) { - const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${id}:${address}`; - this.setCaipAddress(caipAddress); - if (chain?.blockExplorers?.default?.url) { - const url = `${chain.blockExplorers.default.url}/address/${address}`; - this.setAddressExplorerUrl(url); - } else { - this.setAddressExplorerUrl(undefined); - } - if (this.hasSyncedConnectedAccount) { - await this.syncProfile(address, chainId); - await this.syncBalance(address, chainId); - } - } - } - } - - private async syncProfile(address: Hex, chainId: number) { - try { - const response = await this.fetchIdentity({ address }); - - if (!response) { - throw new Error('Couldnt fetch idendity'); - } - - this.setProfileName(response.name); - this.setProfileImage(response.avatar); - } catch { - if (chainId === mainnet.id) { - const profileName = await getEnsName(this.wagmiConfig, { address, chainId }); - if (profileName) { - this.setProfileName(profileName); - const profileImage = await wagmiGetEnsAvatar(this.wagmiConfig, { - name: profileName, - chainId - }); - if (profileImage) { - this.setProfileImage(profileImage); - } - } - } else { - this.setProfileName(undefined); - this.setProfileImage(undefined); - } - } - } - - private async syncBalance(address: Hex, chainId: number) { - const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId); - try { - if (chain) { - const balance = await getBalance(this.wagmiConfig, { - address, - chainId: chain.id, - token: this.options?.tokens?.[chainId]?.address as Hex - }); - const formattedBalance = formatUnits(balance.value, balance.decimals); - this.setBalance(formattedBalance, balance.symbol); - - return; - } - this.setBalance(undefined, undefined); - } catch { - this.setBalance(undefined, undefined); - } - } - - private async syncConnectedWalletInfo(connector: GetAccountReturnType['connector']) { - if (!connector) { - throw Error('syncConnectedWalletInfo - connector is undefined'); - } - - if (connector.id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID && connector.getProvider) { - const walletConnectProvider = (await connector.getProvider()) as Awaited< - ReturnType<(typeof EthereumProvider)['init']> - >; - if (walletConnectProvider.session) { - this.setConnectedWalletInfo({ - ...walletConnectProvider.session.peer.metadata, - name: walletConnectProvider.session.peer.metadata.name, - icon: walletConnectProvider.session.peer.metadata.icons?.[0] - }); - } - } else { - this.setConnectedWalletInfo({ - id: connector.id, - name: connector.name, - icon: this.options?.connectorImages?.[connector.id] ?? connector.icon - }); - } - } - - private syncConnectors(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { - const uniqueIds = new Set(); - const filteredConnectors = connectors.filter( - item => !uniqueIds.has(item.id) && uniqueIds.add(item.id) - ); - - const excludedConnectors = [ConstantsUtil.AUTH_CONNECTOR_ID]; - - const _connectors: Connector[] = []; - filteredConnectors.forEach(({ id, name, icon }) => { - if (!excludedConnectors.includes(id)) { - _connectors.push({ - id, - explorerId: PresetsUtil.ConnectorExplorerIds[id], - imageId: PresetsUtil.ConnectorImageIds[id] ?? icon, - imageUrl: this.options?.connectorImages?.[id], - name: PresetsUtil.ConnectorNamesMap[id] ?? name, - type: PresetsUtil.ConnectorTypesMap[id] ?? 'EXTERNAL' - }); - } - }); - - this.setConnectors(_connectors); - this.syncWalletConnectListeners(filteredConnectors); - this.syncAuthConnector(filteredConnectors); - } - - private async syncWalletConnectListeners( - connectors: AppKitClientOptions['wagmiConfig']['connectors'] - ) { - const connector = connectors.find(({ id }) => id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID); - if (connector) { - const provider = (await connector.getProvider()) as EthereumProvider; - - provider.signer.client.core.relayer.on('relayer_connect', () => { - provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { - if (payload?.error) { - this.handleAlertError(payload?.error.message); - } - }); - }); - } - } - - private async syncAuthConnector(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { - const authConnector = connectors.find(({ id }) => id === ConstantsUtil.AUTH_CONNECTOR_ID); - if (authConnector) { - const provider = await authConnector.getProvider(); - this.addConnector({ - id: ConstantsUtil.AUTH_CONNECTOR_ID, - type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!, - name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.AUTH_CONNECTOR_ID], - provider - }); - this.addAuthListeners(authConnector); - } - } - - private async addAuthListeners(connector: WagmiConnector) { - const connectedConnector: ConnectorType | undefined = await StorageUtil.getItem( - '@w3m/connected_connector' - ); - - if (connectedConnector === 'AUTH') { - // Set loader until it reconnects - super.setLoading(true); - } - - const provider = (await connector.getProvider()) as AppKitFrameProvider; - - provider.onSetPreferredAccount(async () => { - await reconnect(this.wagmiConfig, { connectors: [connector] }); - }); - - provider.setOnTimeout(async () => { - this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); - this.setLoading(false); - }); - } -} diff --git a/packages/wagmi/src/connectors/WalletConnectConnector.ts b/packages/wagmi/src/connectors/WalletConnectConnector.ts index 4b732c1d..3c2dd624 100644 --- a/packages/wagmi/src/connectors/WalletConnectConnector.ts +++ b/packages/wagmi/src/connectors/WalletConnectConnector.ts @@ -1,472 +1,222 @@ -import { type EthereumProviderOptions } from '@walletconnect/ethereum-provider/dist/types/EthereumProvider'; +import type { Provider, WalletConnector } from '@reown/appkit-common-react-native'; import { - type Address, - type ProviderConnectInfo, - ProviderRpcError, - SwitchChainError, - UserRejectedRequestError, getAddress, numberToHex, - RpcError, - type AddEthereumChainParameter + SwitchChainError, + UserRejectedRequestError, + type Chain, + type Hex } from 'viem'; +import { ChainNotConfiguredError, createConnector, ProviderNotFoundError } from 'wagmi'; -import { - ChainNotConfiguredError, - ProviderNotFoundError, - createConnector, - type Connector -} from '@wagmi/core'; - -import { EthereumProvider } from '@walletconnect/ethereum-provider'; - -/**** Types ****/ - -type IWalletConnectConnector = Connector & { - onDisplayUri(uri: string): void; - onSessionDelete(data: { topic: string }): void; -}; - -export type WalletConnectParameters = { - /** - * Reown Cloud Project ID. - * @link https://cloud.reown.com/sign-in. - */ - projectId: EthereumProviderOptions['projectId']; - /** - * If a new chain is added to a previously existing configured connector `chains`, this flag - * will determine if that chain should be considered as stale. A stale chain is a chain that - * WalletConnect has yet to establish a relationship with (e.g. the user has not approved or - * rejected the chain). - * - * This flag mainly affects the behavior when a wallet does not support dynamic chain authorization - * with WalletConnect v2. - * - * If `true` (default), the new chain will be treated as a stale chain. If the user - * has yet to establish a relationship (approved/rejected) with this chain in their WalletConnect - * session, the connector will disconnect upon the dapp auto-connecting, and the user will have to - * reconnect to the dapp (revalidate the chain) in order to approve the newly added chain. - * This is the default behavior to avoid an unexpected error upon switching chains which may - * be a confusing user experience (e.g. the user will not know they have to reconnect - * unless the dapp handles these types of errors). - * - * If `false`, the new chain will be treated as a potentially valid chain. This means that if the user - * has yet to establish a relationship with the chain in their WalletConnect session, wagmi will successfully - * auto-connect the user. This comes with the trade-off that the connector will throw an error - * when attempting to switch to the unapproved chain if the wallet does not support dynamic session updates. - * This may be useful in cases where a dapp constantly - * modifies their configured chains, and they do not want to disconnect the user upon - * auto-connecting. If the user decides to switch to the unapproved chain, it is important that the - * dapp handles this error and prompts the user to reconnect to the dapp in order to approve - * the newly added chain. - * - * @default true - */ - isNewChainsStale?: boolean; - /** - * Metadata for your app. - * @link https://docs.reown.com/appkit/react-native/core/installation#implementation - */ - metadata: EthereumProviderOptions['metadata']; -} & Omit< - EthereumProviderOptions, - | 'chains' - | 'events' - | 'optionalChains' - | 'optionalEvents' - | 'optionalMethods' - | 'methods' - | 'rpcMap' - | 'showQrModal' - | 'qrModalOptions' - | 'storageOptions' ->; - -type Provider = Awaited>; - -type NamespaceMethods = 'wallet_addEthereumChain' | 'wallet_switchEthereumChain'; - -type Properties = { - connect(parameters?: { chainId?: number; pairingTopic?: string }): Promise<{ - accounts: readonly Address[]; - chainId: number; - }>; - getNamespaceChainsIds(): number[]; - getNamespaceMethods(): NamespaceMethods[]; - getRequestedChainsIds(): Promise; - isChainsStale(): Promise; - onConnect(connectInfo: ProviderConnectInfo): void; - onDisplayUri(uri: string): void; - onSessionDelete(data: { topic: string }): void; - setRequestedChainsIds(chains: number[]): void; - requestedChainsStorageKey: `${string}.requestedChains`; -}; +export function WalletConnectConnector(appKitProvidedConnector: WalletConnector) { + let provider: Provider | undefined; -type StorageItem = { - [_ in Properties['requestedChainsStorageKey']]: number[]; -}; + let accountsChangedHandler: ((accounts: string[]) => void) | undefined; + let chainChangedHandler: ((chainId: string | number) => void) | undefined; + let disconnectHandler: ((error?: Error) => void) | undefined; -walletConnect.type = 'walletConnect' as const; -export function walletConnect(parameters: WalletConnectParameters) { - const isNewChainsStale = parameters.isNewChainsStale ?? true; + type AppKitConnectorProperties = { ready: boolean }; - let provider_: Provider | undefined; - let providerPromise: Promise; - const NAMESPACE = 'eip155'; - - let accountsChanged: IWalletConnectConnector['onAccountsChanged'] | undefined; - let chainChanged: IWalletConnectConnector['onChainChanged'] | undefined; - let connect: IWalletConnectConnector['onConnect'] | undefined; - let displayUri: IWalletConnectConnector['onDisplayUri'] | undefined; - let sessionDelete: IWalletConnectConnector['onSessionDelete'] | undefined; - let disconnect: IWalletConnectConnector['onDisconnect'] | undefined; - // let genericConnector: WalletConnectConnector | undefined; - - return createConnector(config => ({ - id: 'walletConnect', + return createConnector(config => ({ + id: 'walletconnect', name: 'WalletConnect', - type: walletConnect.type, + type: 'walletconnect' as const, + ready: !!appKitProvidedConnector.getProvider(), + async setup() { - // genericConnector = WalletConnectConnector.create({ projectId: parameters.projectId, metadata: parameters.metadata }); - const provider = await this.getProvider().catch(() => null); - if (!provider) return; - if (!connect) { - connect = this.onConnect.bind(this); - provider.on('connect', connect); - } - if (!sessionDelete) { - sessionDelete = this.onSessionDelete.bind(this); - provider.on('session_delete', sessionDelete); + provider = appKitProvidedConnector.getProvider(); + if (provider?.on) { + accountsChangedHandler = (accounts: string[]) => { + const hexAccounts = accounts.map(acc => getAddress(acc)); + config.emitter.emit('change', { accounts: hexAccounts }); + if (hexAccounts.length === 0) { + config.emitter.emit('disconnect'); + } + }; + chainChangedHandler = (chainId: string | number) => { + const newChainId = typeof chainId === 'string' ? parseInt(chainId, 10) : chainId; + config.emitter.emit('change', { chainId: newChainId }); + }; + disconnectHandler = (error?: Error) => { + config.emitter.emit('disconnect'); + if (error) config.emitter.emit('error', { error }); + }; + + if (accountsChangedHandler) provider.on('accountsChanged', accountsChangedHandler); + if (chainChangedHandler) provider.on('chainChanged', chainChangedHandler); + if (disconnectHandler) provider.on('disconnect', disconnectHandler); + if (disconnectHandler) provider.on('session_delete', disconnectHandler); } }, - async connect({ chainId, ...rest } = {}) { - try { - const provider = await this.getProvider(); - if (!provider) throw new ProviderNotFoundError(); - if (!displayUri) { - displayUri = this.onDisplayUri; - provider.on('display_uri', displayUri); - } + async connect({ chainId } = {}) { + try { + const _provider = await this.getProvider(); + if (!_provider) throw new ProviderNotFoundError(); - let targetChainId = chainId; - if (!targetChainId) { - const state = (await config.storage?.getItem('state')) ?? {}; - const isChainSupported = config.chains.some(x => x.id === state.chainId); - if (isChainSupported) targetChainId = state.chainId; - else targetChainId = config.chains[0]?.id; + // AppKit connector is already connected or handles its own connection. + // We just need to sync its state with Wagmi. + const accountAddresses = await this.getAccounts(); + if (!accountAddresses || accountAddresses.length === 0) { + throw new UserRejectedRequestError( + new Error('No accounts found or user rejected connection via AppKit.') + ); } - if (!targetChainId) throw new Error('No chains found on connector.'); - - const isChainsStale = await this.isChainsStale(); - // If there is an active session with stale chains, disconnect current session. - if (provider.session && isChainsStale) await provider.disconnect(); - // If there isn't an active session or chains are stale, connect. - if (!provider.session || isChainsStale) { - const optionalChains = config.chains - .filter(chain => chain.id !== targetChainId) - .map(optionalChain => optionalChain.id); - await provider.connect({ - optionalChains: [targetChainId, ...optionalChains], - ...('pairingTopic' in rest ? { pairingTopic: rest.pairingTopic } : {}) - }); + let currentChainId = await this.getChainId(); - this.setRequestedChainsIds(config.chains.map(x => x.id)); + // Handle chain switching if requested and different + if (chainId && currentChainId !== chainId) { + await this.switchChain?.({ chainId }); + currentChainId = chainId; } - // If session exists and chains are authorized, enable provider for required chain - const accounts: Address[] = (await provider.enable()).map(getAddress); - const currentChainId = await this.getChainId(); - - if (displayUri) { - provider.removeListener('display_uri', displayUri); - displayUri = undefined; - } - if (connect) { - provider.removeListener('connect', connect); - connect = undefined; - } - if (!accountsChanged) { - accountsChanged = this.onAccountsChanged.bind(this); - provider.on('accountsChanged', accountsChanged); - } - if (!chainChanged) { - chainChanged = this.onChainChanged.bind(this); - provider.on('chainChanged', chainChanged); - } - if (!disconnect) { - disconnect = this.onDisconnect.bind(this); - provider.on('disconnect', disconnect); - } - if (!sessionDelete) { - sessionDelete = this.onSessionDelete.bind(this); - provider.on('session_delete', sessionDelete); - } + this.ready = true; - return { accounts, chainId: currentChainId }; + return { accounts: accountAddresses, chainId: currentChainId }; } catch (error) { - if ( - /(user rejected|connection request reset)/i.test((error as ProviderRpcError)?.message) - ) { - throw new UserRejectedRequestError(error as Error); - } - throw error; + if (error instanceof UserRejectedRequestError) throw error; + throw new UserRejectedRequestError(error as Error); // Generalize other errors as user rejection for simplicity } }, - async disconnect() { - const provider = await this.getProvider(); - try { - await provider?.disconnect(); - } catch (error) { - if (!/No matching key/i.test((error as Error).message)) throw error; - } finally { - if (chainChanged) { - provider?.removeListener('chainChanged', chainChanged); - chainChanged = undefined; - } - if (disconnect) { - provider?.removeListener('disconnect', disconnect); - disconnect = undefined; - } - if (!connect) { - connect = this.onConnect.bind(this); - provider?.on('connect', connect); - } - if (accountsChanged) { - provider?.removeListener('accountsChanged', accountsChanged); - accountsChanged = undefined; - } - if (sessionDelete) { - provider?.removeListener('session_delete', sessionDelete); - sessionDelete = undefined; - } - this.setRequestedChainsIds([]); + async disconnect() { + await provider?.disconnect(); + if (provider?.off && accountsChangedHandler && chainChangedHandler && disconnectHandler) { + provider.off('accountsChanged', accountsChangedHandler); + provider.off('chainChanged', chainChangedHandler); + provider.off('disconnect', disconnectHandler); + provider.off('session_delete', disconnectHandler); + accountsChangedHandler = undefined; + chainChangedHandler = undefined; + disconnectHandler = undefined; } + this.ready = false; }, + async getAccounts() { - const provider: Provider = await this.getProvider(); + const namespaces = appKitProvidedConnector.getNamespaces(); + // @ts-ignore + const eip155Accounts = namespaces?.eip155?.accounts; + if (!eip155Accounts) return [] as readonly Hex[]; - return provider.accounts.map(getAddress); + return eip155Accounts + .map((caipAddr: string) => { + const parts = caipAddr.split(':'); + + return parts.length === 3 ? parts[2] : null; + }) + .filter((addrPart): addrPart is string => !!addrPart) + .map((addrPart: string) => getAddress(addrPart)) as readonly Hex[]; }, - async getProvider({ chainId } = {}) { - async function initProvider() { - const optionalChains = config.chains.map(x => x.id) as [number]; - if (!optionalChains.length) return Promise.resolve(undefined); - const { projectId, metadata, ...params } = parameters; + async getChainId() { + const _provider = await this.getProvider(); + if (_provider) { + try { + const chainId = (await _provider.request({ + method: 'eth_chainId' + })) as string; - return await EthereumProvider.init({ - optionalChains, - projectId, - rpcMap: Object.fromEntries( - config.chains.map(chain => [chain.id, chain.rpcUrls.default.http[0]!]) - ), - showQrModal: false, - qrModalOptions: undefined, - disableProviderPing: true, - metadata, - ...params - }); + return parseInt(chainId, 10); + } catch (e) { + // console.warn("Could not get chainId from provider", e); + } } - if (!provider_) { - if (!providerPromise) providerPromise = initProvider(); - provider_ = await providerPromise; - provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY); + // Fallback: Try to get from CAIP accounts if available + const namespaces = appKitProvidedConnector.getNamespaces(); + // @ts-ignore + const eip155Accounts = namespaces?.eip155?.accounts; + if (eip155Accounts && eip155Accounts.length > 0) { + const parts = eip155Accounts[0]?.split(':'); + if (parts && parts.length > 1 && typeof parts[1] === 'string') { + const chainIdNum = parseInt(parts[1], 10); + if (!isNaN(chainIdNum)) { + return chainIdNum; + } + } } - if (chainId) await this.switchChain?.({ chainId }); - - return provider_!; + if (config.chains && config.chains.length > 0) return config.chains[0].id; + throw new Error('Unable to determine chainId.'); }, - async getChainId() { - const provider = await this.getProvider(); - return provider.chainId; + async getProvider() { + if (!provider) { + provider = appKitProvidedConnector.getProvider(); + } + + return Promise.resolve(provider); }, + async isAuthorized() { try { - const [accounts, provider] = await Promise.all([this.getAccounts(), this.getProvider()]); - - // If an account does not exist on the session, then the connector is unauthorized. - if (!accounts.length) return false; - - // If the chains are stale on the session, then the connector is unauthorized. - const isChainsStale = await this.isChainsStale(); - if (isChainsStale && provider.session) { - await provider.disconnect().catch(() => { }); + const accounts = await this.getAccounts(); - return false; - } - - return true; + return !!(accounts && accounts.length > 0); } catch { return false; } }, - async switchChain({ addEthereumChainParameter, chainId }) { - const provider = await this.getProvider(); - if (!provider) throw new ProviderNotFoundError(); - const chain = config.chains.find(c => c.id === chainId); - if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()); + async switchChain({ chainId }) { + const _provider = await this.getProvider(); + if (!_provider) throw new Error('Provider not available for switching chain.'); + const currentChainId = await this.getChainId(); + if (currentChainId === chainId) return config.chains.find(c => c.id === chainId) as Chain; try { - await Promise.all([ - new Promise(resolve => { - const listener = ({ chainId: currentChainId }: { chainId?: number }) => { - if (currentChainId === chainId) { - config.emitter.off('change', listener); - resolve(); - } - }; - config.emitter.on('change', listener); - }), - provider.request({ - method: 'wallet_switchEthereumChain', - params: [{ chainId: numberToHex(chainId) }] - }) - ]); - - const requestedChains = await this.getRequestedChainsIds(); - if (!requestedChains.includes(chainId)) { - this.setRequestedChainsIds([...requestedChains, chainId]); - } - - return chain; - } catch (err) { - const error = err as RpcError; - - if (/(user rejected)/i.test(error.message)) throw new UserRejectedRequestError(error); - - // Indicates chain is not added to provider - try { - let blockExplorerUrls: string[] | undefined; - if (addEthereumChainParameter?.blockExplorerUrls) - blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls; - else - blockExplorerUrls = chain.blockExplorers?.default.url - ? [chain.blockExplorers?.default.url] - : []; - - let rpcUrls: readonly string[]; - if (addEthereumChainParameter?.rpcUrls?.length) - rpcUrls = addEthereumChainParameter.rpcUrls; - else rpcUrls = [...chain.rpcUrls.default.http]; - - const addEthereumChain = { - blockExplorerUrls, - chainId: numberToHex(chainId), - chainName: addEthereumChainParameter?.chainName ?? chain.name, - iconUrls: addEthereumChainParameter?.iconUrls, - nativeCurrency: addEthereumChainParameter?.nativeCurrency ?? chain.nativeCurrency, - rpcUrls - } satisfies AddEthereumChainParameter; - - await provider.request({ - method: 'wallet_addEthereumChain', - params: [addEthereumChain] - }); - - const requestedChains = await this.getRequestedChainsIds(); - this.setRequestedChainsIds([...requestedChains, chainId]); + await _provider.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: numberToHex(chainId) }] + }); + config.emitter.emit('change', { chainId }); - return chain; - } catch (e) { - throw new UserRejectedRequestError(e as Error); - } + return config.chains.find(c => c.id === chainId) as Chain; + } catch (error) { + const chain = config.chains.find(c => c.id === chainId); + // Check if chain is not configured + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()); + + // Try to add chain if switch failed (common pattern) + //4902 in MetaMask: Unrecognized chain ID + if ((error as any)?.code === 4902 || (error as any)?.data?.originalError?.code === 4902) { + try { + await _provider.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: numberToHex(chainId), + chainName: chain.name, + nativeCurrency: chain.nativeCurrency, + rpcUrls: [chain.rpcUrls.default?.http[0] ?? ''], // Take first default HTTP RPC URL + blockExplorerUrls: [chain.blockExplorers?.default?.url] + } + ] + }); + config.emitter.emit('change', { chainId }); + + return chain; + } catch (addError) { + throw new UserRejectedRequestError(addError as Error); + } + } + throw new SwitchChainError(error as Error); } }, - onAccountsChanged(accounts) { + + onAccountsChanged(accounts: string[]) { if (accounts.length === 0) this.onDisconnect(); else config.emitter.emit('change', { accounts: accounts.map(x => getAddress(x)) }); }, - onChainChanged(chain) { + + onChainChanged(chain: string) { const chainId = Number(chain); config.emitter.emit('change', { chainId }); }, - async onConnect(connectInfo) { - const chainId = Number(connectInfo.chainId); - const accounts = await this.getAccounts(); - config.emitter.emit('connect', { accounts, chainId }); - }, - async onDisconnect(_error) { - this.setRequestedChainsIds([]); - config.emitter.emit('disconnect'); - - const provider = await this.getProvider(); - if (accountsChanged) { - provider.removeListener('accountsChanged', accountsChanged); - accountsChanged = undefined; - } - if (chainChanged) { - provider.removeListener('chainChanged', chainChanged); - chainChanged = undefined; - } - if (disconnect) { - provider.removeListener('disconnect', disconnect); - disconnect = undefined; - } - if (sessionDelete) { - provider.removeListener('session_delete', sessionDelete); - sessionDelete = undefined; - } - if (!connect) { - connect = this.onConnect.bind(this); - provider.on('connect', connect); - } - }, - onDisplayUri(uri) { - config.emitter.emit('message', { type: 'display_uri', data: uri }); - }, - onSessionDelete() { - this.onDisconnect(); - }, - getNamespaceChainsIds() { - if (!provider_) return []; - const chainIds = provider_.session?.namespaces[NAMESPACE]?.accounts?.map(account => - parseInt(account.split(':')[1] || '') - ); - return chainIds ?? []; - }, - getNamespaceMethods() { - if (!provider_) return []; - const methods = provider_.session?.namespaces[NAMESPACE]?.methods as NamespaceMethods[]; - - return methods ?? []; - }, - async getRequestedChainsIds() { - return (await config.storage?.getItem(this.requestedChainsStorageKey)) ?? []; - }, - /** - * Checks if the target chains match the chains that were - * initially requested by the connector for the WalletConnect session. - * If there is a mismatch, this means that the chains on the connector - * are considered stale, and need to be revalidated at a later point (via - * connection). - * - * There may be a scenario where a dapp adds a chain to the - * connector later on, however, this chain will not have been approved or rejected - * by the wallet. In this case, the chain is considered stale. - */ - async isChainsStale() { - if (!isNewChainsStale) return false; - - const connectorChains = config.chains.map(x => x.id); - const namespaceChains = this.getNamespaceChainsIds(); - if (namespaceChains.length && !namespaceChains.some(id => connectorChains.includes(id))) - return false; - - const requestedChains = await this.getRequestedChainsIds(); - - return !connectorChains.every(id => requestedChains.includes(id)); - }, - async setRequestedChainsIds(chains) { - await config.storage?.setItem(this.requestedChainsStorageKey, chains); - }, - get requestedChainsStorageKey() { - return `${this.id}.requestedChains` as Properties['requestedChainsStorageKey']; + onDisconnect: () => { + config.emitter.emit('disconnect'); } })); } diff --git a/packages/wagmi/src/index.tsx b/packages/wagmi/src/index.tsx index 78583101..b2f47a68 100644 --- a/packages/wagmi/src/index.tsx +++ b/packages/wagmi/src/index.tsx @@ -1,125 +1,3 @@ -import '@walletconnect/react-native-compat'; -import { useEffect, useState, useSyncExternalStore } from 'react'; -export { - AccountButton, - AppKitButton, - ConnectButton, - NetworkButton, - AppKit -} from '@reown/appkit-scaffold-react-native'; -import type { EventName, EventsControllerState } from '@reown/appkit-scaffold-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; - -export { defaultWagmiConfig } from './utils/defaultWagmiConfig'; -import type { AppKitOptions } from './client'; -import { AppKit } from './client'; import { WagmiAdapter } from './adapter'; -// -- Types ------------------------------------------------------------------- -export type { AppKitOptions } from './client'; - -type OpenOptions = Parameters[0]; - -// -- Setup ------------------------------------------------------------------- -let modal: AppKit | undefined; export { WagmiAdapter }; - -export function createAppKit(options: AppKitOptions) { - if (!modal) { - modal = new AppKit({ - ...options, - _sdkVersion: `react-native-wagmi-${ConstantsUtil.VERSION}` - }); - } - - return modal; -} - -// -- Hooks ------------------------------------------------------------------- -export function useAppKit() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKit" hook'); - } - - async function open(options?: OpenOptions) { - await modal?.open(options); - } - - async function close() { - await modal?.close(); - } - - return { open, close }; -} - -export function useAppKitState() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitState" hook'); - } - - const [state, setState] = useState(modal.getState()); - - useEffect(() => { - const unsubscribe = modal?.subscribeState(newState => { - if (newState) setState({ ...newState }); - }); - - return () => { - unsubscribe?.(); - }; - }, []); - - return state; -} - -export function useWalletInfo() { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useWalletInfo" hook'); - } - - const walletInfo = useSyncExternalStore( - modal.subscribeWalletInfo, - modal.getWalletInfo, - modal.getWalletInfo - ); - - return { walletInfo }; -} - -export function useAppKitEvents(callback?: (newEvent: EventsControllerState) => void) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEvents" hook'); - } - - const [event, setEvents] = useState(modal.getEvent()); - - useEffect(() => { - const unsubscribe = modal?.subscribeEvents(newEvent => { - setEvents({ ...newEvent }); - callback?.(newEvent); - }); - - return () => { - unsubscribe?.(); - }; - }, [callback]); - - return event; -} - -export function useAppKitEventSubscription( - event: EventName, - callback: (newEvent: EventsControllerState) => void -) { - if (!modal) { - throw new Error('Please call "createAppKit" before using "useAppKitEventSubscription" hook'); - } - - useEffect(() => { - const unsubscribe = modal?.subscribeEvent(event, callback); - - return () => { - unsubscribe?.(); - }; - }, [callback, event]); -} diff --git a/packages/wagmi/src/utils/defaultWagmiConfig.ts b/packages/wagmi/src/utils/defaultWagmiConfig.ts deleted file mode 100644 index fc3a06c2..00000000 --- a/packages/wagmi/src/utils/defaultWagmiConfig.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - createConfig, - createStorage, - type CreateConnectorFn, - type CreateConfigParameters -} from 'wagmi'; -import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider/dist/types/EthereumProvider'; -import { StorageUtil } from '@reown/appkit-scaffold-utils-react-native'; - -import { walletConnect } from '../connectors/WalletConnectConnector'; -import { getTransport } from './helpers'; - -export type ConfigOptions = Partial & { - projectId: string; - metadata: Exclude; - chains: CreateConfigParameters['chains']; - enableWalletConnect?: boolean; - extraConnectors?: CreateConnectorFn[]; -}; - -export function defaultWagmiConfig({ - projectId, - chains, - metadata, - enableWalletConnect = true, - extraConnectors, - ...wagmiConfig -}: ConfigOptions) { - const connectors: CreateConnectorFn[] = []; - const transportsArr = chains.map(chain => [ - chain.id, - getTransport({ chainId: chain.id, projectId }) - ]); - const transports = Object.fromEntries(transportsArr); - const storage = createStorage({ storage: StorageUtil }); - - if (enableWalletConnect) { - connectors.push(walletConnect({ projectId, metadata })); - } - - if (extraConnectors) { - connectors.push(...extraConnectors); - } - - return createConfig({ - chains, - connectors, - transports, - storage, - multiInjectedProviderDiscovery: false, - ...wagmiConfig - }); -} diff --git a/packages/wagmi/src/utils/helpers.ts b/packages/wagmi/src/utils/helpers.ts index ffa4892f..2b1efa49 100644 --- a/packages/wagmi/src/utils/helpers.ts +++ b/packages/wagmi/src/utils/helpers.ts @@ -1,52 +1,6 @@ -import { CoreHelperUtil } from '@reown/appkit-scaffold-react-native'; -import { - PresetsUtil, - ConstantsUtil, - type CaipNetwork, - type CaipNetworkId -} from '@reown/appkit-common-react-native'; -import type { Connector } from '@wagmi/core'; -import { EthereumProvider } from '@walletconnect/ethereum-provider'; -import type { AppKitClientOptions } from '../client'; -import { http, type Hex } from 'viem'; - -export function getCaipDefaultChain(chain?: AppKitClientOptions['defaultChain']) { - if (!chain) { - return undefined; - } - - return { - id: `${ConstantsUtil.EIP155}:${chain.id}`, - name: chain.name, - imageId: PresetsUtil.NetworkImageIds[chain.id] - } as CaipNetwork; -} - -export async function getWalletConnectCaipNetworks(connector?: Connector) { - if (!connector) { - throw new Error('networkControllerClient:getApprovedCaipNetworks - connector is undefined'); - } - const provider = (await connector?.getProvider()) as Awaited< - ReturnType<(typeof EthereumProvider)['init']> - >; - const ns = provider?.signer?.session?.namespaces; - const nsMethods = ns?.[ConstantsUtil.EIP155]?.methods; - const nsChains = ns?.[ConstantsUtil.EIP155]?.chains as CaipNetworkId[]; - - return { - supportsAllNetworks: Boolean(nsMethods?.includes(ConstantsUtil.ADD_CHAIN_METHOD)), - approvedCaipNetworkIds: nsChains - }; -} - -export function getAuthCaipNetworks() { - return { - supportsAllNetworks: false, - approvedCaipNetworkIds: PresetsUtil.RpcChainIds.map( - id => `${ConstantsUtil.EIP155}:${id}` - ) as CaipNetworkId[] - }; -} +import { CoreHelperUtil } from '@reown/appkit-react-native'; +import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; +import { http } from 'viem'; export function getTransport({ chainId, projectId }: { chainId: number; projectId: string }) { const RPC_URL = CoreHelperUtil.getBlockchainApiUrl(); @@ -57,15 +11,3 @@ export function getTransport({ chainId, projectId }: { chainId: number; projectI return http(`${RPC_URL}/v1/?chainId=${ConstantsUtil.EIP155}:${chainId}&projectId=${projectId}`); } - -export function requireCaipAddress(caipAddress: string) { - if (!caipAddress) { - throw new Error('No CAIP address provided'); - } - const account = caipAddress.split(':')[2] as Hex; - if (!account) { - throw new Error('Invalid CAIP address'); - } - - return account; -} From 3451bb99dfdae748d84311ede12579668e26f5f7 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 9 May 2025 14:32:29 -0300 Subject: [PATCH 24/91] chore: removed common code of adapters, getting balance in chainChanged event, universal provider singleton, explorer button --- apps/native/App.tsx | 84 ++++++++++--------- apps/native/package.json | 4 + apps/native/src/views/ActionsView.tsx | 6 +- packages/appkit/src/AppKit.ts | 27 +++--- .../src/connectors/WalletConnectConnector.ts | 21 ++++- .../views/w3m-account-default-view/index.tsx | 10 +-- packages/bitcoin/src/adapter.ts | 47 +---------- .../src/controllers/ConnectionController.ts | 1 - packages/ethers/src/adapter.ts | 45 ---------- packages/solana/src/adapter.ts | 45 ---------- packages/wagmi/src/adapter.ts | 5 +- yarn.lock | 4 +- 12 files changed, 98 insertions(+), 201 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index cc15e6d3..275d3f06 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -22,8 +22,7 @@ import { AppKitButton, NetworkButton, solana, - bitcoin, - bitcoinTestnet + bitcoin } from '@reown/appkit-react-native'; // import { authConnector } from '@reown/appkit-auth-wagmi-react-native'; @@ -34,13 +33,15 @@ import { Button, Text } from '@reown/appkit-ui-react-native'; // import { chains } from './src/utils/WagmiUtils'; // import { OpenButton } from './src/components/OpenButton'; // import { DisconnectButton } from './src/components/DisconnectButton'; -import { EthersAdapter } from '@reown/appkit-ethers-react-native'; +// import { EthersAdapter } from '@reown/appkit-ethers-react-native'; import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; +import { WagmiAdapter } from '@reown/appkit-wagmi-react-native'; import { mainnet, polygon, avalanche } from 'viem/chains'; import { ActionsView } from './src/views/ActionsView'; import { WalletInfoView } from './src/views/WalletInfoView'; import { EventsView } from './src/views/EventsView'; +import { WagmiProvider } from 'wagmi'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -84,8 +85,13 @@ const queryClient = new QueryClient(); // networks: chains // }); -const ethersAdapter = new EthersAdapter({ - projectId +// const ethersAdapter = new EthersAdapter({ +// projectId +// }); + +const wagmiAdapter = new WagmiAdapter({ + projectId, + networks: [mainnet, polygon, avalanche] }); const solanaAdapter = new SolanaAdapter({ @@ -116,10 +122,10 @@ const bitcoinAdapter = new BitcoinAdapter({ const appKit = createAppKit({ projectId, - adapters: [ethersAdapter, solanaAdapter, bitcoinAdapter], + adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter], metadata, - networks: [mainnet, polygon, avalanche, solana, bitcoin, bitcoinTestnet], - defaultChain: polygon, + networks: [mainnet, polygon, avalanche, bitcoin, solana], + defaultChain: mainnet, clipboardClient, debug: true, enableAnalytics: true @@ -137,37 +143,37 @@ export default function Native() { const isDarkMode = useColorScheme() === 'dark'; return ( - // - - - - - - AppKit for React Native - - - - - - {/* */} - {/* */} - {/* */} - - - - - - - - // + + + + + + + AppKit for React Native + + + + + + {/* */} + {/* */} + {/* */} + + + + + + + + ); } diff --git a/apps/native/package.json b/apps/native/package.json index cb883cb5..653b6369 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -62,5 +62,9 @@ "babel-plugin-module-resolver": "^5.0.0", "gh-pages": "^6.2.0", "typescript": "~5.3.3" + }, + "resolutions": { + "@walletconnect/ethereum-provider": "2.20.2", + "@walletconnect/universal-provider": "2.20.2" } } diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index 8fb4e34a..9b436f04 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -2,9 +2,11 @@ import { StyleSheet } from 'react-native'; import { FlexView } from '@reown/appkit-ui-react-native'; import { useAccount } from '@reown/appkit-react-native'; -import { EthersActionsView } from './EthersActionsView'; +// import { EthersActionsView } from './EthersActionsView'; import { SolanaActionsView } from './SolanaActionsView'; import { BitcoinActionsView } from './BitcoinActionsView'; +import { WagmiActionsView } from './WagmiActionsView'; + export function ActionsView() { const isConnected = true; const { chainId } = useAccount(); @@ -12,7 +14,7 @@ export function ActionsView() { return isConnected ? ( {chainId?.startsWith('eip155') ? ( - + ) : chainId?.startsWith('solana') ? ( ) : chainId?.startsWith('bip122') ? ( diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 52036846..6c135795 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -136,20 +136,16 @@ export class AppKit { */ async disconnect(namespace?: string, isInternal?: boolean): Promise { try { - if (!namespace || !ConnectionsController.state.activeNamespace) { + const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; + + if (!activeNamespace) { return; } - const connection = - ConnectionsController.state.connections[ - namespace ?? ConnectionsController.state.activeNamespace - ]; + const connection = ConnectionsController.state.connections[activeNamespace]; const connectorType = connection?.adapter?.connector?.type; - await ConnectionsController.disconnect( - namespace ?? ConnectionsController.state.activeNamespace, - isInternal - ); + await ConnectionsController.disconnect(activeNamespace, isInternal); if (connectorType) { await StorageUtil.removeConnectedConnectors(connectorType); @@ -216,7 +212,7 @@ export class AppKit { ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); } - adapter.getBalance({ network, tokens: this.config.tokens }); + // adapter.getBalance({ network, tokens: this.config.tokens }); } open(options?: OpenOptions) { @@ -248,7 +244,7 @@ export class AppKit { */ private async initConnectors() { const connectedConnectors = await StorageUtil.getConnectedConnectors(); // Fetch stored connectors - + console.log('initConnectors', connectedConnectors); if (connectedConnectors.length > 0) { ModalController.setLoading(true); @@ -376,8 +372,17 @@ export class AppKit { }); adapter.on('chainChanged', ({ chainId, namespace }) => { + console.log('chainChanged', chainId, namespace); const chain = `${namespace}:${chainId}` as CaipNetworkId; ConnectionsController.setActiveChain(namespace, chain); + + const network = this.networks.find(n => n.id?.toString() === chainId); + if (network) { + adapter.getBalance({ + network, + tokens: this.config.tokens + }); + } }); adapter.on('disconnect', ({ namespace }) => { diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 0727d270..03fff25c 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -12,7 +12,7 @@ import { } from '@reown/appkit-common-react-native'; export class WalletConnectConnector extends WalletConnector { - // private override provider: IUniversalProvider; + private static universalProviderInstance: IUniversalProvider | null = null; private constructor(provider: IUniversalProvider) { super({ type: 'walletconnect', provider: provider as Provider }); @@ -33,6 +33,23 @@ export class WalletConnectConnector extends WalletConnector { } } + private static async getUniversalProvider({ + projectId, + metadata + }: { + projectId: string; + metadata: Metadata; + }): Promise { + if (!WalletConnectConnector.universalProviderInstance) { + WalletConnectConnector.universalProviderInstance = await UniversalProvider.init({ + projectId, + metadata + }); + } + + return WalletConnectConnector.universalProviderInstance; + } + public static async create({ projectId, metadata @@ -40,7 +57,7 @@ export class WalletConnectConnector extends WalletConnector { projectId: string; metadata: Metadata; }): Promise { - const provider = await UniversalProvider.init({ + const provider = await WalletConnectConnector.getUniversalProvider({ projectId, metadata }); diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index a27f5ef2..b27a87df 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -38,9 +38,7 @@ import { AuthButtons } from './components/auth-buttons'; import styles from './styles'; export function AccountDefaultView() { - const { profileName, profileImage, addressExplorerUrl, preferredAccountType } = useSnapshot( - AccountController.state - ); + const { profileName, profileImage, preferredAccountType } = useSnapshot(AccountController.state); const { loading } = useSnapshot(ModalController.state); const { activeAddress: address, @@ -57,7 +55,7 @@ export function AccountDefaultView() { const showCopy = OptionsController.isClipboardAvailable(); const isAuth = connectedConnector === 'AUTH'; const showBalance = balance && !isAuth; - const showExplorer = addressExplorerUrl && !isAuth; + const showExplorer = Object.keys(activeNetwork?.blockExplorers ?? {}).length > 0 && !isAuth; const showBack = history.length > 1; const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); const { padding } = useCustomDimensions(); @@ -107,8 +105,8 @@ export function AccountDefaultView() { }; const onExplorerPress = () => { - if (AccountController.state.addressExplorerUrl) { - Linking.openURL(AccountController.state.addressExplorerUrl); + if (showExplorer && ConnectionsController.state.activeNetwork?.blockExplorers?.default?.url) { + Linking.openURL(ConnectionsController.state.activeNetwork?.blockExplorers?.default?.url); } }; diff --git a/packages/bitcoin/src/adapter.ts b/packages/bitcoin/src/adapter.ts index 66770cdc..05bac8ed 100644 --- a/packages/bitcoin/src/adapter.ts +++ b/packages/bitcoin/src/adapter.ts @@ -1,6 +1,5 @@ import { BlockchainAdapter, - WalletConnector, type AppKitNetwork, type CaipAddress, type ChainNamespace, @@ -60,7 +59,7 @@ export class BitcoinAdapter extends BlockchainAdapter { } } - override async switchNetwork(network: AppKitNetwork): Promise { + async switchNetwork(network: AppKitNetwork): Promise { if (!this.connector) throw new Error('No active connector'); const provider = this.connector.getProvider(); @@ -98,48 +97,4 @@ export class BitcoinAdapter extends BlockchainAdapter { getSupportedNamespace(): ChainNamespace { return BitcoinAdapter.supportedNamespace; } - - onChainChanged(chainId: string): void { - this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); - } - - onAccountsChanged(accounts: string[]): void { - const _accounts = this.getAccounts(); - const shouldEmit = _accounts?.some(account => { - const accountAddress = account.split(':')[2]; - - return accountAddress !== undefined && accounts.includes(accountAddress); - }); - - if (shouldEmit) { - this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); - } - } - - onDisconnect(): void { - this.emit('disconnect', { namespace: this.getSupportedNamespace() }); - - const provider = this.connector?.getProvider(); - if (provider) { - provider.off('chainChanged', this.onChainChanged.bind(this)); - provider.off('accountsChanged', this.onAccountsChanged.bind(this)); - provider.off('disconnect', this.onDisconnect.bind(this)); - } - - this.connector = undefined; - } - - override setConnector(connector: WalletConnector): void { - super.setConnector(connector); - this.subscribeToEvents(); - } - - subscribeToEvents(): void { - const provider = this.connector?.getProvider(); - if (!provider) return; - - provider.on('chainChanged', this.onChainChanged.bind(this)); - provider.on('accountsChanged', this.onAccountsChanged.bind(this)); - provider.on('disconnect', this.onDisconnect.bind(this)); - } } diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index b2014a8a..04b236bb 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -201,7 +201,6 @@ export const ConnectionController = { }, async disconnect() { - await this._getClient()?.disconnect(); this.resetWcConnection(); // remove transactions // RouterController.reset('Connect'); diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 827bd07f..3d8dfca5 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -1,7 +1,6 @@ import { formatEther, JsonRpcProvider } from 'ethers'; import { EVMAdapter, - WalletConnector, type AppKitNetwork, type CaipAddress, type ChainNamespace, @@ -109,48 +108,4 @@ export class EthersAdapter extends EVMAdapter { getSupportedNamespace(): ChainNamespace { return EthersAdapter.supportedNamespace; } - - onChainChanged(chainId: string): void { - this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); - } - - onAccountsChanged(accounts: string[]): void { - const _accounts = this.getAccounts(); - const shouldEmit = _accounts?.some(account => { - const accountAddress = account.split(':')[2]; - - return accountAddress !== undefined && accounts.includes(accountAddress); - }); - - if (shouldEmit) { - this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); - } - } - - onDisconnect(): void { - this.emit('disconnect', { namespace: this.getSupportedNamespace() }); - - const provider = this.connector?.getProvider(); - if (provider) { - provider.off('chainChanged', this.onChainChanged.bind(this)); - provider.off('accountsChanged', this.onAccountsChanged.bind(this)); - provider.off('disconnect', this.onDisconnect.bind(this)); - } - - this.connector = undefined; - } - - override setConnector(connector: WalletConnector): void { - super.setConnector(connector); - this.subscribeToEvents(); - } - - subscribeToEvents(): void { - const provider = this.connector?.getProvider(); - if (!provider) return; - - provider.on('chainChanged', this.onChainChanged.bind(this)); - provider.on('accountsChanged', this.onAccountsChanged.bind(this)); - provider.on('disconnect', this.onDisconnect.bind(this)); - } } diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index f606aa56..8cd1f544 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -1,6 +1,5 @@ import { SolanaBaseAdapter, - WalletConnector, type AppKitNetwork, type CaipAddress, type ChainNamespace, @@ -94,48 +93,4 @@ export class SolanaAdapter extends SolanaBaseAdapter { getSupportedNamespace(): ChainNamespace { return SolanaAdapter.supportedNamespace; } - - onChainChanged(chainId: string): void { - this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); - } - - onAccountsChanged(accounts: string[]): void { - const _accounts = this.getAccounts(); - const shouldEmit = _accounts?.some(account => { - const accountAddress = account.split(':')[2]; - - return accountAddress !== undefined && accounts.includes(accountAddress); - }); - - if (shouldEmit) { - this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); - } - } - - onDisconnect(): void { - this.emit('disconnect', { namespace: this.getSupportedNamespace() }); - - const provider = this.connector?.getProvider(); - if (provider) { - provider.off('chainChanged', this.onChainChanged.bind(this)); - provider.off('accountsChanged', this.onAccountsChanged.bind(this)); - provider.off('disconnect', this.onDisconnect.bind(this)); - } - - this.connector = undefined; - } - - override setConnector(connector: WalletConnector): void { - super.setConnector(connector); - this.subscribeToEvents(); - } - - subscribeToEvents(): void { - const provider = this.connector?.getProvider(); - if (!provider) return; - - provider.on('chainChanged', this.onChainChanged.bind(this)); - provider.on('accountsChanged', this.onAccountsChanged.bind(this)); - provider.on('disconnect', this.onDisconnect.bind(this)); - } } diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index 18782d00..b62b7e34 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -68,7 +68,10 @@ export class WagmiAdapter extends EVMAdapter { throw new Error('WagmiAdapter: AppKit connector not set or not connected via Wagmi.'); } - await switchChainWagmi(this.wagmiConfig, { chainId: network.id as number }); + await switchChainWagmi(this.wagmiConfig, { + chainId: network.id as number, + connector: this.appKitWagmiConnector + }); } async getBalance(params: GetBalanceParams): Promise { diff --git a/yarn.lock b/yarn.lock index ce5bc009..54c39b25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7211,7 +7211,6 @@ __metadata: dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" "@reown/appkit-react-native": "workspace:*" - "@reown/appkit-scaffold-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" "@walletconnect/ethereum-provider": "npm:2.20.2" @@ -7265,7 +7264,7 @@ __metadata: "@reown/appkit-core-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" "@reown/appkit-ui-react-native": "npm:1.2.3" - "@walletconnect/universal-provider": "npm:2.19.2" + "@walletconnect/universal-provider": "npm:2.20.2" valtio: "npm:^1.13.2" peerDependencies: react: ">=17" @@ -7388,7 +7387,6 @@ __metadata: dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" "@reown/appkit-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" peerDependencies: From 9c8570fdd8816c007c240b88316c7c13c298272d Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 May 2025 16:40:29 -0300 Subject: [PATCH 25/91] chore: improved wagmi connector --- apps/native/App.tsx | 59 ++++--------------- apps/native/src/components/OpenButton.tsx | 2 +- packages/appkit/src/AppKit.ts | 2 +- packages/common/src/utils/TypeUtil.ts | 18 +++--- packages/wagmi/src/adapter.ts | 50 +++++++++------- ...nectConnector.ts => UniversalConnector.ts} | 8 ++- 6 files changed, 55 insertions(+), 84 deletions(-) rename packages/wagmi/src/connectors/{WalletConnectConnector.ts => UniversalConnector.ts} (96%) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 275d3f06..2bdbe93a 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -31,9 +31,9 @@ import { Button, Text } from '@reown/appkit-ui-react-native'; // import { siweConfig } from './src/utils/SiweUtils'; // import { AccountView } from './src/views/AccountView'; // import { chains } from './src/utils/WagmiUtils'; -// import { OpenButton } from './src/components/OpenButton'; -// import { DisconnectButton } from './src/components/DisconnectButton'; -// import { EthersAdapter } from '@reown/appkit-ethers-react-native'; +import { OpenButton } from './src/components/OpenButton'; +import { DisconnectButton } from './src/components/DisconnectButton'; +import { EthersAdapter } from '@reown/appkit-ethers-react-native'; import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; import { WagmiAdapter } from '@reown/appkit-wagmi-react-native'; @@ -62,32 +62,11 @@ const clipboardClient = { } }; -// const auth = authConnector({ projectId, metadata }); - -// const extraConnectors = Platform.select({ -// ios: [auth], -// android: [auth], -// default: [] -// }); - -// const wagmiConfig = defaultWagmiConfig({ -// chains, -// projectId, -// metadata, -// extraConnectors -// }); - const queryClient = new QueryClient(); -// const wagmiAdapter = new WagmiAdapter({ -// wagmiConfig, -// projectId, -// networks: chains -// }); - -// const ethersAdapter = new EthersAdapter({ -// projectId -// }); +const ethersAdapter = new EthersAdapter({ + projectId +}); const wagmiAdapter = new WagmiAdapter({ projectId, @@ -102,30 +81,12 @@ const bitcoinAdapter = new BitcoinAdapter({ projectId }); -// createAppKit({ -// projectId, -// wagmiConfig, -// siweConfig, -// clipboardClient, -// customWallets, -// enableAnalytics: true, -// metadata, -// debug: true, -// features: { -// email: true, -// socials: ['x', 'discord', 'apple'], -// emailShowWallets: true, -// swaps: true -// // onramp: true -// } -// }); - const appKit = createAppKit({ projectId, adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter], metadata, networks: [mainnet, polygon, avalanche, bitcoin, solana], - defaultChain: mainnet, + defaultChain: polygon, clipboardClient, debug: true, enableAnalytics: true @@ -149,7 +110,7 @@ export default function Native() { - AppKit for React Native + AppKit Multichain for React Native {/* */} - {/* */} - {/* */} + + diff --git a/apps/native/src/components/OpenButton.tsx b/apps/native/src/components/OpenButton.tsx index c79b2ed9..868fe6d0 100644 --- a/apps/native/src/components/OpenButton.tsx +++ b/apps/native/src/components/OpenButton.tsx @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; import { Button } from '@reown/appkit-ui-react-native'; -import { useAppKit } from '@reown/appkit-wagmi-react-native'; +import { useAppKit } from '@reown/appkit-react-native'; import { useAccount } from 'wagmi'; export function OpenButton() { diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 6c135795..0a9b53bb 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -244,7 +244,6 @@ export class AppKit { */ private async initConnectors() { const connectedConnectors = await StorageUtil.getConnectedConnectors(); // Fetch stored connectors - console.log('initConnectors', connectedConnectors); if (connectedConnectors.length > 0) { ModalController.setLoading(true); @@ -386,6 +385,7 @@ export class AppKit { }); adapter.on('disconnect', ({ namespace }) => { + console.log('AppKit disconnect namespace', namespace); this.disconnect(namespace, false); }); diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index daf1c277..669c2f9e 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -172,6 +172,15 @@ export abstract class BlockchainAdapter extends EventEmitter { return this.connector.getProvider(); } + subscribeToEvents(): void { + const provider = this.connector?.getProvider(); + if (!provider) return; + + provider.on('chainChanged', this.onChainChanged.bind(this)); + provider.on('accountsChanged', this.onAccountsChanged.bind(this)); + provider.on('disconnect', this.onDisconnect.bind(this)); + } + onChainChanged(chainId: string): void { this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); } @@ -202,15 +211,6 @@ export abstract class BlockchainAdapter extends EventEmitter { this.connector = undefined; } - subscribeToEvents(): void { - const provider = this.connector?.getProvider(); - if (!provider) return; - - provider.on('chainChanged', this.onChainChanged.bind(this)); - provider.on('accountsChanged', this.onAccountsChanged.bind(this)); - provider.on('disconnect', this.onDisconnect.bind(this)); - } - abstract disconnect(): Promise; abstract request(method: string, params?: any[]): Promise; abstract getSupportedNamespace(): ChainNamespace; diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index b62b7e34..d20bd32d 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -20,19 +20,19 @@ import { import type { Chain } from 'wagmi/chains'; import { getTransport } from './utils/helpers'; import { formatUnits, type Hex } from 'viem'; -import { WalletConnectConnector } from './connectors/WalletConnectConnector'; +import { UniversalConnector } from './connectors/UniversalConnector'; type ConfigParams = Partial & { - networks: [Chain, ...Chain[]]; // Use Wagmi's Chain type + networks: [Chain, ...Chain[]]; projectId: string; connectors?: Connector[]; }; export class WagmiAdapter extends EVMAdapter { private static supportedNamespace: ChainNamespace = 'eip155'; - public wagmiChains: readonly Chain[] | undefined; // Use Wagmi's Chain type + public wagmiChains: readonly Chain[] | undefined; public wagmiConfig!: Config; - private appKitWagmiConnector?: Connector; // Store the created connector instance + private wagmiConfigConnector?: Connector; constructor(configParams: ConfigParams) { super({ @@ -64,13 +64,13 @@ export class WagmiAdapter extends EVMAdapter { } async switchNetwork(network: AppKitNetwork): Promise { - if (!this.appKitWagmiConnector) { + if (!this.wagmiConfigConnector) { throw new Error('WagmiAdapter: AppKit connector not set or not connected via Wagmi.'); } await switchChainWagmi(this.wagmiConfig, { chainId: network.id as number, - connector: this.appKitWagmiConnector + connector: this.wagmiConfigConnector }); } @@ -80,8 +80,7 @@ export class WagmiAdapter extends EVMAdapter { if (!this.connector) throw new Error('No active AppKit connector (EVMAdapter.connector)'); if (!network) throw new Error('No network provided'); - if (!this.appKitWagmiConnector) { - // Ensure our Wagmi connector wrapper is also active + if (!this.wagmiConfigConnector) { throw new Error('WagmiAdapter: AppKit connector not properly configured with Wagmi.'); } @@ -136,11 +135,12 @@ export class WagmiAdapter extends EVMAdapter { } async disconnect(): Promise { - if (this.appKitWagmiConnector) { - await disconnectWagmiCore(this.wagmiConfig, { connector: this.appKitWagmiConnector }); - this.appKitWagmiConnector = undefined; + if (this.wagmiConfigConnector) { + await disconnectWagmiCore(this.wagmiConfig, { connector: this.wagmiConfigConnector }); + this.wagmiConfigConnector = undefined; } else if (this.connector) { await this.connector.disconnect(); + this.onDisconnect(); } const evmAdapterInstance = this as any; @@ -160,23 +160,31 @@ export class WagmiAdapter extends EVMAdapter { return WagmiAdapter.supportedNamespace; } - override setConnector(newAppKitConnector: WalletConnector): void { - super.setConnector(newAppKitConnector); + override setConnector(_connector: WalletConnector): void { + super.setConnector(_connector); - if (newAppKitConnector && this.wagmiChains) { - if (!this.appKitWagmiConnector) { + if (_connector && this.wagmiChains) { + if (!this.wagmiConfigConnector) { // Manually add the connector to the wagmiConfig - const connector = this.wagmiConfig._internal.connectors.setup( - WalletConnectConnector(newAppKitConnector) + const connectorInstance = this.wagmiConfig._internal.connectors.setup( + UniversalConnector(_connector) ); - this.wagmiConfig._internal.connectors.setState(prev => [...prev, connector]); - this.appKitWagmiConnector = connector as unknown as Connector; + this.wagmiConfig._internal.connectors.setState(prev => [...prev, connectorInstance]); + this.wagmiConfigConnector = connectorInstance; + + connectorInstance.emitter.on('message', ({ type }: { type: string }) => { + if (type === 'externalDisconnect') { + this.onDisconnect(); + + this.wagmiConfigConnector = undefined; + } + }); try { - connectWagmi(this.wagmiConfig, { connector }); + connectWagmi(this.wagmiConfig, { connector: connectorInstance }); } catch (error) { - this.appKitWagmiConnector = undefined; // Clear if connection fails + this.wagmiConfigConnector = undefined; } } } diff --git a/packages/wagmi/src/connectors/WalletConnectConnector.ts b/packages/wagmi/src/connectors/UniversalConnector.ts similarity index 96% rename from packages/wagmi/src/connectors/WalletConnectConnector.ts rename to packages/wagmi/src/connectors/UniversalConnector.ts index 3c2dd624..1e1a09d9 100644 --- a/packages/wagmi/src/connectors/WalletConnectConnector.ts +++ b/packages/wagmi/src/connectors/UniversalConnector.ts @@ -9,7 +9,7 @@ import { } from 'viem'; import { ChainNotConfiguredError, createConnector, ProviderNotFoundError } from 'wagmi'; -export function WalletConnectConnector(appKitProvidedConnector: WalletConnector) { +export function UniversalConnector(appKitProvidedConnector: WalletConnector) { let provider: Provider | undefined; let accountsChangedHandler: ((accounts: string[]) => void) | undefined; @@ -26,6 +26,7 @@ export function WalletConnectConnector(appKitProvidedConnector: WalletConnector) async setup() { provider = appKitProvidedConnector.getProvider(); + // appkitConnector = appKitProvidedConnector; if (provider?.on) { accountsChangedHandler = (accounts: string[]) => { const hexAccounts = accounts.map(acc => getAddress(acc)); @@ -82,7 +83,8 @@ export function WalletConnectConnector(appKitProvidedConnector: WalletConnector) }, async disconnect() { - await provider?.disconnect(); + await appKitProvidedConnector.disconnect(); + config.emitter.emit('message', { type: 'externalDisconnect' }); if (provider?.off && accountsChangedHandler && chainChangedHandler && disconnectHandler) { provider.off('accountsChanged', accountsChangedHandler); provider.off('chainChanged', chainChangedHandler); @@ -112,7 +114,7 @@ export function WalletConnectConnector(appKitProvidedConnector: WalletConnector) }, async getChainId() { - const _provider = await this.getProvider(); + const _provider = appKitProvidedConnector.getProvider(); if (_provider) { try { const chainId = (await _provider.request({ From 71d6bb620b2a5dc45b73245efbe567452453ecfd Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 May 2025 16:59:41 -0300 Subject: [PATCH 26/91] chore: fixed tests --- apps/native/App.tsx | 8 +++--- packages/appkit/src/AppKit.ts | 5 ++-- .../BlockchainApiController.test.ts | 28 ++++++++++++++++++- .../controllers/OnRampController.test.ts | 16 ++++++++--- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 2bdbe93a..06afa12a 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -33,7 +33,7 @@ import { Button, Text } from '@reown/appkit-ui-react-native'; // import { chains } from './src/utils/WagmiUtils'; import { OpenButton } from './src/components/OpenButton'; import { DisconnectButton } from './src/components/DisconnectButton'; -import { EthersAdapter } from '@reown/appkit-ethers-react-native'; +// import { EthersAdapter } from '@reown/appkit-ethers-react-native'; import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; import { WagmiAdapter } from '@reown/appkit-wagmi-react-native'; @@ -64,9 +64,9 @@ const clipboardClient = { const queryClient = new QueryClient(); -const ethersAdapter = new EthersAdapter({ - projectId -}); +// const ethersAdapter = new EthersAdapter({ +// projectId +// }); const wagmiAdapter = new WagmiAdapter({ projectId, diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 0a9b53bb..f7eee274 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -366,12 +366,13 @@ export class AppKit { private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { adapter.on('accountsChanged', ({ accounts, namespace }) => { + //eslint-disable-next-line no-console console.log('accountsChanged', accounts, namespace); //TODO: check this }); adapter.on('chainChanged', ({ chainId, namespace }) => { - console.log('chainChanged', chainId, namespace); + // console.log('chainChanged', chainId, namespace); const chain = `${namespace}:${chainId}` as CaipNetworkId; ConnectionsController.setActiveChain(namespace, chain); @@ -385,7 +386,7 @@ export class AppKit { }); adapter.on('disconnect', ({ namespace }) => { - console.log('AppKit disconnect namespace', namespace); + // console.log('AppKit disconnect namespace', namespace); this.disconnect(namespace, false); }); diff --git a/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts b/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts index 63258e38..72054a6e 100644 --- a/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts +++ b/packages/core/src/__tests__/controllers/BlockchainApiController.test.ts @@ -5,6 +5,25 @@ const MOCK_IDENTITY = { avatar: 'https://example.com' }; +// Mock FetchUtil using jest +jest.mock('../../utils/FetchUtil', () => ({ + FetchUtil: jest.fn().mockImplementation(() => ({ + get: jest.fn().mockResolvedValue(MOCK_IDENTITY) + })) +})); + +// Mock ConnectionsController +jest.mock('../../controllers/ConnectionsController', () => ({ + ConnectionsController: { + state: { + activeCaipNetworkId: 'eip155:1' + } + } +})); + +// Mock isNetworkSupported using jest +jest.spyOn(BlockchainApiController, 'isNetworkSupported').mockResolvedValue(true); + // @ts-ignore global.fetch = jest.fn(() => Promise.resolve({ @@ -18,9 +37,16 @@ global.fetch = jest.fn(() => // -- Tests -------------------------------------------------------------------- describe('BlockchainApiController', () => { + // Reset the API state before each test + beforeEach(() => { + // Ensure API instance is properly mocked + BlockchainApiController.state.api = { + get: jest.fn().mockResolvedValue(MOCK_IDENTITY) + } as any; + }); + it('fetch identity of account', async () => { let identity = await BlockchainApiController.fetchIdentity({ - caipChainId: 'eip155:1', address: '0x00000' }); expect(identity).toEqual(MOCK_IDENTITY); diff --git a/packages/core/src/__tests__/controllers/OnRampController.test.ts b/packages/core/src/__tests__/controllers/OnRampController.test.ts index da42e4d2..a40c78da 100644 --- a/packages/core/src/__tests__/controllers/OnRampController.test.ts +++ b/packages/core/src/__tests__/controllers/OnRampController.test.ts @@ -110,7 +110,7 @@ beforeEach(() => { OnRampController.resetState(); }); -// -- Tests -------------------------------------------------------------------- +// -- Tests --------------------------------------------------------------------- describe('OnRampController', () => { it('should have valid default state', () => { expect(OnRampController.state.quotesLoading).toBe(false); @@ -223,7 +223,7 @@ describe('OnRampController', () => { Object.defineProperty(ConstantsUtil, 'COUNTRY_CURRENCIES', { value: { US: 'USD', - AR: 'ARS' // Assuming mockCountry2 has ES country code + AR: 'ARS' }, configurable: true }); @@ -237,9 +237,17 @@ describe('OnRampController', () => { mockFiatCurrency, // USD mockFiatCurrency2 // ARS ]); + (StorageUtil.getOnRampCountries as jest.Mock).mockResolvedValue([]); + (StorageUtil.getOnRampPreferredCountry as jest.Mock).mockResolvedValue(null); + (StorageUtil.getOnRampFiatCurrencies as jest.Mock).mockResolvedValue([]); + (StorageUtil.getOnRampPreferredFiatCurrency as jest.Mock).mockResolvedValue(null); + (BlockchainApiController.fetchOnRampPaymentMethods as jest.Mock).mockResolvedValue([]); + (BlockchainApiController.fetchOnRampCryptoCurrencies as jest.Mock).mockResolvedValue([]); - // Execute - await OnRampController.loadOnRampData(); + // Explicitly set the state to ensure the initial values are as expected + OnRampController.state.selectedCountry = mockCountry; + OnRampController.state.paymentCurrency = mockFiatCurrency; + OnRampController.state.paymentCurrencies = [mockFiatCurrency, mockFiatCurrency2]; // First verify the initial state expect(OnRampController.state.selectedCountry).toEqual(mockCountry); From 7f6621d85a011239f657b7596d7865eb684aaca5 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 May 2025 17:06:48 -0300 Subject: [PATCH 27/91] chore: changed lockfile --- apps/native/package.json | 8 ++++---- packages/ethers/package.json | 2 +- yarn.lock | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/native/package.json b/apps/native/package.json index 653b6369..6f02164e 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -24,10 +24,10 @@ "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", "@reown/appkit-auth-wagmi-react-native": "1.2.3", - "@reown/appkit-bitcoin-react-native": "workspace:*", - "@reown/appkit-ethers-react-native": "workspace:*", - "@reown/appkit-react-native": "workspace:*", - "@reown/appkit-solana-react-native": "workspace:*", + "@reown/appkit-bitcoin-react-native": "1.2.3", + "@reown/appkit-ethers-react-native": "1.2.3", + "@reown/appkit-react-native": "1.2.3", + "@reown/appkit-solana-react-native": "1.2.3", "@reown/appkit-wagmi-react-native": "1.2.3", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 2e10f17c..91533f1d 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-react-native": "workspace:*", + "@reown/appkit-react-native": "1.2.3", "@reown/appkit-scaffold-utils-react-native": "1.2.3", "@reown/appkit-siwe-react-native": "1.2.3", "@walletconnect/ethereum-provider": "2.20.2" diff --git a/yarn.lock b/yarn.lock index 54c39b25..4777f09f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -108,10 +108,10 @@ __metadata: "@react-native-async-storage/async-storage": "npm:2.1.2" "@react-native-community/netinfo": "npm:11.4.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.2.3" - "@reown/appkit-bitcoin-react-native": "workspace:*" - "@reown/appkit-ethers-react-native": "workspace:*" - "@reown/appkit-react-native": "workspace:*" - "@reown/appkit-solana-react-native": "workspace:*" + "@reown/appkit-bitcoin-react-native": "npm:1.2.3" + "@reown/appkit-ethers-react-native": "npm:1.2.3" + "@reown/appkit-react-native": "npm:1.2.3" + "@reown/appkit-solana-react-native": "npm:1.2.3" "@reown/appkit-wagmi-react-native": "npm:1.2.3" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" @@ -7122,7 +7122,7 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-bitcoin-react-native@workspace:*, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": +"@reown/appkit-bitcoin-react-native@npm:1.2.3, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": version: 0.0.0-use.local resolution: "@reown/appkit-bitcoin-react-native@workspace:packages/bitcoin" dependencies: @@ -7205,12 +7205,12 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-ethers-react-native@workspace:*, @reown/appkit-ethers-react-native@workspace:packages/ethers": +"@reown/appkit-ethers-react-native@npm:1.2.3, @reown/appkit-ethers-react-native@workspace:packages/ethers": version: 0.0.0-use.local resolution: "@reown/appkit-ethers-react-native@workspace:packages/ethers" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-react-native": "workspace:*" + "@reown/appkit-react-native": "npm:1.2.3" "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" "@walletconnect/ethereum-provider": "npm:2.20.2" @@ -7256,7 +7256,7 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-react-native@npm:1.2.3, @reown/appkit-react-native@workspace:*, @reown/appkit-react-native@workspace:packages/appkit": +"@reown/appkit-react-native@npm:1.2.3, @reown/appkit-react-native@workspace:packages/appkit": version: 0.0.0-use.local resolution: "@reown/appkit-react-native@workspace:packages/appkit" dependencies: @@ -7325,7 +7325,7 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-solana-react-native@workspace:*, @reown/appkit-solana-react-native@workspace:packages/solana": +"@reown/appkit-solana-react-native@npm:1.2.3, @reown/appkit-solana-react-native@workspace:packages/solana": version: 0.0.0-use.local resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" dependencies: From ac50389c6e4ad3fa73d98b4e02e669894bd0b19c Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 May 2025 17:16:07 -0300 Subject: [PATCH 28/91] chore: changeset file --- .changeset/forty-zoos-double.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .changeset/forty-zoos-double.md diff --git a/.changeset/forty-zoos-double.md b/.changeset/forty-zoos-double.md new file mode 100644 index 00000000..c3d8e7e5 --- /dev/null +++ b/.changeset/forty-zoos-double.md @@ -0,0 +1,20 @@ +--- +'@reown/appkit-scaffold-utils-react-native': major +'@reown/appkit-bitcoin-react-native': major +'@reown/appkit-ethers5-react-native': major +'@reown/appkit-react-native': major +'@reown/appkit-common-react-native': major +'@reown/appkit-ethers-react-native': major +'@reown/appkit-solana-react-native': major +'@reown/appkit-wagmi-react-native': major +'@reown/appkit-core-react-native': major +'@reown/appkit-siwe-react-native': major +'@reown/appkit-ui-react-native': major +'@reown/appkit-auth-ethers-react-native': major +'@reown/appkit-auth-wagmi-react-native': major +'@reown/appkit-coinbase-ethers-react-native': major +'@reown/appkit-coinbase-wagmi-react-native': major +'@reown/appkit-wallet-react-native': major +--- + +feat: added multichain support From 6158106101f56c15374e85f09bbcf5e28a851f6a Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 May 2025 17:19:24 -0300 Subject: [PATCH 29/91] chore: removed onramp changeset file --- .changeset/slimy-apricots-complain.md | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .changeset/slimy-apricots-complain.md diff --git a/.changeset/slimy-apricots-complain.md b/.changeset/slimy-apricots-complain.md deleted file mode 100644 index 28137489..00000000 --- a/.changeset/slimy-apricots-complain.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@reown/appkit-scaffold-react-native': minor -'@reown/appkit-common-react-native': minor -'@reown/appkit-core-react-native': minor -'@reown/appkit-siwe-react-native': minor -'@reown/appkit-ui-react-native': minor -'@reown/appkit-auth-ethers-react-native': minor -'@reown/appkit-auth-wagmi-react-native': minor -'@reown/appkit-coinbase-ethers-react-native': minor -'@reown/appkit-coinbase-wagmi-react-native': minor -'@reown/appkit-ethers-react-native': minor -'@reown/appkit-ethers5-react-native': minor -'@reown/appkit-scaffold-utils-react-native': minor -'@reown/appkit-wagmi-react-native': minor -'@reown/appkit-wallet-react-native': minor ---- - -feat: added onramp feature From 35c54070c52b2d7ac39afefd765a29ee67590c5e Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 May 2025 17:30:30 -0300 Subject: [PATCH 30/91] chore: changed snapshot action to create a package if it doesn't exist --- .github/workflows/snapshot.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index b8d1ab43..3760d802 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -19,6 +19,25 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: Publish Initial Versions for New Packages + continue-on-error: false + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" + # Find packages that don't exist in NPM and publish them with initial version + yarn workspaces list --json | jq -r '.name' | while read pkg; do + if [ "$pkg" != "." ] && ! npm view "$pkg" &>/dev/null; then + echo "Package $pkg doesn't exist in NPM, publishing initial version 0.0.0" + cd $(yarn workspaces info --json | jq -r ".[\"$pkg\"].location") + # Temporarily set version to 0.0.0 for initial publish + jq '.version = "0.0.0"' package.json > package.json.tmp && mv package.json.tmp package.json + yarn npm publish --access public --tag alpha + cd - + fi + done + - name: Publish Snapshots continue-on-error: false env: From 212fdfd8e7b486de0df07a6bc2c7afe5edb03d78 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 May 2025 10:53:32 -0300 Subject: [PATCH 31/91] chore: modified snapshot action --- .github/workflows/snapshot.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 3760d802..eadf18f3 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -30,11 +30,23 @@ jobs: yarn workspaces list --json | jq -r '.name' | while read pkg; do if [ "$pkg" != "." ] && ! npm view "$pkg" &>/dev/null; then echo "Package $pkg doesn't exist in NPM, publishing initial version 0.0.0" - cd $(yarn workspaces info --json | jq -r ".[\"$pkg\"].location") - # Temporarily set version to 0.0.0 for initial publish - jq '.version = "0.0.0"' package.json > package.json.tmp && mv package.json.tmp package.json - yarn npm publish --access public --tag alpha - cd - + # Get the location of the package + PKG_DIR=$(yarn workspaces info --json | jq -r ".[\"$pkg\"].location") + if [ -d "$PKG_DIR" ] && [ -f "$PKG_DIR/package.json" ]; then + echo "Found package directory at $PKG_DIR" + # Create a backup of the original package.json + cp "$PKG_DIR/package.json" "$PKG_DIR/package.json.bak" + # Update the version to 0.0.0 + jq '.version = "0.0.0"' "$PKG_DIR/package.json" > "$PKG_DIR/package.json.tmp" && mv "$PKG_DIR/package.json.tmp" "$PKG_DIR/package.json" + # Publish from the package directory + (cd "$PKG_DIR" && yarn npm publish --access public --tag alpha) + # Restore the original package.json + mv "$PKG_DIR/package.json.bak" "$PKG_DIR/package.json" + else + echo "Error: Could not find package.json in $PKG_DIR" + echo "Current directory: $(pwd)" + echo "Directory contents: $(ls -la)" + fi fi done From ddb535e68be39b2b151a6aa511a16d1b2d293b0b Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 May 2025 10:56:24 -0300 Subject: [PATCH 32/91] chore: modified snapshot action --- .github/workflows/snapshot.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index eadf18f3..132ac92c 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -36,8 +36,13 @@ jobs: echo "Found package directory at $PKG_DIR" # Create a backup of the original package.json cp "$PKG_DIR/package.json" "$PKG_DIR/package.json.bak" - # Update the version to 0.0.0 - jq '.version = "0.0.0"' "$PKG_DIR/package.json" > "$PKG_DIR/package.json.tmp" && mv "$PKG_DIR/package.json.tmp" "$PKG_DIR/package.json" + # Update the version to 0.0.0 using Node.js + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('$PKG_DIR/package.json', 'utf8')); + pkg.version = '0.0.0'; + fs.writeFileSync('$PKG_DIR/package.json', JSON.stringify(pkg, null, 2)); + " # Publish from the package directory (cd "$PKG_DIR" && yarn npm publish --access public --tag alpha) # Restore the original package.json From c8c2372ed0e2a40a6c0bb7aa908bd8a290b9bd68 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 May 2025 12:07:33 -0300 Subject: [PATCH 33/91] chore: modified snapshot action --- .github/scripts/publish-initial-versions.js | 142 ++++++++++++++++++++ .github/workflows/changesets.yml | 2 +- .github/workflows/snapshot.yml | 30 +---- 3 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 .github/scripts/publish-initial-versions.js diff --git a/.github/scripts/publish-initial-versions.js b/.github/scripts/publish-initial-versions.js new file mode 100644 index 00000000..4ae34636 --- /dev/null +++ b/.github/scripts/publish-initial-versions.js @@ -0,0 +1,142 @@ +const { execSync, spawnSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +// Helper function to run commands and handle errors +function runCommand(command, args, options) { + console.log( + `Executing: ${command} ${args.join(' ')} ${options && options.cwd ? `in ${options.cwd}` : ''}` + ); + const result = spawnSync(command, args, { stdio: 'inherit', ...options }); + if (result.error) { + console.error(`Error executing ${command}:`, result.error); + throw result.error; + } + if (result.status !== 0) { + const message = `Command failed: ${command} ${args.join(' ')} exited with status ${ + result.status + }`; + console.error(message); + throw new Error(message); + } + return result; +} + +console.log('Starting initial package publishing process...'); + +let packagesToPublish = []; +const rootDir = process.cwd(); + +const packagesToExclude = ['@apps/native', '@apps/gallery']; + +try { + // Get workspace info using yarn workspaces list --json + // Yarn v1 outputs newline-delimited JSON objects + const rawOutput = execSync('yarn workspaces list --json', { encoding: 'utf8' }); + const lines = rawOutput + .trim() + .split('\n') + .filter(line => line.trim() !== ''); + const workspacePackages = lines.map(line => JSON.parse(line)); + + for (const pkgData of workspacePackages) { + // Skip the root package or any package without a defined location + if (pkgData.name === '.' || !pkgData.location) { + continue; + } + + // Skip excluded packages + if (packagesToExclude.includes(pkgData.name)) { + console.log(`Skipping excluded package: ${pkgData.name}`); + continue; + } + + const pkgName = pkgData.name; + const pkgDir = path.resolve(rootDir, pkgData.location); + + // Check if package exists on npm + console.log(`Checking NPM status for ${pkgName}...`); + const npmViewResult = spawnSync('npm', ['view', pkgName, 'version'], { encoding: 'utf8' }); + + // If npm view exits with 0 and has output, package exists. + // Otherwise (non-zero exit or empty output), it likely doesn't. + if (npmViewResult.status === 0 && npmViewResult.stdout && npmViewResult.stdout.trim() !== '') { + console.log( + `Package ${pkgName} (version: ${npmViewResult.stdout.trim()}) already exists on NPM. Skipping initial publish.` + ); + } else { + console.log( + `Package ${pkgName} does not appear to exist on NPM or has no published versions.` + ); + if (fs.existsSync(path.join(pkgDir, 'package.json'))) { + packagesToPublish.push({ name: pkgName, dir: pkgDir }); + } else { + console.warn(`Skipping ${pkgName}: package.json not found in ${pkgDir}`); + } + } + } +} catch (error) { + console.error('Error processing workspace info or checking NPM status:', error.message); + process.exit(1); // Critical error, exit +} + +if (packagesToPublish.length === 0) { + console.log('No new packages to publish initially.'); +} else { + console.log( + `Found ${packagesToPublish.length} new package(s) to publish initially: ${packagesToPublish + .map(p => p.name) + .join(', ')}` + ); +} + +let hasPublishErrors = false; +for (const pkg of packagesToPublish) { + console.log(`Attempting to publish ${pkg.name} from ${pkg.dir} with alpha tag...`); + const packageJsonPath = path.join(pkg.dir, 'package.json'); + let originalPackageJson = ''; + try { + originalPackageJson = fs.readFileSync(packageJsonPath, 'utf8'); + const parsedPackageJson = JSON.parse(originalPackageJson); + + console.log(`Temporarily setting version of ${pkg.name} to 0.0.1 for initial publish.`); + parsedPackageJson.version = '0.0.1'; + fs.writeFileSync(packageJsonPath, JSON.stringify(parsedPackageJson, null, 2)); + + // runCommand('yarn', ['npm', 'publish', '--access', 'public', '--tag', 'alpha'], { + // cwd: pkg.dir + // }); + console.log( + `DRY RUN: Would publish ${pkg.name} from ${pkg.dir} with version 0.0.1 and alpha tag.` + ); + console.log( + `DRY RUN: Command would be: yarn npm publish --access public --tag alpha (in ${pkg.dir})` + ); + } catch (publishError) { + // runCommand already logs error details if it's from there + console.error(`Failed to publish ${pkg.name}: ${publishError.message}`); + hasPublishErrors = true; // Mark that an error occurred but continue trying other packages + } finally { + // Restore original package.json + if (originalPackageJson) { + console.log(`Restoring original package.json for ${pkg.name}.`); + try { + fs.writeFileSync(packageJsonPath, originalPackageJson); + } catch (restoreError) { + console.error( + `CRITICAL: Failed to restore original package.json for ${pkg.name}: ${restoreError.message}` + ); + // This is a more critical error, as it leaves the repo in a modified state. + // Depending on desired behavior, you might want to ensure this error is highly visible + // or even causes the entire workflow to fail more loudly. + hasPublishErrors = true; // Ensure the overall process is marked as failed. + } + } + } +} + +console.log('Initial package publishing process finished.'); +if (hasPublishErrors) { + console.error('One or more packages failed during initial publishing.'); + process.exit(1); // Exit with error if any package failed to publish +} diff --git a/.github/workflows/changesets.yml b/.github/workflows/changesets.yml index 64bb4382..d3eddd70 100644 --- a/.github/workflows/changesets.yml +++ b/.github/workflows/changesets.yml @@ -21,7 +21,7 @@ jobs: - name: Checkout Repo uses: actions/checkout@v4 - - name: + - name: Setup Environment uses: ./.github/actions/setup - name: Create Release Pull Request or Publish to NPM diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 132ac92c..67ae9d75 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -23,37 +23,9 @@ jobs: continue-on-error: false env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" - # Find packages that don't exist in NPM and publish them with initial version - yarn workspaces list --json | jq -r '.name' | while read pkg; do - if [ "$pkg" != "." ] && ! npm view "$pkg" &>/dev/null; then - echo "Package $pkg doesn't exist in NPM, publishing initial version 0.0.0" - # Get the location of the package - PKG_DIR=$(yarn workspaces info --json | jq -r ".[\"$pkg\"].location") - if [ -d "$PKG_DIR" ] && [ -f "$PKG_DIR/package.json" ]; then - echo "Found package directory at $PKG_DIR" - # Create a backup of the original package.json - cp "$PKG_DIR/package.json" "$PKG_DIR/package.json.bak" - # Update the version to 0.0.0 using Node.js - node -e " - const fs = require('fs'); - const pkg = JSON.parse(fs.readFileSync('$PKG_DIR/package.json', 'utf8')); - pkg.version = '0.0.0'; - fs.writeFileSync('$PKG_DIR/package.json', JSON.stringify(pkg, null, 2)); - " - # Publish from the package directory - (cd "$PKG_DIR" && yarn npm publish --access public --tag alpha) - # Restore the original package.json - mv "$PKG_DIR/package.json.bak" "$PKG_DIR/package.json" - else - echo "Error: Could not find package.json in $PKG_DIR" - echo "Current directory: $(pwd)" - echo "Directory contents: $(ls -la)" - fi - fi - done + node .github/scripts/publish-initial-versions.js - name: Publish Snapshots continue-on-error: false From 51e4bfbdedf6ad1e9b03f31fba97d6b96cd1aaf1 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 May 2025 12:13:32 -0300 Subject: [PATCH 34/91] chore: modified snapshot action --- .github/scripts/publish-initial-versions.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/scripts/publish-initial-versions.js b/.github/scripts/publish-initial-versions.js index 4ae34636..ba88f3ba 100644 --- a/.github/scripts/publish-initial-versions.js +++ b/.github/scripts/publish-initial-versions.js @@ -103,15 +103,15 @@ for (const pkg of packagesToPublish) { parsedPackageJson.version = '0.0.1'; fs.writeFileSync(packageJsonPath, JSON.stringify(parsedPackageJson, null, 2)); - // runCommand('yarn', ['npm', 'publish', '--access', 'public', '--tag', 'alpha'], { - // cwd: pkg.dir - // }); - console.log( - `DRY RUN: Would publish ${pkg.name} from ${pkg.dir} with version 0.0.1 and alpha tag.` - ); - console.log( - `DRY RUN: Command would be: yarn npm publish --access public --tag alpha (in ${pkg.dir})` - ); + runCommand('yarn', ['npm', 'publish', '--access', 'public', '--tag', 'alpha'], { + cwd: pkg.dir + }); + // console.log( + // `DRY RUN: Would publish ${pkg.name} from ${pkg.dir} with version 0.0.1 and alpha tag.` + // ); + // console.log( + // `DRY RUN: Command would be: yarn npm publish --access public --tag alpha (in ${pkg.dir})` + // ); } catch (publishError) { // runCommand already logs error details if it's from there console.error(`Failed to publish ${pkg.name}: ${publishError.message}`); From bc514ee24ce8a501f139e558b58d592e91d7d24e Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 May 2025 12:25:28 -0300 Subject: [PATCH 35/91] chore: modified snapshot action --- .github/workflows/snapshot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 67ae9d75..c6dbdd49 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -23,8 +23,10 @@ jobs: continue-on-error: false env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" + yarn run changeset:prepublish node .github/scripts/publish-initial-versions.js - name: Publish Snapshots From 10f5ec63575ba226bdee59dd51ec87a821caf28d Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 May 2025 12:40:18 -0300 Subject: [PATCH 36/91] chore: modified snapshot action --- .github/scripts/publish-initial-versions.js | 22 ++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/scripts/publish-initial-versions.js b/.github/scripts/publish-initial-versions.js index ba88f3ba..86efd5c4 100644 --- a/.github/scripts/publish-initial-versions.js +++ b/.github/scripts/publish-initial-versions.js @@ -27,7 +27,7 @@ console.log('Starting initial package publishing process...'); let packagesToPublish = []; const rootDir = process.cwd(); -const packagesToExclude = ['@apps/native', '@apps/gallery']; +const packagesToExclude = ['@apps/native', '@apps/gallery', 'appkit-react-native']; try { // Get workspace info using yarn workspaces list --json @@ -40,8 +40,13 @@ try { const workspacePackages = lines.map(line => JSON.parse(line)); for (const pkgData of workspacePackages) { - // Skip the root package or any package without a defined location - if (pkgData.name === '.' || !pkgData.location) { + console.log(`[DEBUG] Processing workspace entry: ${JSON.stringify(pkgData)}`); + + // Skip the root package (identified by location '.') or any package without a defined location + if (pkgData.location === '.' || !pkgData.location) { + console.log( + `[DEBUG] Skipping root or undefined location package: ${pkgData.name} at ${pkgData.location}` + ); continue; } @@ -69,6 +74,11 @@ try { `Package ${pkgName} does not appear to exist on NPM or has no published versions.` ); if (fs.existsSync(path.join(pkgDir, 'package.json'))) { + const packageJsonContent = fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'); + const parsedPackageJson = JSON.parse(packageJsonContent); + console.log( + `[DEBUG] package.json for ${pkgName}: private=${parsedPackageJson.private}, version=${parsedPackageJson.version}` + ); // Added for debugging packagesToPublish.push({ name: pkgName, dir: pkgDir }); } else { console.warn(`Skipping ${pkgName}: package.json not found in ${pkgDir}`); @@ -92,6 +102,7 @@ if (packagesToPublish.length === 0) { let hasPublishErrors = false; for (const pkg of packagesToPublish) { + console.log(`[DEBUG] Attempting to publish from list: ${JSON.stringify(pkg)}`); // Added for debugging console.log(`Attempting to publish ${pkg.name} from ${pkg.dir} with alpha tag...`); const packageJsonPath = path.join(pkg.dir, 'package.json'); let originalPackageJson = ''; @@ -99,6 +110,11 @@ for (const pkg of packagesToPublish) { originalPackageJson = fs.readFileSync(packageJsonPath, 'utf8'); const parsedPackageJson = JSON.parse(originalPackageJson); + if (parsedPackageJson.private === true) { + console.log(`Package ${pkg.name} is private, skipping initial publish.`); + continue; // Skip to the next package + } + console.log(`Temporarily setting version of ${pkg.name} to 0.0.1 for initial publish.`); parsedPackageJson.version = '0.0.1'; fs.writeFileSync(packageJsonPath, JSON.stringify(parsedPackageJson, null, 2)); From 18d4aaa4630fab2a14ad71b81193be6e6c524c23 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 May 2025 14:48:18 -0300 Subject: [PATCH 37/91] chore: modified snapshot action --- .github/scripts/publish-initial-versions.js | 12 ++++++++++++ .github/workflows/snapshot.yml | 1 - apps/native/app.json | 10 +++------- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/scripts/publish-initial-versions.js b/.github/scripts/publish-initial-versions.js index 86efd5c4..e7f2b6ac 100644 --- a/.github/scripts/publish-initial-versions.js +++ b/.github/scripts/publish-initial-versions.js @@ -98,6 +98,18 @@ if (packagesToPublish.length === 0) { .map(p => p.name) .join(', ')}` ); + + // Conditionally run changeset:prepublish if there are packages to publish + if (packagesToPublish.length > 0) { + console.log('New packages found. Running changeset:prepublish to build packages...'); + try { + runCommand('yarn', ['run', 'changeset:prepublish']); // Assumes it runs from rootDir + console.log('changeset:prepublish completed successfully.'); + } catch (prepublishError) { + console.error('Failed to run changeset:prepublish:', prepublishError.message); + process.exit(1); // Exit if build fails, as publishing would also fail + } + } } let hasPublishErrors = false; diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index c6dbdd49..cca20af5 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -26,7 +26,6 @@ jobs: YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | npm config set "//registry.npmjs.org/:_authToken" "$NPM_TOKEN" - yarn run changeset:prepublish node .github/scripts/publish-initial-versions.js - name: Publish Snapshots diff --git a/apps/native/app.json b/apps/native/app.json index af128602..95e1fe6d 100644 --- a/apps/native/app.json +++ b/apps/native/app.json @@ -20,12 +20,8 @@ "policy": "appVersion" }, "owner": "nacho.reown", - "assetBundlePatterns": [ - "**/*" - ], - "plugins": [ - "./expo-plugins/installed-wallets.js" - ], + "assetBundlePatterns": ["**/*"], + "plugins": ["./expo-plugins/installed-wallets.js"], "ios": { "buildNumber": "1", "bundleIdentifier": "com.walletconnect.web3modal.rnsdk", @@ -92,4 +88,4 @@ } } } -} \ No newline at end of file +} From e5d4e473f8daf4e6befaaec0ae1cf40728965be5 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 14 May 2025 11:24:50 -0300 Subject: [PATCH 38/91] chore: replaced solana lib with fetch because of polyfill issue --- packages/bitcoin/package.json | 12 +- packages/solana/package.json | 10 +- packages/solana/src/adapter.ts | 23 ++- packages/solana/src/helpers.ts | 40 ++++ yarn.lock | 361 ++------------------------------- 5 files changed, 69 insertions(+), 377 deletions(-) create mode 100644 packages/solana/src/helpers.ts diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json index 162e8b01..10b626ea 100644 --- a/packages/bitcoin/package.json +++ b/packages/bitcoin/package.json @@ -38,17 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@solana/web3.js": "1.98.2", - "bs58": "6.0.0" - }, - "peerDependencies": { - "@solana/web3.js": ">=1.90.0", - "bs58": ">=6.0.0" - }, - "devDependencies": { - "@solana/web3.js": "1.98.2", - "bs58": "6.0.0" + "@reown/appkit-common-react-native": "1.2.3" }, "react-native": "src/index.tsx" } diff --git a/packages/solana/package.json b/packages/solana/package.json index bef2c33b..5eea0626 100644 --- a/packages/solana/package.json +++ b/packages/solana/package.json @@ -38,15 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@solana/web3.js": "1.98.2" - }, - "peerDependencies": { - "@solana/web3.js": ">=1.90.0", - "bs58": ">=6.0.0" - }, - "devDependencies": { - "@solana/web3.js": "1.98.2" + "@reown/appkit-common-react-native": "1.2.3" }, "react-native": "src/index.tsx" } diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 8cd1f544..5a56cc3d 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -6,7 +6,7 @@ import { type GetBalanceParams, type GetBalanceResponse } from '@reown/appkit-common-react-native'; -import { Connection, PublicKey } from '@solana/web3.js'; +import { getSolanaBalance } from './helpers'; export class SolanaAdapter extends SolanaBaseAdapter { private static supportedNamespace: ChainNamespace = 'solana'; @@ -28,19 +28,22 @@ export class SolanaAdapter extends SolanaBaseAdapter { address || this.getAccounts()?.find(account => account.includes(network.id.toString())); if (!balanceAddress) { - return Promise.resolve({ amount: '0.00', symbol: 'SOL' }); + return { amount: '0.00', symbol: 'SOL' }; } try { - const connection = new Connection(network?.rpcUrls?.default?.http?.[0] as string); //TODO: check connection settings - const balanceAmount = await connection.getBalance( - new PublicKey(balanceAddress.split(':')[2] as string) - ); - const formattedBalance = (balanceAmount / 1000000000).toString(); //TODO: add util with LAMPORTS_PER_SOL + const rpcUrl = network.rpcUrls?.default?.http?.[0]; + if (!rpcUrl) throw new Error('No RPC URL available'); + + const base58Address = balanceAddress.split(':')[2]; + + if (!base58Address) throw new Error('Invalid balance address'); + + const amount = await getSolanaBalance(rpcUrl, base58Address); const balance = { - amount: formattedBalance, - symbol: network?.nativeCurrency.symbol || 'SOL' + amount: amount.toString(), + symbol: network.nativeCurrency?.symbol ?? 'SOL' }; this.emit('balanceChanged', { @@ -78,7 +81,7 @@ export class SolanaAdapter extends SolanaBaseAdapter { } disconnect(): Promise { - if (!this.connector) throw new Error('SolanaAdapter:disconnect - No active connector'); + if (!this.connector) throw new Error('No active connector'); return this.connector.disconnect(); } diff --git a/packages/solana/src/helpers.ts b/packages/solana/src/helpers.ts new file mode 100644 index 00000000..ddb90702 --- /dev/null +++ b/packages/solana/src/helpers.ts @@ -0,0 +1,40 @@ +/** + * Validates if the given string is a Solana address. + * @param address The string to validate. + * @returns True if the address is valid, false otherwise. + */ +export function isSolanaAddress(address: string): boolean { + const solanaAddressRegex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/; + + return solanaAddressRegex.test(address); +} + +/** + * Helper to fetch SOL balance using JSON-RPC + * @param rpcUrl Solana RPC endpoint + * @param address Solana public address (base58) + */ +export async function getSolanaBalance(rpcUrl: string, address: string): Promise { + if (!isSolanaAddress(address)) { + throw new Error('Invalid Solana address format'); + } + + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'getBalance', + params: [address] + }) + }); + + const json = (await response.json()) as { + result: { value: number }; + error?: { message: string }; + }; + if (json.error) throw new Error(json.error.message); + + return json.result.value / 1000000000; // Convert lamports to SOL +} diff --git a/yarn.lock b/yarn.lock index 4777f09f..17ec3b4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6140,15 +6140,6 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:^1.4.2, @noble/curves@npm:^1.7.0": - version: 1.9.0 - resolution: "@noble/curves@npm:1.9.0" - dependencies: - "@noble/hashes": "npm:1.8.0" - checksum: a76d57444b4d136f43363eb19229d990df15a00fb0e2efbf08a7a4cbaee655f73e46eb29b6ad07b8749be5f7b890c0a7a06a19f4324a4b149b06b3da1def8593 - languageName: node - linkType: hard - "@noble/curves@npm:^1.6.0, @noble/curves@npm:~1.6.0": version: 1.6.0 resolution: "@noble/curves@npm:1.6.0" @@ -6158,6 +6149,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.7.0": + version: 1.9.0 + resolution: "@noble/curves@npm:1.9.0" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: a76d57444b4d136f43363eb19229d990df15a00fb0e2efbf08a7a4cbaee655f73e46eb29b6ad07b8749be5f7b890c0a7a06a19f4324a4b149b06b3da1def8593 + languageName: node + linkType: hard + "@noble/hashes@npm:1.3.1": version: 1.3.1 resolution: "@noble/hashes@npm:1.3.1" @@ -7127,11 +7127,6 @@ __metadata: resolution: "@reown/appkit-bitcoin-react-native@workspace:packages/bitcoin" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" - "@solana/web3.js": "npm:1.98.2" - bs58: "npm:6.0.0" - peerDependencies: - "@solana/web3.js": ">=1.90.0" - bs58: ">=6.0.0" languageName: unknown linkType: soft @@ -7330,10 +7325,6 @@ __metadata: resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" - "@solana/web3.js": "npm:1.98.2" - peerDependencies: - "@solana/web3.js": ">=1.90.0" - bs58: ">=6.0.0" languageName: unknown linkType: soft @@ -7664,75 +7655,6 @@ __metadata: languageName: node linkType: hard -"@solana/buffer-layout@npm:^4.0.1": - version: 4.0.1 - resolution: "@solana/buffer-layout@npm:4.0.1" - dependencies: - buffer: "npm:~6.0.3" - checksum: 6535f3908cf6dfc405b665795f0c2eaa0482a8c6b1811403945cf7b450e7eb7b40acce3e8af046f2fcc3eea1a15e61d48c418315d813bee4b720d56b00053305 - languageName: node - linkType: hard - -"@solana/codecs-core@npm:2.1.0": - version: 2.1.0 - resolution: "@solana/codecs-core@npm:2.1.0" - dependencies: - "@solana/errors": "npm:2.1.0" - peerDependencies: - typescript: ">=5" - checksum: 9435aa070433733b7cfee6b98abbf59503a7e828cbb8d70ecae5f7223d21a127fc750ba4fee11f08c2211cc88f7af4a8d460bf52e20aac1308c00be87b057c56 - languageName: node - linkType: hard - -"@solana/codecs-numbers@npm:^2.1.0": - version: 2.1.0 - resolution: "@solana/codecs-numbers@npm:2.1.0" - dependencies: - "@solana/codecs-core": "npm:2.1.0" - "@solana/errors": "npm:2.1.0" - peerDependencies: - typescript: ">=5" - checksum: ae5c256e4d49f7d2eb37ad5aa52e038eb3f13d611cd5799f3d7e549b6b983571511a0d924fb287f49b687a101bb849114de4e7b29eb103a2369126e4fd3dbebb - languageName: node - linkType: hard - -"@solana/errors@npm:2.1.0": - version: 2.1.0 - resolution: "@solana/errors@npm:2.1.0" - dependencies: - chalk: "npm:^5.3.0" - commander: "npm:^13.1.0" - peerDependencies: - typescript: ">=5" - bin: - errors: bin/cli.mjs - checksum: 707f657721d5421af3d016fb876a20c6f9f9c70e9012c7ddb23c02d9f29349edee273e6d2ec219fa38dc569df4a5aa90111bfd49eef435b232e381e243c013de - languageName: node - linkType: hard - -"@solana/web3.js@npm:1.98.2": - version: 1.98.2 - resolution: "@solana/web3.js@npm:1.98.2" - dependencies: - "@babel/runtime": "npm:^7.25.0" - "@noble/curves": "npm:^1.4.2" - "@noble/hashes": "npm:^1.4.0" - "@solana/buffer-layout": "npm:^4.0.1" - "@solana/codecs-numbers": "npm:^2.1.0" - agentkeepalive: "npm:^4.5.0" - bn.js: "npm:^5.2.1" - borsh: "npm:^0.7.0" - bs58: "npm:^4.0.1" - buffer: "npm:6.0.3" - fast-stable-stringify: "npm:^1.0.0" - jayson: "npm:^4.1.1" - node-fetch: "npm:^2.7.0" - rpc-websockets: "npm:^9.0.2" - superstruct: "npm:^2.0.2" - checksum: 04230d8f9d3f1aa7665d8acf9f54342c022bd84070790909f5b6ff17d27b03e95373d3491f4a25f4ee2e10a9e82765ee541db33fd9f63be2efa49a4490bc1a0e - languageName: node - linkType: hard - "@storybook/addon-actions@npm:8.3.0": version: 8.3.0 resolution: "@storybook/addon-actions@npm:8.3.0" @@ -8271,15 +8193,6 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:^0.5.11": - version: 0.5.17 - resolution: "@swc/helpers@npm:0.5.17" - dependencies: - tslib: "npm:^2.8.0" - checksum: fe1f33ebb968558c5a0c595e54f2e479e4609bff844f9ca9a2d1ffd8dd8504c26f862a11b031f48f75c95b0381c2966c3dd156e25942f90089badd24341e7dbb - languageName: node - linkType: hard - "@tanstack/query-async-storage-persister@npm:^5.40.0": version: 5.40.0 resolution: "@tanstack/query-async-storage-persister@npm:5.40.0" @@ -8538,15 +8451,6 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:^3.4.33": - version: 3.4.38 - resolution: "@types/connect@npm:3.4.38" - dependencies: - "@types/node": "npm:*" - checksum: 2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c - languageName: node - linkType: hard - "@types/debug@npm:^4.1.7": version: 4.1.9 resolution: "@types/debug@npm:4.1.9" @@ -8763,7 +8667,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^12.12.54, @types/node@npm:^12.7.1": +"@types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" checksum: 3b190bb0410047d489c49bbaab592d2e6630de6a50f00ba3d7d513d59401d279972a8f5a598b5bb8ddc1702f8a2f4ec57a65d93852f9c329639738e7053637d1 @@ -8922,13 +8826,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^8.3.4": - version: 8.3.4 - resolution: "@types/uuid@npm:8.3.4" - checksum: b9ac98f82fcf35962317ef7dc44d9ac9e0f6fdb68121d384c88fe12ea318487d5585d3480fa003cf28be86a3bbe213ca688ba786601dce4a97724765eb5b1cf2 - languageName: node - linkType: hard - "@types/uuid@npm:^9.0.1": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -8936,24 +8833,6 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^7.4.4": - version: 7.4.7 - resolution: "@types/ws@npm:7.4.7" - dependencies: - "@types/node": "npm:*" - checksum: f1f53febd8623a85cef2652949acd19d83967e350ea15a851593e3033501750a1e04f418552e487db90a3d48611a1cff3ffcf139b94190c10f2fd1e1dc95ff10 - languageName: node - linkType: hard - -"@types/ws@npm:^8.2.2": - version: 8.18.1 - resolution: "@types/ws@npm:8.18.1" - dependencies: - "@types/node": "npm:*" - checksum: 61aff1129143fcc4312f083bc9e9e168aa3026b7dd6e70796276dcfb2c8211c4292603f9c4864fae702f2ed86e4abd4d38aa421831c2fd7f856c931a481afbab - languageName: node - linkType: hard - "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -9966,15 +9845,6 @@ __metadata: languageName: node linkType: hard -"agentkeepalive@npm:^4.5.0": - version: 4.6.0 - resolution: "agentkeepalive@npm:4.6.0" - dependencies: - humanize-ms: "npm:^1.2.1" - checksum: 235c182432f75046835b05f239708107138a40103deee23b6a08caee5136873709155753b394ec212e49e60e94a378189562cb01347765515cff61b692c69187 - languageName: node - linkType: hard - "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -10778,15 +10648,6 @@ __metadata: languageName: node linkType: hard -"base-x@npm:^3.0.2": - version: 3.0.11 - resolution: "base-x@npm:3.0.11" - dependencies: - safe-buffer: "npm:^5.0.1" - checksum: 4c5b8cd9cef285973b0460934be4fc890eedfd22a8aca527fac3527f041c5d1c912f7b9a6816f19e43e69dc7c29a5deabfa326bd3d6a57ee46af0ad46e3991d5 - languageName: node - linkType: hard - "base-x@npm:^5.0.0": version: 5.0.1 resolution: "base-x@npm:5.0.1" @@ -10911,13 +10772,6 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^5.2.0": - version: 5.2.2 - resolution: "bn.js@npm:5.2.2" - checksum: cb97827d476aab1a0194df33cd84624952480d92da46e6b4a19c32964aa01553a4a613502396712704da2ec8f831cf98d02e74ca03398404bd78a037ba93f2ab - languageName: node - linkType: hard - "body-parser@npm:1.20.3": version: 1.20.3 resolution: "body-parser@npm:1.20.3" @@ -10945,17 +10799,6 @@ __metadata: languageName: node linkType: hard -"borsh@npm:^0.7.0": - version: 0.7.0 - resolution: "borsh@npm:0.7.0" - dependencies: - bn.js: "npm:^5.2.0" - bs58: "npm:^4.0.0" - text-encoding-utf-8: "npm:^1.0.2" - checksum: 513b3e51823d2bf5be77cec27742419d2b0427504825dd7ceb00dedb820f246a4762f04b83d5e3aa39c8e075b3cbaeb7ca3c90bd1cbeecccb4a510575be8c581 - languageName: node - linkType: hard - "bowser@npm:^2.9.0": version: 2.11.0 resolution: "bowser@npm:2.11.0" @@ -11152,15 +10995,6 @@ __metadata: languageName: node linkType: hard -"bs58@npm:^4.0.0, bs58@npm:^4.0.1": - version: 4.0.1 - resolution: "bs58@npm:4.0.1" - dependencies: - base-x: "npm:^3.0.2" - checksum: 613a1b1441e754279a0e3f44d1faeb8c8e838feef81e550efe174ff021dd2e08a4c9ae5805b52dfdde79f97b5c0918c78dd24a0eb726c4a94365f0984a0ffc65 - languageName: node - linkType: hard - "bs58check@npm:^4.0.0": version: 4.0.0 resolution: "bs58check@npm:4.0.0" @@ -11211,7 +11045,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:6.0.3, buffer@npm:^6.0.3, buffer@npm:~6.0.3": +"buffer@npm:6.0.3, buffer@npm:^6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" dependencies: @@ -11231,16 +11065,6 @@ __metadata: languageName: node linkType: hard -"bufferutil@npm:^4.0.1": - version: 4.0.9 - resolution: "bufferutil@npm:4.0.9" - dependencies: - node-gyp: "npm:latest" - node-gyp-build: "npm:^4.3.0" - checksum: f8a93279fc9bdcf32b42eba97edc672b39ca0fe5c55a8596099886cffc76ea9dd78e0f6f51ecee3b5ee06d2d564aa587036b5d4ea39b8b5ac797262a363cdf7d - languageName: node - linkType: hard - "bufferutil@npm:^4.0.8": version: 4.0.8 resolution: "bufferutil@npm:4.0.8" @@ -11489,13 +11313,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.3.0": - version: 5.4.1 - resolution: "chalk@npm:5.4.1" - checksum: b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef - languageName: node - linkType: hard - "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -11858,14 +11675,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^13.1.0": - version: 13.1.0 - resolution: "commander@npm:13.1.0" - checksum: 7b8c5544bba704fbe84b7cab2e043df8586d5c114a4c5b607f83ae5060708940ed0b5bd5838cf8ce27539cde265c1cbd59ce3c8c6b017ed3eec8943e3a415164 - languageName: node - linkType: hard - -"commander@npm:^2.20.0, commander@npm:^2.20.3": +"commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 @@ -12518,13 +12328,6 @@ __metadata: languageName: node linkType: hard -"delay@npm:^5.0.0": - version: 5.0.0 - resolution: "delay@npm:5.0.0" - checksum: 01cdc4cd0cd35fb622518a3df848e67e09763a38e7cdada2232b6fda9ddda72eddcf74f0e24211200fbe718434f2335f2a2633875a6c96037fefa6de42896ad7 - languageName: node - linkType: hard - "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -13216,22 +13019,6 @@ __metadata: languageName: node linkType: hard -"es6-promise@npm:^4.0.3": - version: 4.2.8 - resolution: "es6-promise@npm:4.2.8" - checksum: 2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3 - languageName: node - linkType: hard - -"es6-promisify@npm:^5.0.0": - version: 5.0.0 - resolution: "es6-promisify@npm:5.0.0" - dependencies: - es6-promise: "npm:^4.0.3" - checksum: 23284c6a733cbf7842ec98f41eac742c9f288a78753c4fe46652bae826446ced7615b9e8a5c5f121a08812b1cd478ea58630f3e1c3d70835bd5dcd69c7cd75c9 - languageName: node - linkType: hard - "esbuild-register@npm:^3.5.0": version: 3.6.0 resolution: "esbuild-register@npm:3.6.0" @@ -14275,13 +14062,6 @@ __metadata: languageName: node linkType: hard -"eyes@npm:^0.1.8": - version: 0.1.8 - resolution: "eyes@npm:0.1.8" - checksum: 4c79a9cbf45746d8c9f48cc957e35ad8ea336add1c7b8d5a0e002efc791a7a62b27b2188184ef1a1eea7bc3cd06b161791421e0e6c5fe78309705a162c53eea8 - languageName: node - linkType: hard - "fast-base64-decode@npm:^1.0.0": version: 1.0.0 resolution: "fast-base64-decode@npm:1.0.0" @@ -14371,13 +14151,6 @@ __metadata: languageName: node linkType: hard -"fast-stable-stringify@npm:^1.0.0": - version: 1.0.0 - resolution: "fast-stable-stringify@npm:1.0.0" - checksum: 1d773440c7a9615950577665074746c2e92edafceefa789616ecb6166229e0ccc6dae206ca9b9f7da0d274ba5779162aab2d07940a0f6e52a41a4e555392eb3b - languageName: node - linkType: hard - "fast-text-encoding@npm:1.0.6": version: 1.0.6 resolution: "fast-text-encoding@npm:1.0.6" @@ -15586,15 +15359,6 @@ __metadata: languageName: node linkType: hard -"humanize-ms@npm:^1.2.1": - version: 1.2.1 - resolution: "humanize-ms@npm:1.2.1" - dependencies: - ms: "npm:^2.0.0" - checksum: f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a - languageName: node - linkType: hard - "hyphenate-style-name@npm:^1.0.3": version: 1.0.4 resolution: "hyphenate-style-name@npm:1.0.4" @@ -16290,15 +16054,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-ws@npm:^4.0.1": - version: 4.0.1 - resolution: "isomorphic-ws@npm:4.0.1" - peerDependencies: - ws: "*" - checksum: 7cb90dc2f0eb409825558982fb15d7c1d757a88595efbab879592f9d2b63820d6bbfb5571ab8abe36c715946e165a413a99f6aafd9f40ab1f514d73487bc9996 - languageName: node - linkType: hard - "isows@npm:1.0.4": version: 1.0.4 resolution: "isows@npm:1.0.4" @@ -16408,28 +16163,6 @@ __metadata: languageName: node linkType: hard -"jayson@npm:^4.1.1": - version: 4.2.0 - resolution: "jayson@npm:4.2.0" - dependencies: - "@types/connect": "npm:^3.4.33" - "@types/node": "npm:^12.12.54" - "@types/ws": "npm:^7.4.4" - commander: "npm:^2.20.3" - delay: "npm:^5.0.0" - es6-promisify: "npm:^5.0.0" - eyes: "npm:^0.1.8" - isomorphic-ws: "npm:^4.0.1" - json-stringify-safe: "npm:^5.0.1" - stream-json: "npm:^1.9.1" - uuid: "npm:^8.3.2" - ws: "npm:^7.5.10" - bin: - jayson: bin/jayson.js - checksum: 062f525a0d15232c4361d10e0cd26960e998897e483408de03101e147c7bdf275db525bc1d5cc8aff4b777d1b1389004c8e9a5715304aedcf9930557787df6e3 - languageName: node - linkType: hard - "jest-changed-files@npm:^29.7.0": version: 29.7.0 resolution: "jest-changed-files@npm:29.7.0" @@ -17148,13 +16881,6 @@ __metadata: languageName: node linkType: hard -"json-stringify-safe@npm:^5.0.1": - version: 5.0.1 - resolution: "json-stringify-safe@npm:5.0.1" - checksum: 7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 - languageName: node - linkType: hard - "json5@npm:^2.1.1, json5@npm:^2.2.1, json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -18671,7 +18397,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 @@ -18839,7 +18565,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.5.0, node-fetch@npm:^2.7.0": +"node-fetch@npm:^2.5.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -21360,28 +21086,6 @@ __metadata: languageName: node linkType: hard -"rpc-websockets@npm:^9.0.2": - version: 9.1.1 - resolution: "rpc-websockets@npm:9.1.1" - dependencies: - "@swc/helpers": "npm:^0.5.11" - "@types/uuid": "npm:^8.3.4" - "@types/ws": "npm:^8.2.2" - buffer: "npm:^6.0.3" - bufferutil: "npm:^4.0.1" - eventemitter3: "npm:^5.0.1" - utf-8-validate: "npm:^5.0.2" - uuid: "npm:^8.3.2" - ws: "npm:^8.5.0" - dependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: c1c9d78e90bf1c9c9f0607d924f4ff00e42d5b27b76053a384ae37479656912f06b726c1bbc463dbb8b420fcc1bbcaca487db35227a04ecd55e39ecff5cd5653 - languageName: node - linkType: hard - "run-applescript@npm:^5.0.0": version: 5.0.0 resolution: "run-applescript@npm:5.0.0" @@ -22044,22 +21748,6 @@ __metadata: languageName: node linkType: hard -"stream-chain@npm:^2.2.5": - version: 2.2.5 - resolution: "stream-chain@npm:2.2.5" - checksum: c512f50190d7c92d688fa64e7af540c51b661f9c2b775fc72bca38ea9bca515c64c22c2197b1be463741daacbaaa2dde8a8ea24ebda46f08391224f15249121a - languageName: node - linkType: hard - -"stream-json@npm:^1.9.1": - version: 1.9.1 - resolution: "stream-json@npm:1.9.1" - dependencies: - stream-chain: "npm:^2.2.5" - checksum: 0521e5cb3fb6b0e2561d715975e891bd81fa77d0239c8d0b1756846392bc3c7cdd7f1ddb0fe7ed77e6fdef58daab9e665d3b39f7d677bd0859e65a2bff59b92c - languageName: node - linkType: hard - "stream-shift@npm:^1.0.0": version: 1.0.1 resolution: "stream-shift@npm:1.0.1" @@ -22359,13 +22047,6 @@ __metadata: languageName: node linkType: hard -"superstruct@npm:^2.0.2": - version: 2.0.2 - resolution: "superstruct@npm:2.0.2" - checksum: c6853db5240b4920f47b3c864dd1e23ede6819ea399ad29a65387d746374f6958c5f1c5b7e5bb152d9db117a74973e5005056d9bb83c24e26f18ec6bfae4a718 - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -22586,13 +22267,6 @@ __metadata: languageName: node linkType: hard -"text-encoding-utf-8@npm:^1.0.2": - version: 1.0.2 - resolution: "text-encoding-utf-8@npm:1.0.2" - checksum: 87a64b394c850e8387c2ca7fc6929a26ce97fb598f1c55cd0fdaec4b8e2c3ed6770f65b2f3309c9175ef64ac5e403c8e48b53ceeb86d2897940c5e19cc00bb99 - languageName: node - linkType: hard - "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -22886,13 +22560,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.8.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 - languageName: node - linkType: hard - "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" From 26aaecaaf780118644bf512132c50c8316783334 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 15 May 2025 10:13:36 -0300 Subject: [PATCH 39/91] chore: checking for appkit instance in exported hooks --- packages/appkit/src/hooks/useAccount.ts | 3 +++ packages/appkit/src/hooks/useAppKitEvents.ts | 13 +++---------- packages/appkit/src/hooks/useWalletInfo.ts | 9 +++------ 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/appkit/src/hooks/useAccount.ts b/packages/appkit/src/hooks/useAccount.ts index 545e7f7b..fb39ec6d 100644 --- a/packages/appkit/src/hooks/useAccount.ts +++ b/packages/appkit/src/hooks/useAccount.ts @@ -1,7 +1,10 @@ import { useSnapshot } from 'valtio'; import { ConnectionsController } from '@reown/appkit-core-react-native'; +import { useAppKit } from './useAppKit'; export function useAccount() { + useAppKit(); // Use the hook for checks + const { activeAddress: address, activeNamespace, diff --git a/packages/appkit/src/hooks/useAppKitEvents.ts b/packages/appkit/src/hooks/useAppKitEvents.ts index d5f510bd..e01c5d4b 100644 --- a/packages/appkit/src/hooks/useAppKitEvents.ts +++ b/packages/appkit/src/hooks/useAppKitEvents.ts @@ -2,19 +2,15 @@ import { useEffect } from 'react'; import { useSnapshot } from 'valtio'; import { EventsController, - OptionsController, type EventName, type EventsControllerState } from '@reown/appkit-core-react-native'; +import { useAppKit } from './useAppKit'; export function useAppKitEvents(callback?: (newEvent: EventsControllerState) => void) { - const { projectId } = useSnapshot(OptionsController.state); + useAppKit(); // Use the hook for checks const { data, timestamp } = useSnapshot(EventsController.state); - if (!projectId) { - throw new Error('Please call "createAppKit" before using "useAppKitEvents" hook'); - } - useEffect(() => { const unsubscribe = EventsController.subscribe(newEvent => { callback?.(newEvent); @@ -32,10 +28,7 @@ export function useAppKitEventSubscription( event: EventName, callback: (newEvent: EventsControllerState) => void ) { - const { projectId } = useSnapshot(OptionsController.state); - if (!projectId) { - throw new Error('Please call "createAppKit" before using "useAppKitEventSubscription" hook'); - } + useAppKit(); // Use the hook for checks useEffect(() => { const unsubscribe = EventsController?.subscribeEvent(event, callback); diff --git a/packages/appkit/src/hooks/useWalletInfo.ts b/packages/appkit/src/hooks/useWalletInfo.ts index faba92ee..82742316 100644 --- a/packages/appkit/src/hooks/useWalletInfo.ts +++ b/packages/appkit/src/hooks/useWalletInfo.ts @@ -1,13 +1,10 @@ import { useSnapshot } from 'valtio'; -import { ConnectionsController, OptionsController } from '@reown/appkit-core-react-native'; +import { ConnectionsController } from '@reown/appkit-core-react-native'; +import { useAppKit } from './useAppKit'; export function useWalletInfo() { - const { projectId } = useSnapshot(OptionsController.state); + useAppKit(); // Use the hook for checks const { walletInfo } = useSnapshot(ConnectionsController.state); - if (!projectId) { - throw new Error('Please call "createAppKit" before using "useWalletInfo" hook'); - } - return { walletInfo }; } From d08f6cb8b7e78384f7be95926a66ad33386d42fe Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 15 May 2025 15:47:43 -0300 Subject: [PATCH 40/91] chore: removed ethers as a dependency for ethers adapter --- packages/bitcoin/package.json | 4 ++-- packages/ethers/package.json | 4 ---- packages/ethers/src/adapter.ts | 28 +++++++++++----------------- packages/ethers/src/helpers.ts | 25 +++++++++++++++++++++++++ tsconfig.json | 2 +- 5 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 packages/ethers/src/helpers.ts diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json index 10b626ea..33339838 100644 --- a/packages/bitcoin/package.json +++ b/packages/bitcoin/package.json @@ -5,6 +5,7 @@ "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", "source": "src/index.tsx", + "react-native": "src/index.tsx", "scripts": { "build": "bob build", "clean": "rm -rf lib", @@ -39,6 +40,5 @@ }, "dependencies": { "@reown/appkit-common-react-native": "1.2.3" - }, - "react-native": "src/index.tsx" + } } diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 91533f1d..98b610f9 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -48,13 +48,9 @@ "@react-native-async-storage/async-storage": ">=1.17.0", "@react-native-community/netinfo": "*", "@walletconnect/react-native-compat": ">=2.13.1", - "ethers": ">=6.0.0", "react": ">=17", "react-native": ">=0.68.5", "react-native-get-random-values": "*" }, - "devDependencies": { - "ethers": "6.10.0" - }, "react-native": "src/index.tsx" } diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 3d8dfca5..c276fdc3 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -1,4 +1,3 @@ -import { formatEther, JsonRpcProvider } from 'ethers'; import { EVMAdapter, type AppKitNetwork, @@ -8,6 +7,7 @@ import { type GetBalanceResponse } from '@reown/appkit-common-react-native'; import { EthersHelpersUtil } from '@reown/appkit-scaffold-utils-react-native'; +import { formatEther, getEthBalance } from './helpers'; export class EthersAdapter extends EVMAdapter { private static supportedNamespace: ChainNamespace = 'eip155'; @@ -28,26 +28,20 @@ export class EthersAdapter extends EVMAdapter { const balanceAddress = address || this.getAccounts()?.find(account => account.includes(network.id.toString())); - let balance = { amount: '0.00', symbol: network.nativeCurrency.symbol || 'ETH' }; + const balance: GetBalanceResponse = { + amount: '0.00', + symbol: network.nativeCurrency.symbol || 'ETH' + }; - if (!balanceAddress) { - return Promise.resolve(balance); - } + if (!balanceAddress) return balance; const account = balanceAddress.split(':')[2]; + const rpcUrl = network.rpcUrls.default.http?.[0]; + if (!rpcUrl || !account) return balance; try { - const jsonRpcProvider = new JsonRpcProvider(network.rpcUrls.default.http[0], { - chainId: Number(network.id), - name: network.name - }); - - if (jsonRpcProvider && account) { - const _balance = await jsonRpcProvider.getBalance(account); - const formattedBalance = formatEther(_balance); - - balance = { amount: formattedBalance, symbol: network.nativeCurrency.symbol || 'ETH' }; - } + const wei = await getEthBalance(rpcUrl, account); + balance.amount = formatEther(wei); this.emit('balanceChanged', { namespace: this.getSupportedNamespace(), @@ -56,7 +50,7 @@ export class EthersAdapter extends EVMAdapter { }); return balance; - } catch (error) { + } catch { return balance; } } diff --git a/packages/ethers/src/helpers.ts b/packages/ethers/src/helpers.ts new file mode 100644 index 00000000..408e3838 --- /dev/null +++ b/packages/ethers/src/helpers.ts @@ -0,0 +1,25 @@ +// Helper to convert Wei (as string or bigint) to ETH +export const formatEther = (wei: bigint): string => { + return (Number(wei) / 1e18).toString(); +}; + +// Raw JSON-RPC for balance lookup +export async function getEthBalance(rpcUrl: string, address: string): Promise { + const body = { + jsonrpc: '2.0', + method: 'eth_getBalance', + params: [address, 'latest'], + id: 1 + }; + + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + + const json = await response.json(); + if (json.error) throw new Error(json.error.message); + + return BigInt(json.result); // result is hex string +} diff --git a/tsconfig.json b/tsconfig.json index d7ff00e5..3ab69898 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "isolatedModules": true, "forceConsistentCasingInFileNames": true, "jsx": "react-jsx", - "lib": ["esnext"], + "lib": ["esnext", "dom"], "module": "esnext", "moduleResolution": "node", "noFallthroughCasesInSwitch": true, From 3c8618fd91a6127da633bef77b2dbba3fb4d6a8e Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 15 May 2025 16:00:47 -0300 Subject: [PATCH 41/91] chore: enable activity list only for solana and evm --- .../src/partials/w3m-account-activity/index.tsx | 14 +++++++++++++- .../src/views/w3m-account-default-view/index.tsx | 6 ++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/appkit/src/partials/w3m-account-activity/index.tsx b/packages/appkit/src/partials/w3m-account-activity/index.tsx index 7bae4d4c..dbbfa357 100644 --- a/packages/appkit/src/partials/w3m-account-activity/index.tsx +++ b/packages/appkit/src/partials/w3m-account-activity/index.tsx @@ -32,8 +32,9 @@ export function AccountActivity({ style }: Props) { const [refreshing, setRefreshing] = useState(false); const [initialLoad, setInitialLoad] = useState(true); const { loading, transactions, next } = useSnapshot(TransactionsController.state); - const { activeNetwork } = useSnapshot(ConnectionsController.state); + const { activeNetwork, activeNamespace } = useSnapshot(ConnectionsController.state); const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); + const isSupported = activeNamespace && ['eip155', 'solana'].includes(activeNamespace); const handleLoadMore = () => { const address = ConnectionsController.state.activeAddress?.split(':')[2]; @@ -81,6 +82,17 @@ export function AccountActivity({ style }: Props) { ); } + if (!isSupported) { + return ( + + ); + } + // Only show placeholder when we're not in initial load or loading state if (!Object.keys(transactionsByYear).length && !loading && !initialLoad) { return ( diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index b27a87df..77c353aa 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -43,7 +43,8 @@ export function AccountDefaultView() { const { activeAddress: address, activeBalance: balance, - activeNetwork + activeNetwork, + activeNamespace } = useSnapshot(ConnectionsController.state); const account = address?.split(':')[2]; const [disconnecting, setDisconnecting] = useState(false); @@ -58,6 +59,7 @@ export function AccountDefaultView() { const showExplorer = Object.keys(activeNetwork?.blockExplorers ?? {}).length > 0 && !isAuth; const showBack = history.length > 1; const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); + const showActivity = !isAuth && activeNamespace && ['eip155', 'solana'].includes(activeNamespace); const { padding } = useCustomDimensions(); const { disconnect } = useAppKit(); @@ -287,7 +289,7 @@ export function AccountDefaultView() { Swap )} - {!isAuth && ( + {showActivity && ( Date: Thu, 15 May 2025 16:54:20 -0300 Subject: [PATCH 42/91] chore: fixed switchchain on wagmi connector --- apps/native/src/views/WagmiActionsView.tsx | 6 +----- .../appkit/src/connectors/WalletConnectConnector.ts | 7 +++++-- packages/wagmi/src/connectors/UniversalConnector.ts | 11 +++++++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/native/src/views/WagmiActionsView.tsx b/apps/native/src/views/WagmiActionsView.tsx index 7e201997..cd0884b1 100644 --- a/apps/native/src/views/WagmiActionsView.tsx +++ b/apps/native/src/views/WagmiActionsView.tsx @@ -38,11 +38,7 @@ export function WagmiActionsView() { const { data: gas, isError: isGasError } = useEstimateGas(TX); - const { - isPending: isSending, - - sendTransaction - } = useSendTransaction({ + const { isPending: isSending, sendTransaction } = useSendTransaction({ mutation: { onSuccess: onSendSuccess, onError: onSendError diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 03fff25c..dd6a28e4 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -101,8 +101,11 @@ export class WalletConnectConnector extends WalletConnector { } override switchNetwork(network: AppKitNetwork): Promise { - if (!network.caipNetworkId) throw new Error('No network provided'); - (this.provider as IUniversalProvider).setDefaultChain(network.caipNetworkId); + if (!network) throw new Error('No network provided'); + + let caipNetworkId = network.caipNetworkId ?? `eip155:${network.id}`; + + (this.provider as IUniversalProvider).setDefaultChain(caipNetworkId); return Promise.resolve(); } diff --git a/packages/wagmi/src/connectors/UniversalConnector.ts b/packages/wagmi/src/connectors/UniversalConnector.ts index 1e1a09d9..d4413501 100644 --- a/packages/wagmi/src/connectors/UniversalConnector.ts +++ b/packages/wagmi/src/connectors/UniversalConnector.ts @@ -165,16 +165,22 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { const _provider = await this.getProvider(); if (!_provider) throw new Error('Provider not available for switching chain.'); const currentChainId = await this.getChainId(); - if (currentChainId === chainId) return config.chains.find(c => c.id === chainId) as Chain; + const newChain = config.chains.find(c => c.id === chainId) as Chain; + + if (!newChain) throw new Error('Chain not found'); + + if (currentChainId === chainId) return newChain; try { await _provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: numberToHex(chainId) }] }); + + await appKitProvidedConnector.switchNetwork(newChain); config.emitter.emit('change', { chainId }); - return config.chains.find(c => c.id === chainId) as Chain; + return newChain; } catch (error) { const chain = config.chains.find(c => c.id === chainId); // Check if chain is not configured @@ -196,6 +202,7 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { } ] }); + await appKitProvidedConnector.switchNetwork(newChain); config.emitter.emit('change', { chainId }); return chain; From 63dd67e61a1ee689e7cddf0751930f731ed49d0c Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 15 May 2025 16:55:18 -0300 Subject: [PATCH 43/91] chore: removed request method from adapters --- packages/bitcoin/src/adapter.ts | 7 ------- packages/common/src/utils/TypeUtil.ts | 1 - packages/core/src/utils/FetchUtil.ts | 2 +- packages/ethers/src/adapter.ts | 7 ------- packages/solana/src/adapter.ts | 7 ------- packages/wagmi/src/adapter.ts | 9 ++------- 6 files changed, 3 insertions(+), 30 deletions(-) diff --git a/packages/bitcoin/src/adapter.ts b/packages/bitcoin/src/adapter.ts index 05bac8ed..e38e8992 100644 --- a/packages/bitcoin/src/adapter.ts +++ b/packages/bitcoin/src/adapter.ts @@ -87,13 +87,6 @@ export class BitcoinAdapter extends BlockchainAdapter { return this.connector.disconnect(); } - async request(method: string, params?: any[]) { - if (!this.connector) throw new Error('No active connector'); - const provider = this.connector.getProvider(); - - return provider.request({ method, params }); - } - getSupportedNamespace(): ChainNamespace { return BitcoinAdapter.supportedNamespace; } diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 669c2f9e..e80379f2 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -212,7 +212,6 @@ export abstract class BlockchainAdapter extends EventEmitter { } abstract disconnect(): Promise; - abstract request(method: string, params?: any[]): Promise; abstract getSupportedNamespace(): ChainNamespace; abstract getBalance(params: GetBalanceParams): Promise; abstract getAccounts(): CaipAddress[] | undefined; diff --git a/packages/core/src/utils/FetchUtil.ts b/packages/core/src/utils/FetchUtil.ts index 7f0ee6dd..db9aed88 100644 --- a/packages/core/src/utils/FetchUtil.ts +++ b/packages/core/src/utils/FetchUtil.ts @@ -8,7 +8,7 @@ interface Options { interface RequestArguments { path: string; - headers?: HeadersInit_; + headers?: HeadersInit; params?: Record; cache?: RequestCache; signal?: AbortSignal; diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index c276fdc3..718bc24f 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -92,13 +92,6 @@ export class EthersAdapter extends EVMAdapter { return this.connector.disconnect(); } - async request(method: string, params?: any[]) { - if (!this.connector) throw new Error('No active connector'); - const provider = this.connector.getProvider(); - - return provider.request({ method, params }); - } - getSupportedNamespace(): ChainNamespace { return EthersAdapter.supportedNamespace; } diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 5a56cc3d..fe4db15e 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -86,13 +86,6 @@ export class SolanaAdapter extends SolanaBaseAdapter { return this.connector.disconnect(); } - async request(method: string, params?: any[]) { - if (!this.connector) throw new Error('No active connector'); - const provider = this.connector.getProvider(); - - return provider.request({ method, params }); - } - getSupportedNamespace(): ChainNamespace { return SolanaAdapter.supportedNamespace; } diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index d20bd32d..a217fb80 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -68,6 +68,8 @@ export class WagmiAdapter extends EVMAdapter { throw new Error('WagmiAdapter: AppKit connector not set or not connected via Wagmi.'); } + await this.wagmiConfigConnector?.switchChain?.({ chainId: network.id as number }); + await switchChainWagmi(this.wagmiConfig, { chainId: network.id as number, connector: this.wagmiConfigConnector @@ -149,13 +151,6 @@ export class WagmiAdapter extends EVMAdapter { } } - async request(method: string, params?: any[]) { - if (!this.connector) throw new Error('WagmiAdapter: No active AppKit connector'); - const provider = this.connector.getProvider(); - - return provider.request({ method, params }); - } - getSupportedNamespace(): ChainNamespace { return WagmiAdapter.supportedNamespace; } From 5a2ee6ecc8fd4582bd5b85693c618e53b6f77e20 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 15 May 2025 17:22:25 -0300 Subject: [PATCH 44/91] chore: added types to events --- packages/common/src/utils/TypeUtil.ts | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index e80379f2..e594a7ac 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -139,12 +139,53 @@ export type Tokens = Record; export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; +//********** Adapter Event Payloads **********// +export type AccountsChangedEvent = { + accounts: string[]; + namespace: ChainNamespace; +}; + +export type ChainChangedEvent = { + chainId: string; + namespace: ChainNamespace; +}; + +export type DisconnectEvent = { + namespace: ChainNamespace; +}; + +export type BalanceChangedEvent = { + namespace: ChainNamespace; + address: CaipAddress; + balance: { + amount: string; + symbol: string; + contractAddress?: ContractAddress; + }; +}; + +//********** Adapter Event Map **********// +export interface AdapterEvents { + accountsChanged: (event: AccountsChangedEvent) => void; + chainChanged: (event: ChainChangedEvent) => void; + disconnect: (event: DisconnectEvent) => void; + balanceChanged: (event: BalanceChangedEvent) => void; +} + //********** Adapter Types **********// export abstract class BlockchainAdapter extends EventEmitter { public projectId: string; public connector?: WalletConnector; public supportedNamespace: ChainNamespace; + // Typed emit method + override emit( + event: K, + payload: Parameters[0] + ): boolean { + return super.emit(event, payload); + } + constructor({ projectId, supportedNamespace From e31429d6a04cae07c4c5d12def620d8cd19524bc Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 16 May 2025 08:53:11 -0300 Subject: [PATCH 45/91] chore: updated lockfile --- apps/native/src/views/EventsView.tsx | 4 +++- packages/appkit/src/hooks/useAppKit.ts | 8 ++++++- yarn.lock | 31 -------------------------- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/apps/native/src/views/EventsView.tsx b/apps/native/src/views/EventsView.tsx index 4733074e..ffc06f4f 100644 --- a/apps/native/src/views/EventsView.tsx +++ b/apps/native/src/views/EventsView.tsx @@ -1,4 +1,4 @@ -import { useAppKitEvents, useAppKitEventSubscription } from '@reown/appkit-react-native'; +import { useAppKitEvents, useAppKitEventSubscription, useAppKit } from '@reown/appkit-react-native'; import { FlexView, Text } from '@reown/appkit-ui-react-native'; import { useState } from 'react'; import { type ViewStyle, type StyleProp, StyleSheet } from 'react-native'; @@ -9,6 +9,7 @@ interface Props { export function EventsView({ style }: Props) { const { data } = useAppKitEvents(); + const { isOpen } = useAppKit(); const [eventCount, setEventCount] = useState(0); useAppKitEventSubscription('MODAL_OPEN', () => { @@ -22,6 +23,7 @@ export function EventsView({ style }: Props) { Last event: {data?.event} Modal open count: {eventCount} + Modal is open: {isOpen ? 'Yes' : 'No'} ) : null; } diff --git a/packages/appkit/src/hooks/useAppKit.ts b/packages/appkit/src/hooks/useAppKit.ts index cdd4bc73..116d880d 100644 --- a/packages/appkit/src/hooks/useAppKit.ts +++ b/packages/appkit/src/hooks/useAppKit.ts @@ -1,4 +1,7 @@ import { useContext } from 'react'; +import { useSnapshot } from 'valtio'; +import { ModalController } from '@reown/appkit-core-react-native'; + import type { AppKit } from '../AppKit'; import { AppKitContext } from '../AppKitContext'; @@ -7,10 +10,12 @@ interface UseAppKitReturn { close: AppKit['close']; disconnect: (namespace?: string) => void; switchNetwork: AppKit['switchNetwork']; + isOpen: boolean; } export const useAppKit = (): UseAppKitReturn => { const context = useContext(AppKitContext); + const { open } = useSnapshot(ModalController.state); if (context === undefined) { throw new Error('useAppKit must be used within an AppKitProvider'); @@ -24,6 +29,7 @@ export const useAppKit = (): UseAppKitReturn => { open: context.appKit.open.bind(context.appKit), close: context.appKit.close.bind(context.appKit), disconnect: (namespace?: string) => context.appKit?.disconnect.bind(context.appKit)(namespace), - switchNetwork: context.appKit.switchNetwork.bind(context.appKit) + switchNetwork: context.appKit.switchNetwork.bind(context.appKit), + isOpen: open }; }; diff --git a/yarn.lock b/yarn.lock index 17ec3b4c..b6170264 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7209,12 +7209,10 @@ __metadata: "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" "@reown/appkit-siwe-react-native": "npm:1.2.3" "@walletconnect/ethereum-provider": "npm:2.20.2" - ethers: "npm:6.10.0" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" "@react-native-community/netinfo": "*" "@walletconnect/react-native-compat": ">=2.13.1" - ethers: ">=6.0.0" react: ">=17" react-native: ">=0.68.5" react-native-get-random-values: "*" @@ -8651,13 +8649,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18.15.13": - version: 18.15.13 - resolution: "@types/node@npm:18.15.13" - checksum: 6e5f61c559e60670a7a8fb88e31226ecc18a21be103297ca4cf9848f0a99049dae77f04b7ae677205f2af494f3701b113ba8734f4b636b355477a6534dbb8ada - languageName: node - linkType: hard - "@types/node@npm:22.7.5": version: 22.7.5 resolution: "@types/node@npm:22.7.5" @@ -13576,21 +13567,6 @@ __metadata: languageName: node linkType: hard -"ethers@npm:6.10.0": - version: 6.10.0 - resolution: "ethers@npm:6.10.0" - dependencies: - "@adraffy/ens-normalize": "npm:1.10.0" - "@noble/curves": "npm:1.2.0" - "@noble/hashes": "npm:1.3.2" - "@types/node": "npm:18.15.13" - aes-js: "npm:4.0.0-beta.5" - tslib: "npm:2.4.0" - ws: "npm:8.5.0" - checksum: 8816249426609a10aadef1a45cab5e2c34db533317557f29fa69ce02cb04be5018079e6d8685f8967d654d375917bae00288f74a13873f93058e5ef39b8a6106 - languageName: node - linkType: hard - "ethers@npm:6.13.5": version: 6.13.5 resolution: "ethers@npm:6.13.5" @@ -22539,13 +22515,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.4.0": - version: 2.4.0 - resolution: "tslib@npm:2.4.0" - checksum: eb19bda3ae545b03caea6a244b34593468e23d53b26bf8649fbc20fce43e9b21a71127fd6d2b9662c0fe48ee6ff668ead48fd00d3b88b2b716b1c12edae25b5d - languageName: node - linkType: hard - "tslib@npm:2.7.0": version: 2.7.0 resolution: "tslib@npm:2.7.0" From b3eed6e3a5967526f684fd303770bcdf1dfbaf33 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 16 May 2025 15:21:03 -0300 Subject: [PATCH 46/91] chore: solved wagmi issue when switching chain --- packages/appkit/src/AppKit.ts | 3 - packages/common/src/utils/TypeUtil.ts | 11 +++- packages/ethers/src/adapter.ts | 2 +- packages/solana/src/adapter.ts | 27 +++++--- packages/solana/src/helpers.ts | 62 ++++++++++++++++++- packages/wagmi/src/adapter.ts | 7 +-- .../src/connectors/UniversalConnector.ts | 55 ++++++++-------- 7 files changed, 118 insertions(+), 49 deletions(-) diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index f7eee274..1d292a24 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -211,8 +211,6 @@ export class AppKit { if (ConnectionsController.state.activeNamespace !== (network.chainNamespace ?? 'eip155')) { ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); } - - // adapter.getBalance({ network, tokens: this.config.tokens }); } open(options?: OpenOptions) { @@ -372,7 +370,6 @@ export class AppKit { }); adapter.on('chainChanged', ({ chainId, namespace }) => { - // console.log('chainChanged', chainId, namespace); const chain = `${namespace}:${chainId}` as CaipNetworkId; ConnectionsController.setActiveChain(namespace, chain); diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index e594a7ac..10faed0a 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -6,7 +6,7 @@ export type CaipNetworkId = `${string}:${string}`; export type ChainNamespace = 'eip155' | 'solana' | 'polkadot' | 'bip122'; -export type AppKitNetwork = { +export type Network = { // Core viem/chain properties id: number | string; name: string; @@ -19,7 +19,9 @@ export type AppKitNetwork = { default: { name: string; url: string }; [key: string]: { name: string; url: string } | undefined; }; +}; +export type AppKitNetwork = Network & { // AppKit specific / CAIP properties (Optional in type, but often needed in practice) chainNamespace?: ChainNamespace; // e.g., 'eip155' caipNetworkId?: CaipNetworkId; // e.g., 'eip155:1' @@ -223,7 +225,12 @@ export abstract class BlockchainAdapter extends EventEmitter { } onChainChanged(chainId: string): void { - this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + const _chains = this.getAccounts()?.map(account => account.split(':')[1]); + const shouldEmit = _chains?.some(chain => chain === chainId); + + if (shouldEmit) { + this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + } } onAccountsChanged(accounts: string[]): void { diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 718bc24f..84ce97bc 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -67,7 +67,7 @@ export class EthersAdapter extends EVMAdapter { method: 'wallet_switchEthereumChain', params: [{ chainId: EthersHelpersUtil.numberToHexString(Number(network.id)) }] //TODO: check util }, - `${network.chainNamespace ?? 'eip155'}:${network.id}` + `${EthersAdapter.supportedNamespace}:${network.id}` ); } catch (switchError: any) { const message = switchError?.message as string; diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index fe4db15e..e8a79dcf 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -6,7 +6,7 @@ import { type GetBalanceParams, type GetBalanceResponse } from '@reown/appkit-common-react-native'; -import { getSolanaBalance } from './helpers'; +import { getSolanaNativeBalance, getSolanaTokenBalance } from './helpers'; export class SolanaAdapter extends SolanaBaseAdapter { private static supportedNamespace: ChainNamespace = 'solana'; @@ -19,7 +19,8 @@ export class SolanaAdapter extends SolanaBaseAdapter { } async getBalance(params: GetBalanceParams): Promise { - const { network, address } = params; + console.log('solana getBalance'); + const { network, address, tokens } = params; if (!this.connector) throw new Error('No active connector'); if (!network) throw new Error('No network provided'); @@ -39,12 +40,22 @@ export class SolanaAdapter extends SolanaBaseAdapter { if (!base58Address) throw new Error('Invalid balance address'); - const amount = await getSolanaBalance(rpcUrl, base58Address); - - const balance = { - amount: amount.toString(), - symbol: network.nativeCurrency?.symbol ?? 'SOL' - }; + const token = network?.caipNetworkId && tokens?.[network.caipNetworkId]?.address; + let balance; + + if (token) { + const { amount, symbol } = await getSolanaTokenBalance(rpcUrl, base58Address, token); + balance = { + amount, + symbol + }; + } else { + const amount = await getSolanaNativeBalance(rpcUrl, base58Address); + balance = { + amount: amount.toString(), + symbol: 'SOL' + }; + } this.emit('balanceChanged', { namespace: this.getSupportedNamespace(), diff --git a/packages/solana/src/helpers.ts b/packages/solana/src/helpers.ts index ddb90702..fe9cac9a 100644 --- a/packages/solana/src/helpers.ts +++ b/packages/solana/src/helpers.ts @@ -1,3 +1,11 @@ +export interface TokenInfo { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI?: string; +} + /** * Validates if the given string is a Solana address. * @param address The string to validate. @@ -14,7 +22,7 @@ export function isSolanaAddress(address: string): boolean { * @param rpcUrl Solana RPC endpoint * @param address Solana public address (base58) */ -export async function getSolanaBalance(rpcUrl: string, address: string): Promise { +export async function getSolanaNativeBalance(rpcUrl: string, address: string): Promise { if (!isSolanaAddress(address)) { throw new Error('Invalid Solana address format'); } @@ -38,3 +46,55 @@ export async function getSolanaBalance(rpcUrl: string, address: string): Promise return json.result.value / 1000000000; // Convert lamports to SOL } + +let tokenCache: Record = {}; +/** + * Fetch metadata for a Solana SPL token using the Jupiter token list. + * @param mint - The token's mint address + * @returns TokenInfo if found, or undefined + */ +export async function getSolanaTokenMetadata(mint: string): Promise { + // Return from cache if available + if (tokenCache[mint]) return tokenCache[mint]; + + try { + const res = await fetch('https://token.jup.ag/all'); + const list: TokenInfo[] = await res.json(); + + for (const token of list) { + tokenCache[token.address] = token; + } + + return tokenCache[mint]; + } catch (error) { + return undefined; + } +} + +export async function getSolanaTokenBalance( + rpcUrl: string, + address: string, + tokenAddress: string +): Promise<{ amount: string; symbol: string }> { + if (!isSolanaAddress(address)) { + throw new Error('Invalid Solana address format'); + } + + const token = await getSolanaTokenMetadata(tokenAddress); + + const response = await fetch(rpcUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + jsonrpc: '2.0', + id: 1, + method: 'getTokenAccountsByOwner', + params: [address, { mint: tokenAddress }, { encoding: 'jsonParsed' }] + }) + }); + + const result = await response.json(); + const balance = result.result.value[0]?.account?.data?.parsed?.info?.tokenAmount?.uiAmount; + + return { amount: balance?.toString() ?? '0', symbol: token?.symbol ?? 'SOL' }; +} diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index a217fb80..e7c0b0b3 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -68,8 +68,6 @@ export class WagmiAdapter extends EVMAdapter { throw new Error('WagmiAdapter: AppKit connector not set or not connected via Wagmi.'); } - await this.wagmiConfigConnector?.switchChain?.({ chainId: network.id as number }); - await switchChainWagmi(this.wagmiConfig, { chainId: network.id as number, connector: this.wagmiConfigConnector @@ -77,7 +75,7 @@ export class WagmiAdapter extends EVMAdapter { } async getBalance(params: GetBalanceParams): Promise { - const { network, address } = params; + const { network, address, tokens } = params; if (!this.connector) throw new Error('No active AppKit connector (EVMAdapter.connector)'); if (!network) throw new Error('No network provided'); @@ -96,8 +94,7 @@ export class WagmiAdapter extends EVMAdapter { const accountHex = balanceAddress.split(':')[2] as Hex; - const token = - network?.caipNetworkId && (params.tokens?.[network.caipNetworkId]?.address as Hex); + const token = network?.caipNetworkId && (tokens?.[network.caipNetworkId]?.address as Hex); const balance = await getBalanceWagmi(this.wagmiConfig, { address: accountHex, diff --git a/packages/wagmi/src/connectors/UniversalConnector.ts b/packages/wagmi/src/connectors/UniversalConnector.ts index d4413501..d9e13c48 100644 --- a/packages/wagmi/src/connectors/UniversalConnector.ts +++ b/packages/wagmi/src/connectors/UniversalConnector.ts @@ -1,4 +1,8 @@ -import type { Provider, WalletConnector } from '@reown/appkit-common-react-native'; +import type { + Provider, + RequestArguments, + WalletConnector +} from '@reown/appkit-common-react-native'; import { getAddress, numberToHex, @@ -26,7 +30,6 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { async setup() { provider = appKitProvidedConnector.getProvider(); - // appkitConnector = appKitProvidedConnector; if (provider?.on) { accountsChangedHandler = (accounts: string[]) => { const hexAccounts = accounts.map(acc => getAddress(acc)); @@ -114,18 +117,10 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { }, async getChainId() { - const _provider = appKitProvidedConnector.getProvider(); - if (_provider) { - try { - const chainId = (await _provider.request({ - method: 'eth_chainId' - })) as string; - - return parseInt(chainId, 10); - } catch (e) { - // console.warn("Could not get chainId from provider", e); - } - } + const chainId = appKitProvidedConnector.getChainId('eip155')?.split(':')[1]; + + if (chainId) return parseInt(chainId, 10); + // Fallback: Try to get from CAIP accounts if available const namespaces = appKitProvidedConnector.getNamespaces(); // @ts-ignore @@ -148,7 +143,17 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { provider = appKitProvidedConnector.getProvider(); } - return Promise.resolve(provider); + const chainId = await this.getChainId(); + + //TODO: Review this with gancho + const _provider = { + ...provider, + request: (args: RequestArguments) => { + return provider?.request(args, `eip155:${chainId}`); + } + }; + + return Promise.resolve(_provider as Provider); }, async isAuthorized() { @@ -164,12 +169,9 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { async switchChain({ chainId }) { const _provider = await this.getProvider(); if (!_provider) throw new Error('Provider not available for switching chain.'); - const currentChainId = await this.getChainId(); const newChain = config.chains.find(c => c.id === chainId) as Chain; - if (!newChain) throw new Error('Chain not found'); - - if (currentChainId === chainId) return newChain; + if (!newChain) throw new SwitchChainError(new ChainNotConfiguredError()); try { await _provider.request({ @@ -177,15 +179,10 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { params: [{ chainId: numberToHex(chainId) }] }); - await appKitProvidedConnector.switchNetwork(newChain); config.emitter.emit('change', { chainId }); return newChain; } catch (error) { - const chain = config.chains.find(c => c.id === chainId); - // Check if chain is not configured - if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()); - // Try to add chain if switch failed (common pattern) //4902 in MetaMask: Unrecognized chain ID if ((error as any)?.code === 4902 || (error as any)?.data?.originalError?.code === 4902) { @@ -195,17 +192,17 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { params: [ { chainId: numberToHex(chainId), - chainName: chain.name, - nativeCurrency: chain.nativeCurrency, - rpcUrls: [chain.rpcUrls.default?.http[0] ?? ''], // Take first default HTTP RPC URL - blockExplorerUrls: [chain.blockExplorers?.default?.url] + chainName: newChain.name, + nativeCurrency: newChain.nativeCurrency, + rpcUrls: [newChain.rpcUrls.default?.http[0] ?? ''], // Take first default HTTP RPC URL + blockExplorerUrls: [newChain.blockExplorers?.default?.url] } ] }); await appKitProvidedConnector.switchNetwork(newChain); config.emitter.emit('change', { chainId }); - return chain; + return newChain; } catch (addError) { throw new UserRejectedRequestError(addError as Error); } From ed073a6c4cae4f9f2cb806ce40ba924aa939fbc0 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 19 May 2025 14:23:06 -0300 Subject: [PATCH 47/91] chore: check project id before fetching wallets --- packages/appkit/src/modal/w3m-modal/index.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index 631bc2fa..cf87d694 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -33,6 +33,7 @@ export function AppKit() { const { caipAddress, isConnected } = useSnapshot(AccountController.state); const { frameViewVisible, webviewVisible } = useSnapshot(WebviewController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); + const { projectId } = useSnapshot(OptionsController.state); const { height } = useWindowDimensions(); const { isLandscape } = useCustomDimensions(); const portraitHeight = height - 80; @@ -50,10 +51,10 @@ export function AppKit() { return handleClose(); }; - const prefetch = async () => { + const prefetch = useCallback(async () => { await ApiController.prefetch(); EventsController.sendEvent({ type: 'track', event: 'MODAL_LOADED' }); - }; + }, []); const handleClose = async () => { if (OptionsController.state.isSiweEnabled) { @@ -117,8 +118,10 @@ export function AppKit() { }; useEffect(() => { - prefetch(); - }, []); + if (projectId) { + prefetch(); + } + }, [projectId, prefetch]); useEffect(() => { onNewAddress(caipAddress); From f70c343eb954419ceb43c0cd922a5929d8462a56 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 19 May 2025 17:00:23 -0300 Subject: [PATCH 48/91] chore: AppKitNetwork type improvements --- apps/native/App.tsx | 36 +++++-------- apps/native/package.json | 6 +-- packages/appkit/src/AppKit.ts | 34 ++++++++----- packages/appkit/src/networks/solana.ts | 2 +- packages/appkit/src/utils/NetworkUtil.ts | 50 +++++++++---------- packages/common/src/utils/TypeUtil.ts | 13 +++-- .../src/controllers/ConnectionsController.ts | 5 +- packages/solana/src/adapter.ts | 1 - packages/wagmi/src/adapter.ts | 2 +- .../src/connectors/UniversalConnector.ts | 6 +-- packages/wagmi/src/index.tsx | 1 + packages/wagmi/src/utils/helpers.ts | 20 +++++++- yarn.lock | 24 +-------- 13 files changed, 99 insertions(+), 101 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 06afa12a..453930ca 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -2,19 +2,10 @@ import { SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import * as Clipboard from 'expo-clipboard'; import '@walletconnect/react-native-compat'; -// import { WagmiProvider } from 'wagmi'; +import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import Toast from 'react-native-toast-message'; -// import { -// // AppKit, -// // AppKitButton, -// // NetworkButton, -// // createAppKit, -// WagmiAdapter, -// defaultWagmiConfig -// } from '@reown/appkit-wagmi-react-native'; - import { AppKitProvider, createAppKit, @@ -37,11 +28,10 @@ import { DisconnectButton } from './src/components/DisconnectButton'; import { SolanaAdapter } from '@reown/appkit-solana-react-native'; import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; import { WagmiAdapter } from '@reown/appkit-wagmi-react-native'; -import { mainnet, polygon, avalanche } from 'viem/chains'; +import { mainnet, polygon, avalanche, zora, sepolia } from 'wagmi/chains'; import { ActionsView } from './src/views/ActionsView'; import { WalletInfoView } from './src/views/WalletInfoView'; import { EventsView } from './src/views/EventsView'; -import { WagmiProvider } from 'wagmi'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -70,7 +60,7 @@ const queryClient = new QueryClient(); const wagmiAdapter = new WagmiAdapter({ projectId, - networks: [mainnet, polygon, avalanche] + networks: [mainnet, polygon, avalanche, zora, sepolia] }); const solanaAdapter = new SolanaAdapter({ @@ -85,19 +75,19 @@ const appKit = createAppKit({ projectId, adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter], metadata, - networks: [mainnet, polygon, avalanche, bitcoin, solana], + networks: [mainnet, polygon, avalanche, zora, sepolia, solana, bitcoin], defaultChain: polygon, clipboardClient, debug: true, - enableAnalytics: true - // siweConfig, - // features: { - // email: true, - // socials: ['x', 'discord', 'apple'], - // emailShowWallets: true, - // swaps: true, - // onramp: true - // } + enableAnalytics: true, + tokens: { + 'eip155:1': { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + }, + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': { + address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC SPL token + } + } }); export default function Native() { diff --git a/apps/native/package.json b/apps/native/package.json index 6f02164e..616b0b9e 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -49,9 +49,7 @@ "react-native-toast-message": "2.2.1", "react-native-web": "~0.19.13", "react-native-webview": "13.12.5", - "uuid": "^11.1.0", - "viem": "2.28.3", - "wagmi": "2.15.1" + "uuid": "^11.1.0" }, "devDependencies": { "@babel/core": "^7.24.0", @@ -61,7 +59,7 @@ "@types/react": "~18.2.79", "babel-plugin-module-resolver": "^5.0.0", "gh-pages": "^6.2.0", - "typescript": "~5.3.3" + "typescript": "5.2.2" }, "resolutions": { "@walletconnect/ethereum-provider": "2.20.2", diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 1d292a24..9cc12b1c 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -24,7 +24,9 @@ import type { Provider, ThemeVariables, ThemeMode, - WalletInfo + WalletInfo, + Network, + ChainNamespace } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; @@ -37,7 +39,7 @@ interface AppKitConfig { projectId: string; metadata: Metadata; adapters: BlockchainAdapter[]; - networks: AppKitNetwork[]; + networks: Network[]; extraConnectors?: WalletConnector[]; clipboardClient?: OptionsControllerState['clipboardClient']; includeWalletIds?: OptionsControllerState['includeWalletIds']; @@ -50,7 +52,7 @@ interface AppKitConfig { themeMode?: ThemeMode; themeVariables?: ThemeVariables; siweConfig?: AppKitSIWEClient; - defaultChain?: AppKitNetwork; + defaultChain?: Network; // features?: Features; // chainImages?: Record; //TODO: rename to networkImages } @@ -60,6 +62,7 @@ export class AppKit { private metadata: Metadata; private adapters: BlockchainAdapter[]; private networks: AppKitNetwork[]; + private defaultChain?: AppKitNetwork; private namespaces: ProposalNamespaces; private config: AppKitConfig; private extraConnectors: WalletConnector[]; @@ -69,7 +72,10 @@ export class AppKit { this.metadata = config.metadata; this.adapters = config.adapters; this.networks = NetworkUtil.formatNetworks(config.networks, this.projectId); //TODO: check this - this.namespaces = WcHelpersUtil.createNamespaces(config.networks) as ProposalNamespaces; + this.defaultChain = config.defaultChain + ? NetworkUtil.formatNetwork(config.defaultChain, this.projectId) + : undefined; + this.namespaces = WcHelpersUtil.createNamespaces(this.networks) as ProposalNamespaces; this.config = config; this.extraConnectors = config.extraConnectors || []; @@ -85,7 +91,9 @@ export class AppKit { async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { try { const connector = await this.createConnector(type); - const defaultChain = NetworkUtil.getDefaultChainId(this.config.defaultChain); + const defaultChain = this.defaultChain + ? NetworkUtil.getDefaultChainId(this.defaultChain) + : undefined; const approvedNamespaces = await connector.connect({ namespaces: requestedNamespaces ?? this.namespaces, @@ -208,8 +216,8 @@ export class AppKit { `${adapter.getSupportedNamespace()}:${network.id}` as CaipNetworkId ); - if (ConnectionsController.state.activeNamespace !== (network.chainNamespace ?? 'eip155')) { - ConnectionsController.setActiveNamespace(network.chainNamespace ?? 'eip155'); + if (ConnectionsController.state.activeNamespace !== network.chainNamespace) { + ConnectionsController.setActiveNamespace(network.chainNamespace); } } @@ -302,7 +310,7 @@ export class AppKit { return adapters; } - private getAdapterByNamespace(namespace: string = 'eip155'): BlockchainAdapter | null { + private getAdapterByNamespace(namespace: ChainNamespace): BlockchainAdapter | null { const namespaceConnection = ConnectionsController.state.connections[namespace]; return namespaceConnection?.adapter ?? null; @@ -393,7 +401,7 @@ export class AppKit { } private async initControllers(options: AppKitConfig) { - await this.initAsyncValues(options); + await this.initAsyncValues(); OptionsController.setProjectId(options.projectId); OptionsController.setMetadata(options.metadata); @@ -416,7 +424,7 @@ export class AppKit { OptionsController.setClipboardClient(options.clipboardClient); } - ConnectionsController.setNetworks(options.networks); + ConnectionsController.setNetworks(this.networks); if (options.siweConfig) { SIWEController.setSIWEClient(options.siweConfig); @@ -430,12 +438,12 @@ export class AppKit { // } } - private async initAsyncValues(options: AppKitConfig) { + private async initAsyncValues() { const activeNamespace = await StorageUtil.getActiveNamespace(); if (activeNamespace) { ConnectionsController.setActiveNamespace(activeNamespace); - } else if (options.defaultChain) { - ConnectionsController.setActiveNamespace(options.defaultChain?.chainNamespace ?? 'eip155'); + } else if (this.defaultChain) { + ConnectionsController.setActiveNamespace(this.defaultChain?.chainNamespace); } } } diff --git a/packages/appkit/src/networks/solana.ts b/packages/appkit/src/networks/solana.ts index 39a2e32f..f88cd71c 100644 --- a/packages/appkit/src/networks/solana.ts +++ b/packages/appkit/src/networks/solana.ts @@ -30,7 +30,7 @@ export const solanaDevnet: AppKitNetwork = { testnet: true }; -export const solanaTestnet = { +export const solanaTestnet: AppKitNetwork = { id: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', name: 'Solana Testnet', nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, diff --git a/packages/appkit/src/utils/NetworkUtil.ts b/packages/appkit/src/utils/NetworkUtil.ts index 39103ae4..757ac619 100644 --- a/packages/appkit/src/utils/NetworkUtil.ts +++ b/packages/appkit/src/utils/NetworkUtil.ts @@ -1,32 +1,32 @@ import { ConstantsUtil } from '@reown/appkit-common-react-native'; -import type { AppKitNetwork, CaipNetworkId } from '@reown/appkit-common-react-native'; +import type { AppKitNetwork, CaipNetworkId, Network } from '@reown/appkit-common-react-native'; export const NetworkUtil = { - //TODO: check this function - formatNetworks(networks: AppKitNetwork[], projectId: string): AppKitNetwork[] { - return networks.map(network => { - const formattedNetwork = { - ...network, - rpcUrls: { ...network.rpcUrls } - }; - - Object.keys(formattedNetwork.rpcUrls).forEach(key => { - const rpcConfig = formattedNetwork.rpcUrls[key]; - if (rpcConfig?.http?.some(url => url.includes(ConstantsUtil.BLOCKCHAIN_API_RPC_URL))) { - formattedNetwork.rpcUrls[key] = { - ...rpcConfig, - http: [ - this.getBlockchainApiRpcUrl( - network.caipNetworkId ?? `${network.chainNamespace ?? 'eip155'}:${network.id}`, - projectId - ) - ] - }; - } - }); - - return formattedNetwork; + formatNetwork(network: Network, projectId: string): AppKitNetwork { + const formattedNetwork = { + ...network, + rpcUrls: { ...network.rpcUrls }, + chainNamespace: network.chainNamespace ?? 'eip155', + caipNetworkId: network.caipNetworkId ?? `${network.chainNamespace ?? 'eip155'}:${network.id}` + }; + + Object.keys(formattedNetwork.rpcUrls).forEach(key => { + const rpcConfig = formattedNetwork.rpcUrls[key]; + if (rpcConfig?.http?.some(url => url.includes(ConstantsUtil.BLOCKCHAIN_API_RPC_URL))) { + formattedNetwork.rpcUrls[key] = { + ...rpcConfig, + http: [this.getBlockchainApiRpcUrl(formattedNetwork.caipNetworkId, projectId)] + }; + } }); + + return formattedNetwork; + }, + + formatNetworks(networks: Network[], projectId: string): AppKitNetwork[] { + const formattedNetworks = networks.map(network => this.formatNetwork(network, projectId)); + + return formattedNetworks; }, getBlockchainApiRpcUrl(caipNetworkId: CaipNetworkId, projectId: string) { diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 10faed0a..93cc0110 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -19,14 +19,19 @@ export type Network = { default: { name: string; url: string }; [key: string]: { name: string; url: string } | undefined; }; -}; -export type AppKitNetwork = Network & { - // AppKit specific / CAIP properties (Optional in type, but often needed in practice) + // AppKit specific / CAIP properties (Optional in type, but needed in practice) chainNamespace?: ChainNamespace; // e.g., 'eip155' caipNetworkId?: CaipNetworkId; // e.g., 'eip155:1' testnet?: boolean; - deprecatedCaipNetworkId?: CaipNetworkId; // for Solana + deprecatedCaipNetworkId?: CaipNetworkId; // for Solana deprecated id +}; + +export type AppKitNetwork = Network & { + chainNamespace: ChainNamespace; // e.g., 'eip155' + caipNetworkId: CaipNetworkId; // e.g., 'eip155:1' + testnet?: boolean; + deprecatedCaipNetworkId?: CaipNetworkId; // for Solana deprecated id }; export interface CaipNetwork { diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index d93aceb9..d3501939 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -93,7 +93,7 @@ const derivedState = derive( return snap.networks.find( network => - (network.chainNamespace ?? 'eip155') === snap.activeNamespace && + network.chainNamespace === snap.activeNamespace && network.id?.toString() === connection.activeChain?.split(':')[1] ); }, @@ -187,7 +187,8 @@ export const ConnectionsController = { getConnectedNetworks() { return baseState.networks.filter( - network => baseState.connections[network.chainNamespace ?? 'eip155'] + network => + baseState.connections[network.chainNamespace]?.chains.includes(network.caipNetworkId) ); }, diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index e8a79dcf..4601865e 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -19,7 +19,6 @@ export class SolanaAdapter extends SolanaBaseAdapter { } async getBalance(params: GetBalanceParams): Promise { - console.log('solana getBalance'); const { network, address, tokens } = params; if (!this.connector) throw new Error('No active connector'); diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index e7c0b0b3..724ab4ef 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -23,7 +23,7 @@ import { formatUnits, type Hex } from 'viem'; import { UniversalConnector } from './connectors/UniversalConnector'; type ConfigParams = Partial & { - networks: [Chain, ...Chain[]]; + networks: readonly [Chain, ...Chain[]]; projectId: string; connectors?: Connector[]; }; diff --git a/packages/wagmi/src/connectors/UniversalConnector.ts b/packages/wagmi/src/connectors/UniversalConnector.ts index d9e13c48..832d865a 100644 --- a/packages/wagmi/src/connectors/UniversalConnector.ts +++ b/packages/wagmi/src/connectors/UniversalConnector.ts @@ -8,10 +8,10 @@ import { numberToHex, SwitchChainError, UserRejectedRequestError, - type Chain, type Hex } from 'viem'; import { ChainNotConfiguredError, createConnector, ProviderNotFoundError } from 'wagmi'; +import { formatNetwork } from '../utils/helpers'; export function UniversalConnector(appKitProvidedConnector: WalletConnector) { let provider: Provider | undefined; @@ -169,7 +169,7 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { async switchChain({ chainId }) { const _provider = await this.getProvider(); if (!_provider) throw new Error('Provider not available for switching chain.'); - const newChain = config.chains.find(c => c.id === chainId) as Chain; + const newChain = config.chains.find(c => c.id === chainId); if (!newChain) throw new SwitchChainError(new ChainNotConfiguredError()); @@ -199,7 +199,7 @@ export function UniversalConnector(appKitProvidedConnector: WalletConnector) { } ] }); - await appKitProvidedConnector.switchNetwork(newChain); + await appKitProvidedConnector.switchNetwork(formatNetwork(newChain)); config.emitter.emit('change', { chainId }); return newChain; diff --git a/packages/wagmi/src/index.tsx b/packages/wagmi/src/index.tsx index b2f47a68..bcb87829 100644 --- a/packages/wagmi/src/index.tsx +++ b/packages/wagmi/src/index.tsx @@ -1,3 +1,4 @@ import { WagmiAdapter } from './adapter'; export { WagmiAdapter }; +export { formatNetworks, formatNetwork } from './utils/helpers'; diff --git a/packages/wagmi/src/utils/helpers.ts b/packages/wagmi/src/utils/helpers.ts index 2b1efa49..7c10a2af 100644 --- a/packages/wagmi/src/utils/helpers.ts +++ b/packages/wagmi/src/utils/helpers.ts @@ -1,5 +1,10 @@ import { CoreHelperUtil } from '@reown/appkit-react-native'; -import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; +import { + PresetsUtil, + ConstantsUtil, + type AppKitNetwork, + type Network +} from '@reown/appkit-common-react-native'; import { http } from 'viem'; export function getTransport({ chainId, projectId }: { chainId: number; projectId: string }) { @@ -11,3 +16,16 @@ export function getTransport({ chainId, projectId }: { chainId: number; projectI return http(`${RPC_URL}/v1/?chainId=${ConstantsUtil.EIP155}:${chainId}&projectId=${projectId}`); } + +export function formatNetwork(network: Network): AppKitNetwork { + return { + ...network, + rpcUrls: { ...network.rpcUrls }, + chainNamespace: 'eip155', + caipNetworkId: `eip155:${network.id}` + }; +} + +export function formatNetworks(networks: Network[]): AppKitNetwork[] { + return networks.map(formatNetwork); +} diff --git a/yarn.lock b/yarn.lock index b6170264..29249c28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -138,10 +138,8 @@ __metadata: react-native-toast-message: "npm:2.2.1" react-native-web: "npm:~0.19.13" react-native-webview: "npm:13.12.5" - typescript: "npm:~5.3.3" + typescript: "npm:5.2.2" uuid: "npm:^11.1.0" - viem: "npm:2.28.3" - wagmi: "npm:2.15.1" languageName: unknown linkType: soft @@ -22736,16 +22734,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:~5.3.3": - version: 5.3.3 - resolution: "typescript@npm:5.3.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: e33cef99d82573624fc0f854a2980322714986bc35b9cb4d1ce736ed182aeab78e2cb32b385efa493b2a976ef52c53e20d6c6918312353a91850e2b76f1ea44f - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A5.2.2#optional!builtin": version: 5.2.2 resolution: "typescript@patch:typescript@npm%3A5.2.2#optional!builtin::version=5.2.2&hash=f3b441" @@ -22756,16 +22744,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A~5.3.3#optional!builtin": - version: 5.3.3 - resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 1d0a5f4ce496c42caa9a30e659c467c5686eae15d54b027ee7866744952547f1be1262f2d40de911618c242b510029d51d43ff605dba8fb740ec85ca2d3f9500 - languageName: node - linkType: hard - "ua-parser-js@npm:^1.0.35": version: 1.0.35 resolution: "ua-parser-js@npm:1.0.35" From 89fb844e996a0ebb44c8a516acbedfad519d502d Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 15:18:50 -0300 Subject: [PATCH 49/91] chore: hook changes, added same namespace adapters error --- apps/native/App.tsx | 2 +- apps/native/src/views/BitcoinActionsView.tsx | 2 +- apps/native/src/views/EthersActionsView.tsx | 2 +- apps/native/src/views/SolanaActionsView.tsx | 2 +- packages/appkit/src/AppKit.ts | 32 +++++++++++++++----- packages/appkit/src/hooks/useAccount.ts | 2 +- packages/appkit/src/hooks/useProvider.ts | 18 ++++++++--- 7 files changed, 42 insertions(+), 18 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 453930ca..2175dbac 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -76,7 +76,7 @@ const appKit = createAppKit({ adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter], metadata, networks: [mainnet, polygon, avalanche, zora, sepolia, solana, bitcoin], - defaultChain: polygon, + defaultNetwork: polygon, clipboardClient, debug: true, enableAnalytics: true, diff --git a/apps/native/src/views/BitcoinActionsView.tsx b/apps/native/src/views/BitcoinActionsView.tsx index 104917a2..018b9de2 100644 --- a/apps/native/src/views/BitcoinActionsView.tsx +++ b/apps/native/src/views/BitcoinActionsView.tsx @@ -8,7 +8,7 @@ import { BitcoinUtil, SignPSBTResponse } from '../utils/BitcoinUtil'; export function BitcoinActionsView() { const isConnected = true; const { address, chainId } = useAccount(); - const provider = useProvider('bip122'); + const { provider } = useProvider('bip122'); const onSignSuccess = (data: string) => { ToastUtils.showSuccessToast('Sign successful', data); diff --git a/apps/native/src/views/EthersActionsView.tsx b/apps/native/src/views/EthersActionsView.tsx index c0e60963..3fc7693d 100644 --- a/apps/native/src/views/EthersActionsView.tsx +++ b/apps/native/src/views/EthersActionsView.tsx @@ -8,7 +8,7 @@ import { ToastUtils } from '../utils/ToastUtils'; export function EthersActionsView() { const isConnected = true; const { address, chainId } = useAccount(); - const provider = useProvider('eip155'); + const { provider } = useProvider('eip155'); const onSignSuccess = (data: any) => { ToastUtils.showSuccessToast('Sign successful', data); diff --git a/apps/native/src/views/SolanaActionsView.tsx b/apps/native/src/views/SolanaActionsView.tsx index 5258fb03..2305ee02 100644 --- a/apps/native/src/views/SolanaActionsView.tsx +++ b/apps/native/src/views/SolanaActionsView.tsx @@ -8,7 +8,7 @@ import { ToastUtils } from '../utils/ToastUtils'; export function SolanaActionsView() { const isConnected = true; const { address, chainId } = useAccount(); - const provider = useProvider('solana'); + const { provider } = useProvider('solana'); const onSignSuccess = (data: any) => { ToastUtils.showSuccessToast('Sign successful', data); diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 9cc12b1c..d2fb0052 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -52,7 +52,7 @@ interface AppKitConfig { themeMode?: ThemeMode; themeVariables?: ThemeVariables; siweConfig?: AppKitSIWEClient; - defaultChain?: Network; + defaultNetwork?: Network; // features?: Features; // chainImages?: Record; //TODO: rename to networkImages } @@ -62,7 +62,7 @@ export class AppKit { private metadata: Metadata; private adapters: BlockchainAdapter[]; private networks: AppKitNetwork[]; - private defaultChain?: AppKitNetwork; + private defaultNetwork?: AppKitNetwork; private namespaces: ProposalNamespaces; private config: AppKitConfig; private extraConnectors: WalletConnector[]; @@ -71,9 +71,25 @@ export class AppKit { this.projectId = config.projectId; this.metadata = config.metadata; this.adapters = config.adapters; + + // Validate adapters to ensure no duplicate chainNamespaces + const namespaceMap = new Map(); + for (const adapter of this.adapters) { + const chainNamespace = adapter.supportedNamespace; + const adapterName = adapter.constructor.name; + if (namespaceMap.has(chainNamespace)) { + throw new Error( + `Duplicate adapter for namespace '${chainNamespace}'. Adapter "${adapterName}" conflicts with adapter "${namespaceMap.get( + chainNamespace + )}". Please provide only one adapter per chain namespace.` + ); + } + namespaceMap.set(chainNamespace, adapterName); + } + this.networks = NetworkUtil.formatNetworks(config.networks, this.projectId); //TODO: check this - this.defaultChain = config.defaultChain - ? NetworkUtil.formatNetwork(config.defaultChain, this.projectId) + this.defaultNetwork = config.defaultNetwork + ? NetworkUtil.formatNetwork(config.defaultNetwork, this.projectId) : undefined; this.namespaces = WcHelpersUtil.createNamespaces(this.networks) as ProposalNamespaces; this.config = config; @@ -91,8 +107,8 @@ export class AppKit { async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { try { const connector = await this.createConnector(type); - const defaultChain = this.defaultChain - ? NetworkUtil.getDefaultChainId(this.defaultChain) + const defaultChain = this.defaultNetwork + ? NetworkUtil.getDefaultChainId(this.defaultNetwork) : undefined; const approvedNamespaces = await connector.connect({ @@ -442,8 +458,8 @@ export class AppKit { const activeNamespace = await StorageUtil.getActiveNamespace(); if (activeNamespace) { ConnectionsController.setActiveNamespace(activeNamespace); - } else if (this.defaultChain) { - ConnectionsController.setActiveNamespace(this.defaultChain?.chainNamespace); + } else if (this.defaultNetwork) { + ConnectionsController.setActiveNamespace(this.defaultNetwork?.chainNamespace); } } } diff --git a/packages/appkit/src/hooks/useAccount.ts b/packages/appkit/src/hooks/useAccount.ts index fb39ec6d..0dc83068 100644 --- a/packages/appkit/src/hooks/useAccount.ts +++ b/packages/appkit/src/hooks/useAccount.ts @@ -11,7 +11,7 @@ export function useAccount() { connections } = useSnapshot(ConnectionsController.state); - const connection = connections[activeNamespace ?? '']; + const connection = activeNamespace ? connections[activeNamespace] : undefined; return { address: address?.split(':')[2], diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts index 6554eab4..87446d22 100644 --- a/packages/appkit/src/hooks/useProvider.ts +++ b/packages/appkit/src/hooks/useProvider.ts @@ -1,15 +1,23 @@ import { useSnapshot } from 'valtio'; import { ConnectionsController } from '@reown/appkit-core-react-native'; -import type { Provider } from '@reown/appkit-common-react-native'; +import type { Provider, ChainNamespace } from '@reown/appkit-common-react-native'; -export function useProvider(namespace?: string): Provider | undefined { +interface ProviderResult { + provider?: Provider; + providerType?: ChainNamespace; +} + +export function useProvider(namespace?: string): ProviderResult { const { connections, activeNamespace } = useSnapshot(ConnectionsController.state); - if (!namespace || !activeNamespace) return undefined; + if (!namespace || !activeNamespace) return { provider: undefined, providerType: undefined }; const connection = connections[namespace ?? activeNamespace]; - if (!connection) return undefined; + if (!connection) return { provider: undefined, providerType: undefined }; - return connection.adapter.connector?.getProvider(); + return { + provider: connection.adapter.connector?.getProvider(), + providerType: connection.adapter.getSupportedNamespace() + }; } From 40d26c13f87b5d7debbed3508e2b62e1816c82b3 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 15:24:32 -0300 Subject: [PATCH 50/91] chore: added alpha changeset --- .changeset/pre.json | 25 +++++++++++++++++++++++++ .changeset/silly-snails-carry.md | 13 +++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 .changeset/pre.json create mode 100644 .changeset/silly-snails-carry.md diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..74bbbcab --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,25 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "@apps/gallery": "1.0.8", + "@apps/native": "1.0.8", + "@reown/appkit-react-native": "1.2.3", + "@reown/appkit-auth-ethers-react-native": "1.2.3", + "@reown/appkit-auth-wagmi-react-native": "1.2.3", + "@reown/appkit-bitcoin-react-native": "1.2.3", + "@reown/appkit-coinbase-ethers-react-native": "1.2.3", + "@reown/appkit-coinbase-wagmi-react-native": "1.2.3", + "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-core-react-native": "1.2.3", + "@reown/appkit-ethers-react-native": "1.2.3", + "@reown/appkit-ethers5-react-native": "1.2.3", + "@reown/appkit-scaffold-utils-react-native": "1.2.3", + "@reown/appkit-siwe-react-native": "1.2.3", + "@reown/appkit-solana-react-native": "1.2.3", + "@reown/appkit-ui-react-native": "1.2.3", + "@reown/appkit-wagmi-react-native": "1.2.3", + "@reown/appkit-wallet-react-native": "1.2.3" + }, + "changesets": [] +} diff --git a/.changeset/silly-snails-carry.md b/.changeset/silly-snails-carry.md new file mode 100644 index 00000000..30a4d40f --- /dev/null +++ b/.changeset/silly-snails-carry.md @@ -0,0 +1,13 @@ +--- +'@reown/appkit-scaffold-utils-react-native': major +'@reown/appkit-bitcoin-react-native': major +'@reown/appkit-react-native': major +'@reown/appkit-common-react-native': major +'@reown/appkit-ethers-react-native': major +'@reown/appkit-solana-react-native': major +'@reown/appkit-wagmi-react-native': major +'@reown/appkit-core-react-native': major +'@reown/appkit-ui-react-native': major +--- + +BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha.0. Includes duplicate adapter validation and other API refinements. From 1f2fe7096ee2fc361ce9cf4611cdf82f5d9c9545 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 15:28:45 -0300 Subject: [PATCH 51/91] chore: exit pre version --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 74bbbcab..6c6bbbce 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "alpha", "initialVersions": { "@apps/gallery": "1.0.8", From a7c05e9b314c80e719ec3d2f43a786b1ddefafe5 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 17:19:42 -0300 Subject: [PATCH 52/91] chore: add alpha release action --- .github/workflows/alpha-release.yml | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/alpha-release.yml diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml new file mode 100644 index 00000000..2ba6c833 --- /dev/null +++ b/.github/workflows/alpha-release.yml @@ -0,0 +1,57 @@ +name: Create Alpha Release + +on: + workflow_dispatch: + +jobs: + alpha-release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Environment and Install Dependencies (Immutable) + uses: ./.github/actions/setup # Uses your custom action (node setup, yarn install --immutable) + + - name: Configure Git User + run: | + git config user.name "GitHub Actions Bot" + git config user.email "github-actions[bot]@users.noreply.github.com" + shell: bash + + - name: Enter Pre-mode + run: yarn changeset pre enter alpha + shell: bash + + - name: Version Packages for Alpha + run: yarn changeset version + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update Lockfile + run: yarn install --immutable false + shell: bash + + - name: Build Packages + run: yarn changeset:prepublish + shell: bash + + - name: Configure NPM for Publishing + run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc + shell: bash + + - name: Publish Alpha to NPM + run: yarn changeset publish --tag alpha + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Changes and Tags + run: | + git push --follow-tags + shell: bash \ No newline at end of file From c4a56120f4c9332a05c5c93fa115847f7d4b8d87 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 17:21:41 -0300 Subject: [PATCH 53/91] chore: removed alpha action --- .github/workflows/alpha-release.yml | 57 ----------------------------- 1 file changed, 57 deletions(-) delete mode 100644 .github/workflows/alpha-release.yml diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml deleted file mode 100644 index 2ba6c833..00000000 --- a/.github/workflows/alpha-release.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Create Alpha Release - -on: - workflow_dispatch: - -jobs: - alpha-release: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Environment and Install Dependencies (Immutable) - uses: ./.github/actions/setup # Uses your custom action (node setup, yarn install --immutable) - - - name: Configure Git User - run: | - git config user.name "GitHub Actions Bot" - git config user.email "github-actions[bot]@users.noreply.github.com" - shell: bash - - - name: Enter Pre-mode - run: yarn changeset pre enter alpha - shell: bash - - - name: Version Packages for Alpha - run: yarn changeset version - shell: bash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Update Lockfile - run: yarn install --immutable false - shell: bash - - - name: Build Packages - run: yarn changeset:prepublish - shell: bash - - - name: Configure NPM for Publishing - run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - shell: bash - - - name: Publish Alpha to NPM - run: yarn changeset publish --tag alpha - shell: bash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Push Changes and Tags - run: | - git push --follow-tags - shell: bash \ No newline at end of file From 8e3ea25bf581739ca4ce05d4e46d41f58e5abb8a Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 17:23:32 -0300 Subject: [PATCH 54/91] chore: add alpha release action --- .github/workflows/alpha-release.yml | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/alpha-release.yml diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml new file mode 100644 index 00000000..a1cd2a15 --- /dev/null +++ b/.github/workflows/alpha-release.yml @@ -0,0 +1,57 @@ +name: Alpha Release + +on: + workflow_dispatch: + +jobs: + alpha-release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Environment and Install Dependencies (Immutable) + uses: ./.github/actions/setup # Uses your custom action (node setup, yarn install --immutable) + + - name: Configure Git User + run: | + git config user.name "GitHub Actions Bot" + git config user.email "github-actions[bot]@users.noreply.github.com" + shell: bash + + - name: Enter Pre-mode + run: yarn changeset pre enter alpha + shell: bash + + - name: Version Packages for Alpha + run: yarn changeset version + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update Lockfile + run: yarn install --immutable false + shell: bash + + - name: Build Packages + run: yarn changeset:prepublish + shell: bash + + - name: Configure NPM for Publishing + run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc + shell: bash + + - name: Publish Alpha to NPM + run: yarn changeset publish --tag alpha + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Changes and Tags + run: | + git push --follow-tags + shell: bash \ No newline at end of file From c52afc3376e7fa8021b40f371dd98bbbbe0b3f2d Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 17:25:49 -0300 Subject: [PATCH 55/91] chore: changed alpha action --- .github/workflows/alpha-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index a1cd2a15..bcb487c9 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -15,7 +15,7 @@ jobs: fetch-depth: 0 - name: Setup Environment and Install Dependencies (Immutable) - uses: ./.github/actions/setup # Uses your custom action (node setup, yarn install --immutable) + uses: ./.github/actions/setup - name: Configure Git User run: | @@ -34,7 +34,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update Lockfile - run: yarn install --immutable false + run: yarn install shell: bash - name: Build Packages From 97625f90dc1ce84e8a3a43ce32a5353674422820 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 17:30:46 -0300 Subject: [PATCH 56/91] chore: changed alpha action --- .github/workflows/alpha-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index bcb487c9..719e9593 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -46,7 +46,7 @@ jobs: shell: bash - name: Publish Alpha to NPM - run: yarn changeset publish --tag alpha + run: yarn changeset publish shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From fc5b08207be450132e12a52eb40807ba870f9663 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 May 2025 17:36:54 -0300 Subject: [PATCH 57/91] chore: changed alpha action --- .github/workflows/alpha-release.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index 719e9593..b7d80fbd 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -51,6 +51,28 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Exit Pre-mode and Commit Changes + run: | + yarn changeset pre exit + # Check if pre.json was actually deleted (it should be) + if [ ! -f .changeset/pre.json ]; then + echo "pre.json successfully deleted by 'changeset pre exit'." + # Add the potential deletion of pre.json to git. + # If pre.json didn't exist or wasn't tracked, this might not add anything, which is fine. + git add .changeset/pre.json || echo "No pre.json to stage or pre.json was not tracked." + # Commit only if there are changes to commit (i.e., pre.json was deleted and staged) + if ! git diff --staged --quiet; then + git commit -m "chore: exit changeset pre-mode" + else + echo "No changes to commit for pre-mode exit." + fi + else + echo "Warning: .changeset/pre.json still exists after 'changeset pre exit'." + fi + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Push Changes and Tags run: | git push --follow-tags From db24147ac58cfbbd7b6a2af41975f1def8554f2a Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 29 May 2025 11:11:57 -0300 Subject: [PATCH 58/91] chore: changed connections object to be a map, removed namespace param from events as its not needed --- packages/appkit/src/AppKit.ts | 65 ++++++++++------- packages/appkit/src/hooks/useAccount.ts | 2 +- packages/appkit/src/hooks/useProvider.ts | 8 +- packages/bitcoin/src/adapter.ts | 1 - packages/common/src/utils/TypeUtil.ts | 11 +-- .../src/controllers/ConnectionsController.ts | 73 ++++++++++++------- packages/ethers/src/adapter.ts | 6 +- packages/solana/src/adapter.ts | 6 +- packages/wagmi/src/adapter.ts | 6 +- 9 files changed, 99 insertions(+), 79 deletions(-) diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index d2fb0052..35f22a35 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -166,10 +166,12 @@ export class AppKit { return; } - const connection = ConnectionsController.state.connections[activeNamespace]; + const connection = ConnectionsController.state.connections.get( + activeNamespace as ChainNamespace + ); const connectorType = connection?.adapter?.connector?.type; - await ConnectionsController.disconnect(activeNamespace, isInternal); + await ConnectionsController.disconnect(activeNamespace as ChainNamespace, isInternal); if (connectorType) { await StorageUtil.removeConnectedConnectors(connectorType); @@ -203,7 +205,9 @@ export class AppKit { const activeNamespace = namespace ?? ConnectionsController.state.activeNamespace; if (!activeNamespace) return null; - const connection = ConnectionsController.state.connections[activeNamespace]; + const connection = ConnectionsController.state.connections.get( + activeNamespace as ChainNamespace + ); if (!connection || !connection.adapter || !connection.adapter.connector) return null; return connection.adapter.connector.getProvider() as T; @@ -327,28 +331,36 @@ export class AppKit { } private getAdapterByNamespace(namespace: ChainNamespace): BlockchainAdapter | null { - const namespaceConnection = ConnectionsController.state.connections[namespace]; + const namespaceConnection = ConnectionsController.state.connections.get(namespace); + if (namespaceConnection) { + return namespaceConnection.adapter; + } - return namespaceConnection?.adapter ?? null; + return null; } private async syncAccounts(adapters: BlockchainAdapter[]) { - // Get account balances - adapters.map(adapter => { + adapters.forEach(async adapter => { const namespace = adapter.getSupportedNamespace(); - const connection = ConnectionsController.state.connections[namespace]; - - if (!connection) return; - - const network = this.networks.find( - n => n.id?.toString() === connection?.activeChain?.split(':')[1] - ); - - const address = - adapter.getAccounts()?.find(a => a.startsWith(connection?.activeChain)) ?? - adapter.getAccounts()?.[0]; - - adapter.getBalance({ address, network, tokens: this.config.tokens }); + const connection = ConnectionsController.state.connections.get(namespace); + if (connection) { + const accounts = adapter.getAccounts(); + if (accounts && accounts.length > 0) { + ConnectionsController.updateAccounts(namespace, accounts); + + const network = this.networks.find( + n => n.id?.toString() === connection?.activeChain?.split(':')[1] + ); + + const address = accounts.find( + a => a.split(':')[1] === connection.activeChain?.split(':')[1] + ); + + if (address) { + adapter.getBalance({ address, network, tokens: this.config.tokens }); + } + } + } }); } @@ -387,13 +399,15 @@ export class AppKit { } private subscribeToAdapterEvents(adapter: BlockchainAdapter): void { - adapter.on('accountsChanged', ({ accounts, namespace }) => { + adapter.on('accountsChanged', ({ accounts }) => { + const namespace = adapter.getSupportedNamespace(); //eslint-disable-next-line no-console console.log('accountsChanged', accounts, namespace); //TODO: check this }); - adapter.on('chainChanged', ({ chainId, namespace }) => { + adapter.on('chainChanged', ({ chainId }) => { + const namespace = adapter.getSupportedNamespace(); const chain = `${namespace}:${chainId}` as CaipNetworkId; ConnectionsController.setActiveChain(namespace, chain); @@ -406,12 +420,13 @@ export class AppKit { } }); - adapter.on('disconnect', ({ namespace }) => { - // console.log('AppKit disconnect namespace', namespace); + adapter.on('disconnect', () => { + const namespace = adapter.getSupportedNamespace(); this.disconnect(namespace, false); }); - adapter.on('balanceChanged', ({ namespace, address, balance }) => { + adapter.on('balanceChanged', ({ address, balance }) => { + const namespace = adapter.getSupportedNamespace(); ConnectionsController.updateBalance(namespace, address, balance); }); } diff --git a/packages/appkit/src/hooks/useAccount.ts b/packages/appkit/src/hooks/useAccount.ts index 0dc83068..701cc315 100644 --- a/packages/appkit/src/hooks/useAccount.ts +++ b/packages/appkit/src/hooks/useAccount.ts @@ -11,7 +11,7 @@ export function useAccount() { connections } = useSnapshot(ConnectionsController.state); - const connection = activeNamespace ? connections[activeNamespace] : undefined; + const connection = activeNamespace ? connections.get(activeNamespace) : undefined; return { address: address?.split(':')[2], diff --git a/packages/appkit/src/hooks/useProvider.ts b/packages/appkit/src/hooks/useProvider.ts index 87446d22..6962e4e0 100644 --- a/packages/appkit/src/hooks/useProvider.ts +++ b/packages/appkit/src/hooks/useProvider.ts @@ -7,12 +7,14 @@ interface ProviderResult { providerType?: ChainNamespace; } -export function useProvider(namespace?: string): ProviderResult { +export function useProvider(namespace?: ChainNamespace): ProviderResult { const { connections, activeNamespace } = useSnapshot(ConnectionsController.state); - if (!namespace || !activeNamespace) return { provider: undefined, providerType: undefined }; + const targetNamespace = namespace ?? activeNamespace; - const connection = connections[namespace ?? activeNamespace]; + if (!targetNamespace) return { provider: undefined, providerType: undefined }; + + const connection = connections.get(targetNamespace); if (!connection) return { provider: undefined, providerType: undefined }; diff --git a/packages/bitcoin/src/adapter.ts b/packages/bitcoin/src/adapter.ts index e38e8992..e274b6fc 100644 --- a/packages/bitcoin/src/adapter.ts +++ b/packages/bitcoin/src/adapter.ts @@ -45,7 +45,6 @@ export class BitcoinAdapter extends BlockchainAdapter { const formattedBalance = UnitsUtil.parseSatoshis(balance.toString(), network); this.emit('balanceChanged', { - namespace: this.getSupportedNamespace(), address: balanceCaipAddress, balance: { amount: formattedBalance, diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 93cc0110..c87fe00b 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -149,20 +149,15 @@ export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; //********** Adapter Event Payloads **********// export type AccountsChangedEvent = { accounts: string[]; - namespace: ChainNamespace; }; export type ChainChangedEvent = { chainId: string; - namespace: ChainNamespace; }; -export type DisconnectEvent = { - namespace: ChainNamespace; -}; +export type DisconnectEvent = {}; export type BalanceChangedEvent = { - namespace: ChainNamespace; address: CaipAddress; balance: { amount: string; @@ -234,7 +229,7 @@ export abstract class BlockchainAdapter extends EventEmitter { const shouldEmit = _chains?.some(chain => chain === chainId); if (shouldEmit) { - this.emit('chainChanged', { chainId, namespace: this.getSupportedNamespace() }); + this.emit('chainChanged', { chainId }); } } @@ -247,7 +242,7 @@ export abstract class BlockchainAdapter extends EventEmitter { }); if (shouldEmit) { - this.emit('accountsChanged', { accounts, namespace: this.getSupportedNamespace() }); + this.emit('accountsChanged', { accounts }); } } diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index d3501939..751e6c2f 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -26,14 +26,14 @@ interface Connection { export interface ConnectionsControllerState { activeNamespace?: ChainNamespace; - connections: Record; + connections: Map; networks: AppKitNetwork[]; } // -- State --------------------------------------------- // const baseState = proxy({ activeNamespace: undefined, - connections: {}, + connections: new Map(), networks: [] }); @@ -42,9 +42,11 @@ const derivedState = derive( activeAddress: (get): CaipAddress | undefined => { const snap = get(baseState); - if (!snap.activeNamespace) return undefined; + if (!snap.activeNamespace) { + return undefined; + } - const connection = snap.connections[snap.activeNamespace]; + const connection = snap.connections.get(snap.activeNamespace); if (!connection || !connection.accounts || connection.accounts.length === 0) { return undefined; @@ -61,7 +63,7 @@ const derivedState = derive( const snap = get(baseState); if (!snap.activeNamespace) return undefined; - const connection = snap.connections[snap.activeNamespace]; + const connection = snap.connections.get(snap.activeNamespace); if (!connection || !connection.accounts || connection.accounts.length === 0) { return undefined; @@ -87,7 +89,7 @@ const derivedState = derive( if (!snap.activeNamespace) return undefined; - const connection = snap.connections[snap.activeNamespace]; + const connection = snap.connections.get(snap.activeNamespace); if (!connection) return undefined; @@ -102,7 +104,7 @@ const derivedState = derive( if (!snap.activeNamespace) return undefined; - const connection = snap.connections[snap.activeNamespace]; + const connection = snap.connections.get(snap.activeNamespace); if (!connection) return undefined; @@ -113,7 +115,7 @@ const derivedState = derive( if (!snap.activeNamespace) return undefined; - return snap.connections[snap.activeNamespace]?.wallet; + return snap.connections.get(snap.activeNamespace)?.wallet; } }, { @@ -138,14 +140,14 @@ export const ConnectionsController = { wallet, activeChain }: { - namespace: string; + namespace: ChainNamespace; adapter: BlockchainAdapter; accounts: CaipAddress[]; chains: CaipNetworkId[]; wallet?: WalletInfo; activeChain?: CaipNetworkId; }) { - baseState.connections[namespace] = { + const newConnectionEntry = { balances: {}, activeChain: activeChain ?? chains[0]!, adapter: ref(adapter), @@ -153,32 +155,49 @@ export const ConnectionsController = { chains, wallet }; + + // Create a new Map to ensure Valtio detects the change + const newConnectionsMap = new Map(baseState.connections); + newConnectionsMap.set(namespace, newConnectionEntry); + baseState.connections = newConnectionsMap; }, - updateAccounts(namespace: string, accounts: CaipAddress[]) { - const connection = baseState.connections[namespace]; + updateAccounts(namespace: ChainNamespace, accounts: CaipAddress[]) { + const connection = baseState.connections.get(namespace); if (!connection) { return; } - connection.accounts = accounts; + + const newConnectionsMap = new Map(baseState.connections); + const updatedConnection = { ...connection, accounts }; + newConnectionsMap.set(namespace, updatedConnection); + baseState.connections = newConnectionsMap; }, - updateBalance(namespace: string, address: CaipAddress, balance: Balance) { - const connection = baseState.connections[namespace]; + updateBalance(namespace: ChainNamespace, address: CaipAddress, balance: Balance) { + const connection = baseState.connections.get(namespace); if (!connection) { return; } - connection.balances[address] = balance; + + const newBalances = { ...connection.balances, [address]: balance }; + const updatedConnection = { ...connection, balances: newBalances }; + const newConnectionsMap = new Map(baseState.connections); + newConnectionsMap.set(namespace, updatedConnection); + baseState.connections = newConnectionsMap; }, - setActiveChain(namespace: string, chain: CaipNetworkId) { - const connection = baseState.connections[namespace]; + setActiveChain(namespace: ChainNamespace, chain: CaipNetworkId) { + const connection = baseState.connections.get(namespace); if (!connection) { return; } - connection.activeChain = chain; + baseState.connections.set(namespace, { + ...connection, + activeChain: chain + }); }, setNetworks(networks: AppKitNetwork[]) { @@ -188,12 +207,12 @@ export const ConnectionsController = { getConnectedNetworks() { return baseState.networks.filter( network => - baseState.connections[network.chainNamespace]?.chains.includes(network.caipNetworkId) + baseState.connections.get(network.chainNamespace)?.chains.includes(network.caipNetworkId) ); }, - async disconnect(namespace: string, isInternal = true) { - const connection = baseState.connections[namespace]; + async disconnect(namespace: ChainNamespace, isInternal = true) { + const connection = baseState.connections.get(namespace); if (!connection) return; // Get the current connector from the adapter @@ -201,13 +220,13 @@ export const ConnectionsController = { if (!connector) return; // Find all namespaces that use the same connector - const namespacesUsingConnector = Object.keys(baseState.connections).filter( - ns => baseState.connections[ns]?.adapter.connector === connector + const namespacesUsingConnector = Array.from(baseState.connections.keys()).filter( + ns => baseState.connections.get(ns)?.adapter.connector === connector ); // Unsubscribe all event listeners from the adapter namespacesUsingConnector.forEach(ns => { - const _connection = baseState.connections[ns]; + const _connection = baseState.connections.get(ns); if (_connection?.adapter) { _connection.adapter.removeAllListeners(); } @@ -219,9 +238,11 @@ export const ConnectionsController = { } // Remove all namespaces that used this connector + const newConnectionsMap = new Map(baseState.connections); namespacesUsingConnector.forEach(ns => { - delete baseState.connections[ns]; + newConnectionsMap.delete(ns); }); + baseState.connections = newConnectionsMap; // Remove activeNamespace if it is in the list of namespaces using the connector if ( diff --git a/packages/ethers/src/adapter.ts b/packages/ethers/src/adapter.ts index 84ce97bc..c8b3a025 100644 --- a/packages/ethers/src/adapter.ts +++ b/packages/ethers/src/adapter.ts @@ -43,11 +43,7 @@ export class EthersAdapter extends EVMAdapter { const wei = await getEthBalance(rpcUrl, account); balance.amount = formatEther(wei); - this.emit('balanceChanged', { - namespace: this.getSupportedNamespace(), - address: balanceAddress, - balance - }); + this.emit('balanceChanged', { address: balanceAddress, balance }); return balance; } catch { diff --git a/packages/solana/src/adapter.ts b/packages/solana/src/adapter.ts index 4601865e..8728156a 100644 --- a/packages/solana/src/adapter.ts +++ b/packages/solana/src/adapter.ts @@ -56,11 +56,7 @@ export class SolanaAdapter extends SolanaBaseAdapter { }; } - this.emit('balanceChanged', { - namespace: this.getSupportedNamespace(), - address: balanceAddress, - balance - }); + this.emit('balanceChanged', { address: balanceAddress, balance }); return balance; } catch (error) { diff --git a/packages/wagmi/src/adapter.ts b/packages/wagmi/src/adapter.ts index 724ab4ef..d598cc3d 100644 --- a/packages/wagmi/src/adapter.ts +++ b/packages/wagmi/src/adapter.ts @@ -108,11 +108,7 @@ export class WagmiAdapter extends EVMAdapter { contractAddress: token ? (`${network.caipNetworkId}:${token}` as CaipAddress) : undefined }; - this.emit('balanceChanged', { - namespace: this.getSupportedNamespace(), - address: balanceAddress, - balance: formattedBalance - }); + this.emit('balanceChanged', { address: balanceAddress, balance: formattedBalance }); return Promise.resolve(formattedBalance); } From 793137c64022689f028f67519ebe15662b887fbc Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 29 May 2025 11:13:55 -0300 Subject: [PATCH 59/91] chore: removed animations --- packages/appkit/src/utils/UiUtil.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/appkit/src/utils/UiUtil.ts b/packages/appkit/src/utils/UiUtil.ts index 7288f410..842d3eba 100644 --- a/packages/appkit/src/utils/UiUtil.ts +++ b/packages/appkit/src/utils/UiUtil.ts @@ -4,24 +4,18 @@ import { StorageUtil, type WcWallet } from '@reown/appkit-core-react-native'; -import { - LayoutAnimation, - type LayoutAnimationProperty, - type LayoutAnimationType -} from 'react-native'; export const UiUtil = { TOTAL_VISIBLE_WALLETS: 4, createViewTransition: () => { - LayoutAnimation.configureNext(LayoutAnimation.create(200, 'easeInEaseOut', 'opacity')); + //TODO: replace this with reanimated + // LayoutAnimation.configureNext(LayoutAnimation.create(200, 'easeInEaseOut', 'opacity')); }, - animateChange: ( - type: LayoutAnimationType = 'linear', - creationProp: LayoutAnimationProperty = 'scaleX' - ) => { - LayoutAnimation.configureNext(LayoutAnimation.create(150, type, creationProp)); + animateChange: () => { + //TODO: replace this with reanimated + // LayoutAnimation.configureNext(LayoutAnimation.create(150, type, creationProp)); }, storeConnectedWallet: async ( From ae27abcde21b28c872e85a8721b5ad64bc167a31 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 29 May 2025 11:22:40 -0300 Subject: [PATCH 60/91] chore: renamed activeChain to caipNetwork --- packages/appkit/src/AppKit.ts | 12 ++++----- packages/appkit/src/hooks/useAccount.ts | 2 +- .../src/controllers/ConnectionsController.ts | 25 ++++++++++--------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 35f22a35..b073fc89 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -231,7 +231,7 @@ export class AppKit { } }); - ConnectionsController.setActiveChain( + ConnectionsController.setActiveNetwork( adapter.getSupportedNamespace(), `${adapter.getSupportedNamespace()}:${network.id}` as CaipNetworkId ); @@ -349,11 +349,11 @@ export class AppKit { ConnectionsController.updateAccounts(namespace, accounts); const network = this.networks.find( - n => n.id?.toString() === connection?.activeChain?.split(':')[1] + n => n.id?.toString() === connection?.caipNetwork?.split(':')[1] ); const address = accounts.find( - a => a.split(':')[1] === connection.activeChain?.split(':')[1] + a => a.split(':')[1] === connection.caipNetwork?.split(':')[1] ); if (address) { @@ -376,14 +376,14 @@ export class AppKit { const accounts = namespaceDetails.accounts ?? []; const chains = namespaceDetails.chains ?? []; - const activeChain = adapter?.connector?.getChainId(namespace); + const caipNetwork = adapter?.connector?.getChainId(namespace); ConnectionsController.storeConnection({ namespace, adapter, accounts, chains, - activeChain, + caipNetwork, wallet }); }); @@ -409,7 +409,7 @@ export class AppKit { adapter.on('chainChanged', ({ chainId }) => { const namespace = adapter.getSupportedNamespace(); const chain = `${namespace}:${chainId}` as CaipNetworkId; - ConnectionsController.setActiveChain(namespace, chain); + ConnectionsController.setActiveNetwork(namespace, chain); const network = this.networks.find(n => n.id?.toString() === chainId); if (network) { diff --git a/packages/appkit/src/hooks/useAccount.ts b/packages/appkit/src/hooks/useAccount.ts index 701cc315..ec558aeb 100644 --- a/packages/appkit/src/hooks/useAccount.ts +++ b/packages/appkit/src/hooks/useAccount.ts @@ -16,6 +16,6 @@ export function useAccount() { return { address: address?.split(':')[2], isConnected: !!address, - chainId: connection?.activeChain + chainId: connection?.caipNetwork }; } diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 751e6c2f..b34e4beb 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -19,8 +19,7 @@ interface Connection { accounts: CaipAddress[]; balances: Record; //TODO: make this an array of balances adapter: BlockchainAdapter; - chains: CaipNetworkId[]; - activeChain: CaipNetworkId; + caipNetwork: CaipNetworkId; wallet?: WalletInfo; } @@ -54,7 +53,7 @@ const derivedState = derive( //TODO: what happens if there are several accounts on the same chain? const activeAccount = connection.accounts.find(account => - account.startsWith(connection.activeChain) + account.startsWith(connection.caipNetwork) ); return activeAccount; @@ -70,7 +69,7 @@ const derivedState = derive( } const activeAccount = connection.accounts.find(account => - account.startsWith(connection.activeChain) + account.startsWith(connection.caipNetwork) ); if ( @@ -96,7 +95,7 @@ const derivedState = derive( return snap.networks.find( network => network.chainNamespace === snap.activeNamespace && - network.id?.toString() === connection.activeChain?.split(':')[1] + network.id?.toString() === connection.caipNetwork?.split(':')[1] ); }, activeCaipNetworkId: (get): CaipNetworkId | undefined => { @@ -108,7 +107,7 @@ const derivedState = derive( if (!connection) return undefined; - return connection.activeChain; + return connection.caipNetwork; }, walletInfo: (get): WalletInfo | undefined => { const snap = get(baseState); @@ -138,18 +137,18 @@ export const ConnectionsController = { accounts, chains, wallet, - activeChain + caipNetwork }: { namespace: ChainNamespace; adapter: BlockchainAdapter; accounts: CaipAddress[]; chains: CaipNetworkId[]; wallet?: WalletInfo; - activeChain?: CaipNetworkId; + caipNetwork?: CaipNetworkId; }) { const newConnectionEntry = { balances: {}, - activeChain: activeChain ?? chains[0]!, + caipNetwork: caipNetwork ?? chains[0]!, adapter: ref(adapter), accounts, chains, @@ -187,7 +186,7 @@ export const ConnectionsController = { baseState.connections = newConnectionsMap; }, - setActiveChain(namespace: ChainNamespace, chain: CaipNetworkId) { + setActiveNetwork(namespace: ChainNamespace, networkId: CaipNetworkId) { const connection = baseState.connections.get(namespace); if (!connection) { @@ -196,7 +195,7 @@ export const ConnectionsController = { baseState.connections.set(namespace, { ...connection, - activeChain: chain + caipNetwork: networkId }); }, @@ -207,7 +206,9 @@ export const ConnectionsController = { getConnectedNetworks() { return baseState.networks.filter( network => - baseState.connections.get(network.chainNamespace)?.chains.includes(network.caipNetworkId) + baseState.connections + .get(network.chainNamespace) + ?.accounts.some(account => account.startsWith(network.caipNetworkId)) ); }, From c45114c44960004b89f437fa5a407a87fd3797fc Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 29 May 2025 11:39:17 -0300 Subject: [PATCH 61/91] chore: init recent wallets --- packages/appkit/src/AppKit.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index b073fc89..3a1e7390 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -432,7 +432,7 @@ export class AppKit { } private async initControllers(options: AppKitConfig) { - await this.initAsyncValues(); + await this.initAsyncValues(options); OptionsController.setProjectId(options.projectId); OptionsController.setMetadata(options.metadata); @@ -469,7 +469,7 @@ export class AppKit { // } } - private async initAsyncValues() { + private async initActiveNamespace() { const activeNamespace = await StorageUtil.getActiveNamespace(); if (activeNamespace) { ConnectionsController.setActiveNamespace(activeNamespace); @@ -477,6 +477,35 @@ export class AppKit { ConnectionsController.setActiveNamespace(this.defaultNetwork?.chainNamespace); } } + + private async initRecentWallets(options: AppKitConfig) { + const wallets = await StorageUtil.getRecentWallets(); + const connectedWalletImage = await StorageUtil.getConnectedWalletImageUrl(); + + const filteredWallets = wallets.filter(wallet => { + const { includeWalletIds, excludeWalletIds } = options; + if (includeWalletIds) { + return includeWalletIds.includes(wallet.id); + } + if (excludeWalletIds) { + return !excludeWalletIds.includes(wallet.id); + } + + return true; + }); + + ConnectionController.setRecentWallets(filteredWallets); + + if (connectedWalletImage) { + ConnectionController.setConnectedWalletImageUrl(connectedWalletImage); + } + } + + private async initAsyncValues(options: AppKitConfig) { + await this.initActiveNamespace(); + await this.initRecentWallets(options); + //disable coinbase if connector is not set + } } export function createAppKit(config: AppKitConfig): AppKit { From d3ab4c16aee8937587b6a27bcab510abbe6cde4b Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 29 May 2025 11:47:46 -0300 Subject: [PATCH 62/91] chore: removed walletimage logic --- packages/appkit/src/client.ts | 5 ----- packages/core/src/controllers/ConnectionController.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index 420f1cac..b252a727 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -343,7 +343,6 @@ export class AppKitScaffold { private async initRecentWallets(options: ScaffoldOptions) { const wallets = await StorageUtil.getRecentWallets(); - const connectedWalletImage = await StorageUtil.getConnectedWalletImageUrl(); const filteredWallets = wallets.filter(wallet => { const { includeWalletIds, excludeWalletIds } = options; @@ -358,10 +357,6 @@ export class AppKitScaffold { }); ConnectionController.setRecentWallets(filteredWallets); - - if (connectedWalletImage) { - ConnectionController.setConnectedWalletImageUrl(connectedWalletImage); - } } private async initConnectedConnector() { diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 04b236bb..b09431ca 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -50,7 +50,7 @@ export interface ConnectionControllerState { pressedWallet?: WcWallet; recentWallets?: WcWallet[]; selectedSocialProvider?: SocialProvider; - connectedWalletImageUrl?: string; + connectedWalletImageUrl?: string; //TODO: remove this connectedSocialProvider?: SocialProvider; } From 063f52ebff7b5ac1dcdafba6cadafc0accf89c50 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:02:19 -0300 Subject: [PATCH 63/91] chore: created files for each adapter, enabled swaps --- apps/native/App.tsx | 18 +-- packages/appkit/src/AppKit.ts | 23 ++-- .../src/connectors/WalletConnectConnector.ts | 6 +- .../partials/w3m-account-activity/index.tsx | 7 +- .../w3m-account-wallet-features/index.tsx | 37 +++--- .../partials/w3m-send-input-token/index.tsx | 2 +- .../src/partials/w3m-swap-details/index.tsx | 8 +- .../views/w3m-account-default-view/index.tsx | 42 +++---- .../src/views/w3m-connect-view/index.tsx | 1 + .../src/views/w3m-swap-preview-view/index.tsx | 5 +- .../common/src/adapters/BlockchainAdapter.ts | 109 +++++++++++++++++ packages/common/src/adapters/EvmAdapter.ts | 78 ++++++++++++ .../common/src/adapters/SolanaBaseAdapter.ts | 5 + packages/common/src/index.ts | 3 + packages/common/src/utils/TypeUtil.ts | 111 ++---------------- .../src/controllers/ConnectionsController.ts | 49 ++++++-- .../core/src/controllers/SendController.ts | 10 +- .../core/src/controllers/SwapController.ts | 93 ++++++++------- packages/core/src/utils/ConstantsUtil.ts | 111 +++++++++++++++++- packages/core/src/utils/SwapApiUtil.ts | 20 ++-- packages/siwe/src/client.ts | 8 +- .../views/w3m-connecting-siwe-view/index.tsx | 10 +- 22 files changed, 510 insertions(+), 246 deletions(-) create mode 100644 packages/common/src/adapters/BlockchainAdapter.ts create mode 100644 packages/common/src/adapters/EvmAdapter.ts create mode 100644 packages/common/src/adapters/SolanaBaseAdapter.ts diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 2175dbac..6f3abd48 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -79,15 +79,15 @@ const appKit = createAppKit({ defaultNetwork: polygon, clipboardClient, debug: true, - enableAnalytics: true, - tokens: { - 'eip155:1': { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' - }, - 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': { - address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC SPL token - } - } + enableAnalytics: true + // tokens: { + // 'eip155:1': { + // address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' + // }, + // 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': { + // address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC SPL token + // } + // } }); export default function Native() { diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 3a1e7390..8e03ffe3 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -10,7 +10,8 @@ import { StorageUtil, type OptionsControllerState, ThemeController, - ConnectionController + ConnectionController, + type Features } from '@reown/appkit-core-react-native'; import type { @@ -26,7 +27,8 @@ import type { ThemeMode, WalletInfo, Network, - ChainNamespace + ChainNamespace, + ConnectOptions } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; @@ -53,7 +55,7 @@ interface AppKitConfig { themeVariables?: ThemeVariables; siweConfig?: AppKitSIWEClient; defaultNetwork?: Network; - // features?: Features; + features?: Features; // chainImages?: Record; //TODO: rename to networkImages } @@ -102,18 +104,17 @@ export class AppKit { /** * Handles the full connection flow for a given connector type. * @param type - The type of connector to use. - * @param requestedNamespaces - Optional specific namespaces to request. + * @param options - Optional connection options. */ - async connect(type: New_ConnectorType, requestedNamespaces?: ProposalNamespaces): Promise { + async connect(type: New_ConnectorType, options?: ConnectOptions): Promise { try { + const { namespaces, defaultChain, universalLink } = options ?? {}; const connector = await this.createConnector(type); - const defaultChain = this.defaultNetwork - ? NetworkUtil.getDefaultChainId(this.defaultNetwork) - : undefined; const approvedNamespaces = await connector.connect({ - namespaces: requestedNamespaces ?? this.namespaces, - defaultChain + namespaces: namespaces ?? this.namespaces, + defaultChain, + universalLink }); const walletInfo = connector.getWalletInfo(); @@ -443,7 +444,7 @@ export class AppKit { OptionsController.setCustomWallets(options.customWallets); OptionsController.setEnableAnalytics(options.enableAnalytics); OptionsController.setDebug(options.debug); - // OptionsController.setFeatures(options.features); + OptionsController.setFeatures(options.features); ThemeController.setThemeMode(options.themeMode); ThemeController.setThemeVariables(options.themeVariables); diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index dd6a28e4..09f18468 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -4,11 +4,11 @@ import { WalletConnector, type AppKitNetwork, type Namespaces, - type ProposalNamespaces, type Provider, type WalletInfo, type ChainNamespace, - type CaipNetworkId + type CaipNetworkId, + type ConnectOptions } from '@reown/appkit-common-react-native'; export class WalletConnectConnector extends WalletConnector { @@ -69,7 +69,7 @@ export class WalletConnectConnector extends WalletConnector { return this.provider.disconnect(); } - override async connect(opts: { namespaces: ProposalNamespaces; defaultChain?: CaipNetworkId }) { + override async connect(opts: ConnectOptions) { function onUri(uri: string) { ConnectionController.setWcUri(uri); } diff --git a/packages/appkit/src/partials/w3m-account-activity/index.tsx b/packages/appkit/src/partials/w3m-account-activity/index.tsx index dbbfa357..0b9a42ab 100644 --- a/packages/appkit/src/partials/w3m-account-activity/index.tsx +++ b/packages/appkit/src/partials/w3m-account-activity/index.tsx @@ -15,6 +15,7 @@ import { AccountController, AssetUtil, ConnectionsController, + ConstantsUtil, EventsController, OptionsController, TransactionsController @@ -32,9 +33,11 @@ export function AccountActivity({ style }: Props) { const [refreshing, setRefreshing] = useState(false); const [initialLoad, setInitialLoad] = useState(true); const { loading, transactions, next } = useSnapshot(TransactionsController.state); - const { activeNetwork, activeNamespace } = useSnapshot(ConnectionsController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const networkImage = AssetUtil.getNetworkImage(activeNetwork?.id); - const isSupported = activeNamespace && ['eip155', 'solana'].includes(activeNamespace); + const isSupported = + activeNetwork?.caipNetworkId && + ConstantsUtil.ACTIVITY_SUPPORTED_CHAINS.includes(activeNetwork.caipNetworkId); const handleLoadMore = () => { const address = ConnectionsController.state.activeAddress?.split(':')[2]; diff --git a/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx b/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx index 66de6277..ac8308ce 100644 --- a/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/appkit/src/partials/w3m-account-wallet-features/index.tsx @@ -3,10 +3,10 @@ import { useSnapshot } from 'valtio'; import { Balance, FlexView, IconLink, Tabs } from '@reown/appkit-ui-react-native'; import { AccountController, + ConnectionsController, ConstantsUtil, CoreHelperUtil, EventsController, - NetworkController, OnRampController, OptionsController, RouterController, @@ -25,8 +25,12 @@ export function AccountWalletFeatures() { const [activeTab, setActiveTab] = useState(0); const { tokenBalance } = useSnapshot(AccountController.state); const { features, isOnRampEnabled } = useSnapshot(OptionsController.state); + const { activeNetwork } = useSnapshot(ConnectionsController.state); const balance = CoreHelperUtil.calculateAndFormatBalance(tokenBalance as BalanceType[]); - const isSwapsEnabled = features?.swaps; + const isSwapsEnabled = + features?.swaps && + activeNetwork?.caipNetworkId && + ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(activeNetwork.caipNetworkId); const onTabChange = (index: number) => { setActiveTab(index); @@ -46,23 +50,16 @@ export function AccountWalletFeatures() { }; const onSwapPress = () => { - if ( - NetworkController.state.caipNetwork?.id && - !ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(`${NetworkController.state.caipNetwork.id}`) - ) { - RouterController.push('UnsupportedChain'); - } else { - SwapController.resetState(); - EventsController.sendEvent({ - type: 'track', - event: 'OPEN_SWAP', - properties: { - network: NetworkController.state.caipNetwork?.id || '', - isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' - } - }); - RouterController.push('Swap'); - } + SwapController.resetState(); + EventsController.sendEvent({ + type: 'track', + event: 'OPEN_SWAP', + properties: { + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } + }); + RouterController.push('Swap'); }; const onSendPress = () => { @@ -70,7 +67,7 @@ export function AccountWalletFeatures() { type: 'track', event: 'OPEN_SEND', properties: { - network: NetworkController.state.caipNetwork?.id || '', + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); diff --git a/packages/appkit/src/partials/w3m-send-input-token/index.tsx b/packages/appkit/src/partials/w3m-send-input-token/index.tsx index 8c5eb250..19634a00 100644 --- a/packages/appkit/src/partials/w3m-send-input-token/index.tsx +++ b/packages/appkit/src/partials/w3m-send-input-token/index.tsx @@ -43,7 +43,7 @@ export function SendInputToken({ const isNetworkToken = token.address === undefined || Object.values(ConstantsUtil.NATIVE_TOKEN_ADDRESS).some( - nativeAddress => token?.address === nativeAddress + nativeAddress => token?.address?.split(':')[2] === nativeAddress ); const numericGas = NumberUtil.bigNumber(gasPrice).shiftedBy(-token.quantity.decimals); diff --git a/packages/appkit/src/partials/w3m-swap-details/index.tsx b/packages/appkit/src/partials/w3m-swap-details/index.tsx index dd97e87a..c3b064a9 100644 --- a/packages/appkit/src/partials/w3m-swap-details/index.tsx +++ b/packages/appkit/src/partials/w3m-swap-details/index.tsx @@ -1,6 +1,10 @@ import { useSnapshot } from 'valtio'; import { useState } from 'react'; -import { ConstantsUtil, NetworkController, SwapController } from '@reown/appkit-core-react-native'; +import { + ConnectionsController, + ConstantsUtil, + SwapController +} from '@reown/appkit-core-react-native'; import { FlexView, Text, @@ -80,7 +84,7 @@ export function SwapDetails({ initialOpen, canClose }: SwapDetailsProps) { setModalData( getModalData('networkCost', { networkSymbol: SwapController.state.networkTokenSymbol, - networkName: NetworkController.state.caipNetwork?.name + networkName: ConnectionsController.state.activeNetwork?.name }) ); }; diff --git a/packages/appkit/src/views/w3m-account-default-view/index.tsx b/packages/appkit/src/views/w3m-account-default-view/index.tsx index 77c353aa..9774f547 100644 --- a/packages/appkit/src/views/w3m-account-default-view/index.tsx +++ b/packages/appkit/src/views/w3m-account-default-view/index.tsx @@ -59,7 +59,16 @@ export function AccountDefaultView() { const showExplorer = Object.keys(activeNetwork?.blockExplorers ?? {}).length > 0 && !isAuth; const showBack = history.length > 1; const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); - const showActivity = !isAuth && activeNamespace && ['eip155', 'solana'].includes(activeNamespace); + const showActivity = + !isAuth && + activeNamespace && + activeNetwork?.caipNetworkId && + ConstantsUtil.ACTIVITY_SUPPORTED_CHAINS.includes(activeNetwork.caipNetworkId); + const showSwaps = + !isAuth && + features?.swaps && + activeNetwork?.caipNetworkId && + ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(activeNetwork.caipNetworkId); const { padding } = useCustomDimensions(); const { disconnect } = useAppKit(); @@ -82,7 +91,7 @@ export function AccountDefaultView() { event: 'SET_PREFERRED_ACCOUNT_TYPE', properties: { accountType, - network: NetworkController.state.caipNetwork?.id || '' + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '' } }); } @@ -124,23 +133,16 @@ export function AccountDefaultView() { }; const onSwapPress = () => { - if ( - NetworkController.state.caipNetwork?.id && - !ConstantsUtil.SWAP_SUPPORTED_NETWORKS.includes(`${NetworkController.state.caipNetwork.id}`) - ) { - RouterController.push('UnsupportedChain'); - } else { - SwapController.resetState(); - EventsController.sendEvent({ - type: 'track', - event: 'OPEN_SWAP', - properties: { - network: NetworkController.state.caipNetwork?.id || '', - isSmartAccount: false - } - }); - RouterController.push('Swap'); - } + SwapController.resetState(); + EventsController.sendEvent({ + type: 'track', + event: 'OPEN_SWAP', + properties: { + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', + isSmartAccount: false + } + }); + RouterController.push('Swap'); }; const onBuyPress = () => { @@ -276,7 +278,7 @@ export function AccountDefaultView() { Buy crypto )} - {!isAuth && features?.swaps && ( + {showSwaps && ( c.type === 'WALLET_CONNECT'); const isWalletConnectEnabled = true; const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); diff --git a/packages/appkit/src/views/w3m-swap-preview-view/index.tsx b/packages/appkit/src/views/w3m-swap-preview-view/index.tsx index f3c8f77f..164ac7ea 100644 --- a/packages/appkit/src/views/w3m-swap-preview-view/index.tsx +++ b/packages/appkit/src/views/w3m-swap-preview-view/index.tsx @@ -90,7 +90,10 @@ export function SwapPreviewView() { ( + event: K, + payload: Parameters[0] + ): boolean { + return super.emit(event, payload); + } + + constructor({ + projectId, + supportedNamespace + }: { + projectId: string; + supportedNamespace: ChainNamespace; + }) { + super(); + this.projectId = projectId; + this.supportedNamespace = supportedNamespace; + } + + setConnector(connector: WalletConnector) { + this.connector = connector; + this.subscribeToEvents(); + } + + removeConnector() { + this.connector = undefined; + } + + getProvider(): Provider { + if (!this.connector) throw new Error('No active connector'); + + return this.connector.getProvider(); + } + + subscribeToEvents(): void { + const provider = this.connector?.getProvider(); + if (!provider) return; + + provider.on('chainChanged', this.onChainChanged.bind(this)); + provider.on('accountsChanged', this.onAccountsChanged.bind(this)); + provider.on('disconnect', this.onDisconnect.bind(this)); + } + + onChainChanged(chainId: string): void { + const _chains = this.getAccounts()?.map(account => account.split(':')[1]); + const shouldEmit = _chains?.some(chain => chain === chainId); + + if (shouldEmit) { + this.emit('chainChanged', { chainId }); + } + } + + onAccountsChanged(accounts: string[]): void { + const _accounts = this.getAccounts(); + const shouldEmit = _accounts?.some(account => { + const accountAddress = account.split(':')[2]; + + return accountAddress !== undefined && accounts.includes(accountAddress); + }); + + if (shouldEmit) { + this.emit('accountsChanged', { accounts }); + } + } + + onDisconnect(): void { + this.emit('disconnect', { namespace: this.getSupportedNamespace() }); + + const provider = this.connector?.getProvider(); + if (provider) { + provider.off('chainChanged', this.onChainChanged.bind(this)); + provider.off('accountsChanged', this.onAccountsChanged.bind(this)); + provider.off('disconnect', this.onDisconnect.bind(this)); + } + + this.connector = undefined; + } + + parseUnits(value: string, decimals: number): bigint { + const [whole, fraction = ''] = value.split('.'); + const paddedFraction = (fraction + '0'.repeat(decimals)).slice(0, decimals); + + return BigInt(whole + paddedFraction); + } + + abstract disconnect(): Promise; + abstract getSupportedNamespace(): ChainNamespace; + abstract getBalance(params: GetBalanceParams): Promise; + abstract getAccounts(): CaipAddress[] | undefined; + abstract switchNetwork(network: AppKitNetwork): Promise; +} diff --git a/packages/common/src/adapters/EvmAdapter.ts b/packages/common/src/adapters/EvmAdapter.ts new file mode 100644 index 00000000..386c33de --- /dev/null +++ b/packages/common/src/adapters/EvmAdapter.ts @@ -0,0 +1,78 @@ +import { BlockchainAdapter } from './BlockchainAdapter'; + +export abstract class EVMAdapter extends BlockchainAdapter { + async estimateGas({ address, to, data, chainNamespace }: any): Promise { + const provider = this.getProvider(); + + if (!provider) { + throw new Error('EVMAdapter:estimateGas - provider is undefined'); + } + + if (!address) { + throw new Error('EVMAdapter:estimateGas - from address is undefined'); + } + + if (chainNamespace && chainNamespace !== 'eip155') { + throw new Error('EVMAdapter:estimateGas - chainNamespace is not eip155'); + } + + try { + const txParams = { + from: address, + to, + data, + type: '0x0' // optional, legacy type + }; + + const estimatedGasHex = await provider.request({ + method: 'eth_estimateGas', + params: [txParams] + }); + + return BigInt(estimatedGasHex as string); + } catch (error) { + throw new Error('EVMAdapter:estimateGas - eth_estimateGas RPC failed'); + } + } + + async sendTransaction(data: any) { + const { address } = data || {}; + + if (!this.getProvider()) { + throw new Error('EVMAdapter:sendTransaction - provider is undefined'); + } + + if (!address) { + throw new Error('EVMAdapter:sendTransaction - address is undefined'); + } + + const txParams = { + from: address, + to: data.to, + value: data.value?.toString(), // hex string or decimal string + gas: data.gas?.toString(), // optional + gasPrice: data.gasPrice?.toString(), // optional + data: data.data, // hex-encoded bytecode + type: '0x0' // optional: legacy transaction type + }; + + const txHash = await this.getProvider().request({ + method: 'eth_sendTransaction', + params: [txParams] + }); + + let receipt = null; + while (!receipt) { + receipt = (await this.getProvider().request({ + method: 'eth_getTransactionReceipt', + params: [txHash] + })) as { blockHash?: `0x${string}` }; + + if (!receipt) { + await new Promise(r => setTimeout(r, 1000)); // wait 1s + } + } + + return receipt?.blockHash || null; + } +} diff --git a/packages/common/src/adapters/SolanaBaseAdapter.ts b/packages/common/src/adapters/SolanaBaseAdapter.ts new file mode 100644 index 00000000..6fd53e5b --- /dev/null +++ b/packages/common/src/adapters/SolanaBaseAdapter.ts @@ -0,0 +1,5 @@ +import { BlockchainAdapter } from './BlockchainAdapter'; + +export abstract class SolanaBaseAdapter extends BlockchainAdapter { + // solana logic +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 52d0a3bb..afd1547d 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -8,4 +8,7 @@ export { PresetsUtil } from './utils/PresetsUtil'; export { StringUtil } from './utils/StringUtil'; export { ErrorUtil } from './utils/ErrorUtil'; export { erc20ABI } from './contracts/erc20'; +export { BlockchainAdapter } from './adapters/BlockchainAdapter'; +export { EVMAdapter } from './adapters/EvmAdapter'; +export { SolanaBaseAdapter } from './adapters/SolanaBaseAdapter'; export * from './utils/TypeUtil'; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index c87fe00b..a0f39e1c 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -45,7 +45,7 @@ export interface Balance { name: string; symbol: string; chainId: string; - address?: string; + address?: CaipAddress; value?: number; price: number; quantity: BalanceQuantity; @@ -174,104 +174,6 @@ export interface AdapterEvents { balanceChanged: (event: BalanceChangedEvent) => void; } -//********** Adapter Types **********// -export abstract class BlockchainAdapter extends EventEmitter { - public projectId: string; - public connector?: WalletConnector; - public supportedNamespace: ChainNamespace; - - // Typed emit method - override emit( - event: K, - payload: Parameters[0] - ): boolean { - return super.emit(event, payload); - } - - constructor({ - projectId, - supportedNamespace - }: { - projectId: string; - supportedNamespace: ChainNamespace; - }) { - super(); - this.projectId = projectId; - this.supportedNamespace = supportedNamespace; - } - - setConnector(connector: WalletConnector) { - this.connector = connector; - this.subscribeToEvents(); - } - - removeConnector() { - this.connector = undefined; - } - - getProvider(): Provider { - if (!this.connector) throw new Error('No active connector'); - - return this.connector.getProvider(); - } - - subscribeToEvents(): void { - const provider = this.connector?.getProvider(); - if (!provider) return; - - provider.on('chainChanged', this.onChainChanged.bind(this)); - provider.on('accountsChanged', this.onAccountsChanged.bind(this)); - provider.on('disconnect', this.onDisconnect.bind(this)); - } - - onChainChanged(chainId: string): void { - const _chains = this.getAccounts()?.map(account => account.split(':')[1]); - const shouldEmit = _chains?.some(chain => chain === chainId); - - if (shouldEmit) { - this.emit('chainChanged', { chainId }); - } - } - - onAccountsChanged(accounts: string[]): void { - const _accounts = this.getAccounts(); - const shouldEmit = _accounts?.some(account => { - const accountAddress = account.split(':')[2]; - - return accountAddress !== undefined && accounts.includes(accountAddress); - }); - - if (shouldEmit) { - this.emit('accountsChanged', { accounts }); - } - } - - onDisconnect(): void { - this.emit('disconnect', { namespace: this.getSupportedNamespace() }); - - const provider = this.connector?.getProvider(); - if (provider) { - provider.off('chainChanged', this.onChainChanged.bind(this)); - provider.off('accountsChanged', this.onAccountsChanged.bind(this)); - provider.off('disconnect', this.onDisconnect.bind(this)); - } - - this.connector = undefined; - } - - abstract disconnect(): Promise; - abstract getSupportedNamespace(): ChainNamespace; - abstract getBalance(params: GetBalanceParams): Promise; - abstract getAccounts(): CaipAddress[] | undefined; - abstract switchNetwork(network: AppKitNetwork): Promise; -} - -export abstract class EVMAdapter extends BlockchainAdapter { - // ens logic -} - -export abstract class SolanaBaseAdapter extends BlockchainAdapter {} - export interface GetBalanceParams { address?: CaipAddress; network?: AppKitNetwork; @@ -303,6 +205,12 @@ export type ProposalNamespaces = Record< Omit & Required> >; +export type ConnectOptions = { + namespaces?: ProposalNamespaces; + defaultChain?: CaipNetworkId; + universalLink?: string; +}; + export abstract class WalletConnector extends EventEmitter { public type: New_ConnectorType; protected provider: Provider; @@ -315,10 +223,7 @@ export abstract class WalletConnector extends EventEmitter { this.provider = provider; } - abstract connect(opts: { - namespaces?: ProposalNamespaces; - defaultChain?: CaipNetworkId; - }): Promise; + abstract connect(opts: ConnectOptions): Promise; abstract disconnect(): Promise; abstract getProvider(): Provider; abstract getNamespaces(): Namespaces; diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index b34e4beb..68bc2c67 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -1,13 +1,14 @@ import { proxy, ref } from 'valtio'; import { derive } from 'valtio/utils'; -import type { - AppKitNetwork, - BlockchainAdapter, - CaipAddress, - CaipNetworkId, - ChainNamespace, - GetBalanceResponse, - WalletInfo +import { + EVMAdapter, + type AppKitNetwork, + type BlockchainAdapter, + type CaipAddress, + type CaipNetworkId, + type ChainNamespace, + type GetBalanceResponse, + type WalletInfo } from '@reown/appkit-common-react-native'; import { StorageUtil } from '../utils/StorageUtil'; @@ -254,5 +255,37 @@ export const ConnectionsController = { baseState.activeNamespace = undefined; StorageUtil.setActiveNamespace(undefined); } + }, + + parseUnits(value: string, decimals: number) { + if (!baseState.activeNamespace) return undefined; + + return baseState.connections + .get(baseState.activeNamespace) + ?.adapter.parseUnits(value, decimals); + }, + + async sendTransaction(args: any) { + if (!baseState.activeNamespace) return undefined; + + const adapter = baseState.connections.get(baseState.activeNamespace)?.adapter; + + if (adapter instanceof EVMAdapter) { + return adapter.sendTransaction(args); + } + + return undefined; + }, + + async estimateGas(args: any) { + if (!baseState.activeNamespace) return undefined; + + const adapter = baseState.connections.get(baseState.activeNamespace)?.adapter; + + if (adapter instanceof EVMAdapter) { + return adapter.estimateGas(args); + } + + return undefined; } }; diff --git a/packages/core/src/controllers/SendController.ts b/packages/core/src/controllers/SendController.ts index 3aa0141e..a24c74c9 100644 --- a/packages/core/src/controllers/SendController.ts +++ b/packages/core/src/controllers/SendController.ts @@ -6,8 +6,8 @@ import { ConnectionController } from './ConnectionController'; import { SnackController } from './SnackController'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { EventsController } from './EventsController'; -import { NetworkController } from './NetworkController'; import { RouterController } from './RouterController'; +import { ConnectionsController } from './ConnectionsController'; // -- Types --------------------------------------------- // export interface TxParams { @@ -100,7 +100,7 @@ export const SendController = { isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token.address, amount: this.state.sendTokenAmount, - network: NetworkController.state.caipNetwork?.id || '' + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '' } }); this.sendERC20Token({ @@ -123,7 +123,7 @@ export const SendController = { isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token?.symbol, amount: this.state.sendTokenAmount, - network: NetworkController.state.caipNetwork?.id || '' + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '' } }); this.sendNativeToken({ @@ -165,7 +165,7 @@ export const SendController = { isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token?.symbol || '', amount: params.sendTokenAmount, - network: NetworkController.state.caipNetwork?.id || '' + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '' } }); this.resetSend(); @@ -178,7 +178,7 @@ export const SendController = { isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token?.symbol || '', amount: params.sendTokenAmount, - network: NetworkController.state.caipNetwork?.id || '' + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '' } }); SnackController.showError('Something went wrong'); diff --git a/packages/core/src/controllers/SwapController.ts b/packages/core/src/controllers/SwapController.ts index 6a4db8c7..0b8aa16c 100644 --- a/packages/core/src/controllers/SwapController.ts +++ b/packages/core/src/controllers/SwapController.ts @@ -1,10 +1,9 @@ import { subscribeKey as subKey } from 'valtio/utils'; import { proxy, subscribe as sub } from 'valtio'; -import { NumberUtil } from '@reown/appkit-common-react-native'; +import { NumberUtil, type CaipAddress } from '@reown/appkit-common-react-native'; import { ConstantsUtil } from '../utils/ConstantsUtil'; import { SwapApiUtil } from '../utils/SwapApiUtil'; -import { NetworkController } from './NetworkController'; import { BlockchainApiController } from './BlockchainApiController'; import { OptionsController } from './OptionsController'; import { SwapCalculationUtil } from '../utils/SwapCalculationUtil'; @@ -14,7 +13,6 @@ import type { SwapInputTarget, SwapTokenWithBalance } from '../utils/TypeUtil'; import { ConnectorController } from './ConnectorController'; import { AccountController } from './AccountController'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; -import { ConnectionController } from './ConnectionController'; import { TransactionsController } from './TransactionsController'; import { EventsController } from './EventsController'; import { ConnectionsController } from './ConnectionsController'; @@ -166,9 +164,7 @@ export const SwapController = { throw new Error('No active namespace or network found to swap the tokens from.'); } - const networkAddress = `${activeNetwork.caipNetworkId ?? 'eip155:1'}:${ - ConstantsUtil.NATIVE_TOKEN_ADDRESS[activeNamespace] - }`; + const networkAddress: CaipAddress = `${activeNetwork.caipNetworkId}:${ConstantsUtil.NATIVE_TOKEN_ADDRESS[activeNamespace]}`; const type = ConnectorController.state.connectedConnector; @@ -291,7 +287,6 @@ export const SwapController = { async getMyTokensWithBalance(forceUpdate?: string) { const balances = await SwapApiUtil.getMyTokensWithBalance(forceUpdate); - if (!balances) { return; } @@ -410,7 +405,7 @@ export const SwapController = { setBalances(balances: SwapTokenWithBalance[]) { const { networkAddress } = this.getParams(); - const caipNetwork = NetworkController.state.caipNetwork; + const caipNetwork = ConnectionsController.state.activeNetwork; if (!caipNetwork) { return; @@ -422,7 +417,9 @@ export const SwapController = { state.tokensPriceMap[token.address] = token.price || 0; }); - state.myTokensWithBalance = balances.filter(token => token.address?.startsWith(caipNetwork.id)); + state.myTokensWithBalance = balances.filter( + token => token.address?.startsWith(caipNetwork.caipNetworkId) + ); state.networkBalanceInUSD = networkToken ? NumberUtil.multiply(networkToken.quantity.numeric, networkToken.price).toString() @@ -473,12 +470,12 @@ export const SwapController = { // -- Swap ---------------------------------------------- // async swapTokens() { - const address = AccountController.state.address as `${string}:${string}:${string}`; + const address = ConnectionsController.state.activeAddress; const sourceToken = state.sourceToken; const toToken = state.toToken; const haveSourceTokenAmount = NumberUtil.bigNumber(state.sourceTokenAmount).isGreaterThan(0); - if (!toToken || !sourceToken || state.loadingPrices || !haveSourceTokenAmount) { + if (!toToken || !sourceToken || state.loadingPrices || !haveSourceTokenAmount || !address) { return; } @@ -488,39 +485,43 @@ export const SwapController = { .multipliedBy(10 ** sourceToken.decimals) .integerValue(); - const quoteResponse = await BlockchainApiController.fetchSwapQuote({ - userAddress: address, - projectId: OptionsController.state.projectId, - from: sourceToken.address, - to: toToken.address, - gasPrice: state.gasFee, - amount: amountDecimal.toString() - }); + try { + const quoteResponse = await BlockchainApiController.fetchSwapQuote({ + userAddress: address, + projectId: OptionsController.state.projectId, + from: sourceToken.address, + to: toToken.address, + gasPrice: state.gasFee, + amount: amountDecimal.toString() + }); - state.loadingQuote = false; + state.loadingQuote = false; - const quoteToAmount = quoteResponse?.quotes?.[0]?.toAmount; + const quoteToAmount = quoteResponse?.quotes?.[0]?.toAmount; - if (!quoteToAmount) { - return; - } + if (!quoteToAmount) { + return; + } - const toTokenAmount = NumberUtil.bigNumber(quoteToAmount) - .dividedBy(10 ** toToken.decimals) - .toString(); + const toTokenAmount = NumberUtil.bigNumber(quoteToAmount) + .dividedBy(10 ** toToken.decimals) + .toString(); - this.setToTokenAmount(toTokenAmount); + this.setToTokenAmount(toTokenAmount); - const isInsufficientToken = this.hasInsufficientToken( - state.sourceTokenAmount, - sourceToken.address - ); + const isInsufficientToken = this.hasInsufficientToken( + state.sourceTokenAmount, + sourceToken.address + ); - if (isInsufficientToken) { - state.inputError = 'Insufficient balance'; - } else { - state.inputError = undefined; - this.setTransactionDetails(); + if (isInsufficientToken) { + state.inputError = 'Insufficient balance'; + } else { + state.inputError = undefined; + this.setTransactionDetails(); + } + } catch (error) { + console.log('swapTokens error', error); } }, @@ -556,6 +557,7 @@ export const SwapController = { return transaction; } catch (error) { + console.log('getTransaction error', error); RouterController.goBack(); SnackController.showError('Failed to check allowance'); state.loadingBuildTransaction = false; @@ -589,7 +591,7 @@ export const SwapController = { if (!response) { throw new Error('createAllowanceTransaction - No response from generateApproveCalldata'); } - const gasLimit = await ConnectionController.estimateGas({ + const gasLimit = await ConnectionsController.estimateGas({ address: fromAddress as `0x${string}`, to: CoreHelperUtil.getPlainAddress(response.tx.to) as `0x${string}`, data: response.tx.data @@ -642,7 +644,7 @@ export const SwapController = { return undefined; } - const amount = ConnectionController.parseUnits( + const amount = ConnectionsController.parseUnits( sourceTokenAmount, sourceToken.decimals )?.toString(); @@ -710,13 +712,13 @@ export const SwapController = { } try { - await ConnectionController.sendTransaction({ + await ConnectionsController.sendTransaction({ address: fromAddress as `0x${string}`, to: data.to as `0x${string}`, data: data.data as `0x${string}`, value: BigInt(data.value), gasPrice: BigInt(data.gasPrice), - chainNamespace: 'eip155' + chainNamespace: ConnectionsController.state.activeNamespace }); await this.swapTokens(); @@ -762,14 +764,14 @@ export const SwapController = { try { const forceUpdateAddresses = [state.sourceToken?.address, state.toToken?.address].join(','); - const transactionHash = await ConnectionController.sendTransaction({ + const transactionHash = await ConnectionsController.sendTransaction({ address: fromAddress as `0x${string}`, to: data.to as `0x${string}`, data: data.data as `0x${string}`, gas: data.gas, gasPrice: BigInt(data.gasPrice), value: data.value, - chainNamespace: 'eip155' + chainNamespace: ConnectionsController.state.activeNamespace }); state.loadingTransaction = false; @@ -778,7 +780,7 @@ export const SwapController = { type: 'track', event: 'SWAP_SUCCESS', properties: { - network: NetworkController.state.caipNetwork?.id || '', + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', swapFromToken: this.state.sourceToken?.symbol || '', swapToToken: this.state.toToken?.symbol || '', swapFromAmount: this.state.sourceTokenAmount || '', @@ -801,6 +803,7 @@ export const SwapController = { return transactionHash; } catch (err) { + console.log('sendTransactionForSwap error', err); const error = err as TransactionError; state.transactionError = error?.shortMessage; state.loadingTransaction = false; @@ -810,7 +813,7 @@ export const SwapController = { event: 'SWAP_ERROR', properties: { message: error?.shortMessage ?? error?.message ?? 'Unknown', - network: NetworkController.state.caipNetwork?.id || '', + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', swapFromToken: this.state.sourceToken?.symbol || '', swapToToken: this.state.toToken?.symbol || '', swapFromAmount: this.state.sourceTokenAmount || '', diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 840e195d..e5308553 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -3,7 +3,7 @@ import type { Features } from './TypeUtil'; //TODO: enable this again after implemented const defaultFeatures: Features = { - swaps: false, + swaps: true, onramp: false, email: false, emailShowWallets: false, @@ -135,6 +135,115 @@ export const ConstantsUtil = { 'WNT' ], + ACTIVITY_SUPPORTED_CHAINS: [ + // Arbitrum + 'eip155:42161', + // BNB Chain + 'eip155:56', + // Ethereum + 'eip155:1', + // Blast + 'eip155:81457', + // Ape Chain + 'eip155:99999', + // Avalanche + 'eip155:43114', + // Abstract + 'eip155:900', + // opBNB + 'eip155:204', + // Astar zkEVM + 'eip155:3776', + // ZKsync Era + 'eip155:324', + // Berachain + 'eip155:80085', + // BOB + 'eip155:60808', + // Cyber + 'eip155:7560', + // Degen Chain + 'eip155:666666666', + // Fraxtal + 'eip155:252', + // Gravity Alpha + 'eip155:10003', + // Ink + 'eip155:999', + // Lens + 'eip155:1348', + // Lisk + 'eip155:113', + // Mode + 'eip155:34443', + // Base + 'eip155:8453', + // Mantle + 'eip155:5000', + // Optimism + 'eip155:10', + // Polygon + 'eip155:137', + // Celo + 'eip155:42220', + // Manta Pacific + 'eip155:169', + // Gnosis Chain + 'eip155:100', + // Fantom + 'eip155:250', + // Ronin + 'eip155:2020', + // Linea + 'eip155:59144', + // Metis Andromeda + 'eip155:1088', + // Aurora + 'eip155:1313161554', + // XDC + 'eip155:50', + // Cronos zkEVM + 'eip155:1030', + // Polygon zkEVM + 'eip155:1101', + // Polynomial + 'eip155:80001', + // Rari + 'eip155:1380012617', + // Redstone + 'eip155:690', + // Scroll + 'eip155:534352', + // Sei + 'eip155:1329', + // Soneium + 'eip155:1499', + // Sonic + 'eip155:7007', + // Swellchain + 'eip155:7777777', + // Taiko + 'eip155:167000', + // Viction + 'eip155:88', + // Unichain + 'eip155:12345', + // Wonder + 'eip155:8787', + // X Layer + 'eip155:196', + // World Chain + 'eip155:2008', + // ZERϴ + 'eip155:77777', + // ZkLink Nova + 'eip155:810180', + // re.al + 'eip155:666', + // Zora + 'eip155:7777777' + ], + SWAP_SUPPORTED_NETWORKS: [ // Ethereum' 'eip155:1', diff --git a/packages/core/src/utils/SwapApiUtil.ts b/packages/core/src/utils/SwapApiUtil.ts index 9156e14c..2345134c 100644 --- a/packages/core/src/utils/SwapApiUtil.ts +++ b/packages/core/src/utils/SwapApiUtil.ts @@ -1,18 +1,19 @@ import { BlockchainApiController } from '../controllers/BlockchainApiController'; import { OptionsController } from '../controllers/OptionsController'; -import { NetworkController } from '../controllers/NetworkController'; import type { BlockchainApiBalanceResponse, BlockchainApiSwapAllowanceRequest, SwapTokenWithBalance } from './TypeUtil'; import { AccountController } from '../controllers/AccountController'; -import { ConnectionController } from '../controllers/ConnectionController'; import { ConnectionsController } from '../controllers/ConnectionsController'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; +import { ConstantsUtil } from './ConstantsUtil'; export const SwapApiUtil = { async getTokenList() { - const chainId = ConnectionsController.state.activeNetwork?.caipNetworkId ?? 'eip155:1'; + const chainId: CaipNetworkId = + ConnectionsController.state.activeNetwork?.caipNetworkId ?? 'eip155:1'; const response = await BlockchainApiController.fetchSwapTokens({ projectId: OptionsController.state.projectId, chainId @@ -54,7 +55,7 @@ export const SwapApiUtil = { if (response?.allowance && sourceTokenAmount && sourceTokenDecimals) { const parsedValue = - ConnectionController.parseUnits(sourceTokenAmount, sourceTokenDecimals) || 0; + ConnectionsController.parseUnits(sourceTokenAmount, sourceTokenDecimals) || 0; const hasAllowance = BigInt(response.allowance) >= parsedValue; return hasAllowance; @@ -84,12 +85,17 @@ export const SwapApiUtil = { }, mapBalancesToSwapTokens(balances?: BlockchainApiBalanceResponse['balances']) { + const { activeNamespace, activeCaipNetworkId } = ConnectionsController.state; + const address = activeNamespace + ? ConstantsUtil.NATIVE_TOKEN_ADDRESS[activeNamespace] + : undefined; + return ( balances?.map( token => ({ ...token, - address: token?.address || NetworkController.getActiveNetworkTokenAddress(), + address: token?.address ?? `${token?.chainId ?? activeCaipNetworkId}:${address}`, decimals: parseInt(token.quantity.decimals, 10), logoUri: token.iconUrl, eip2612: false @@ -100,7 +106,7 @@ export const SwapApiUtil = { async fetchGasPrice() { const projectId = OptionsController.state.projectId; - const caipNetwork = NetworkController.state.caipNetwork; + const caipNetwork = ConnectionsController.state.activeNetwork; if (!caipNetwork) { return null; @@ -108,7 +114,7 @@ export const SwapApiUtil = { return await BlockchainApiController.fetchGasPrice({ projectId, - chainId: caipNetwork.id + chainId: caipNetwork.caipNetworkId }); } }; diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index c0c28441..b4da23af 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -1,8 +1,8 @@ import { AccountController, - NetworkController, ConnectionController, - RouterUtil + RouterUtil, + ConnectionsController } from '@reown/appkit-core-react-native'; import { NetworkUtil } from '@reown/appkit-common-react-native'; @@ -92,7 +92,9 @@ export class AppKitSIWEClient { if (!address) { throw new Error('An address is required to create a SIWE message.'); } - const chainId = NetworkUtil.caipNetworkIdToNumber(NetworkController.state.caipNetwork?.id); + const chainId = NetworkUtil.caipNetworkIdToNumber( + ConnectionsController.state.activeNetwork?.caipNetworkId + ); if (!chainId) { throw new Error('A chainId is required to create a SIWE message.'); } diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx index a45e251f..d9cf0efb 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx +++ b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx @@ -11,9 +11,9 @@ import { AccountController, AssetUtil, ConnectionController, + ConnectionsController, EventsController, ModalController, - NetworkController, OptionsController, RouterController, SnackController @@ -40,7 +40,7 @@ export function ConnectingSiweView() { event: 'CLICK_SIGN_SIWE_MESSAGE', type: 'track', properties: { - network: NetworkController.state.caipNetwork?.id || '', + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); @@ -51,7 +51,7 @@ export function ConnectingSiweView() { event: 'SIWE_AUTH_SUCCESS', type: 'track', properties: { - network: NetworkController.state.caipNetwork?.id || '', + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); @@ -66,7 +66,7 @@ export function ConnectingSiweView() { event: 'SIWE_AUTH_ERROR', type: 'track', properties: { - network: NetworkController.state.caipNetwork?.id || '', + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); @@ -89,7 +89,7 @@ export function ConnectingSiweView() { event: 'CLICK_CANCEL_SIWE', type: 'track', properties: { - network: NetworkController.state.caipNetwork?.id || '', + network: ConnectionsController.state.activeNetwork?.caipNetworkId || '', isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); From 9e79be0174949569e78cfc9773d0da06f45bcf22 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 3 Jun 2025 10:56:04 -0300 Subject: [PATCH 64/91] chore: convert tx to hex, ui improvements --- .../src/partials/w3m-swap-input/index.tsx | 2 +- .../src/views/w3m-swap-preview-view/index.tsx | 4 +- .../appkit/src/views/w3m-swap-view/index.tsx | 14 ++-- packages/common/src/adapters/EvmAdapter.ts | 7 +- packages/common/src/utils/NumberUtil.ts | 65 +++++++++++++++---- .../core/src/controllers/SwapController.ts | 6 +- packages/core/src/utils/ConstantsUtil.ts | 1 + packages/ui/src/utils/UiUtil.ts | 20 +++--- 8 files changed, 82 insertions(+), 37 deletions(-) diff --git a/packages/appkit/src/partials/w3m-swap-input/index.tsx b/packages/appkit/src/partials/w3m-swap-input/index.tsx index 16db2698..c43621bc 100644 --- a/packages/appkit/src/partials/w3m-swap-input/index.tsx +++ b/packages/appkit/src/partials/w3m-swap-input/index.tsx @@ -128,7 +128,7 @@ export function SwapInput({ > {isMarketValueGreaterThanZero - ? `~$${UiUtil.formatNumberToLocalString(marketValue, 2)}` + ? `~$${UiUtil.formatNumberToLocalString(marketValue, 6)}` : ''} {showMax && ( diff --git a/packages/appkit/src/views/w3m-swap-preview-view/index.tsx b/packages/appkit/src/views/w3m-swap-preview-view/index.tsx index 164ac7ea..f3d02da0 100644 --- a/packages/appkit/src/views/w3m-swap-preview-view/index.tsx +++ b/packages/appkit/src/views/w3m-swap-preview-view/index.tsx @@ -86,7 +86,7 @@ export function SwapPreviewView() { Send - ${UiUtil.formatNumberToLocalString(sourceTokenMarketValue, 2)} + ${UiUtil.formatNumberToLocalString(sourceTokenMarketValue, 6)} - ${UiUtil.formatNumberToLocalString(toTokenMarketValue, 2)} + ${UiUtil.formatNumberToLocalString(toTokenMarketValue, 6)} { - const isNetworkToken = - SwapController.state.sourceToken?.address === - NetworkController.getActiveNetworkTokenAddress(); + const { activeNamespace, activeCaipNetworkId } = ConnectionsController.state; + const networkTokenAddress = activeNamespace + ? `${activeCaipNetworkId}:${ConstantsUtil.NATIVE_TOKEN_ADDRESS[activeNamespace]}` + : undefined; + + const isNetworkToken = SwapController.state.sourceToken?.address === networkTokenAddress; const _gasPriceInUSD = SwapController.state.gasPriceInUSD; const _sourceTokenPriceInUSD = SwapController.state.sourceTokenPriceInUSD; diff --git a/packages/common/src/adapters/EvmAdapter.ts b/packages/common/src/adapters/EvmAdapter.ts index 386c33de..b9aa2556 100644 --- a/packages/common/src/adapters/EvmAdapter.ts +++ b/packages/common/src/adapters/EvmAdapter.ts @@ -1,4 +1,5 @@ import { BlockchainAdapter } from './BlockchainAdapter'; +import { NumberUtil } from '../utils/NumberUtil'; export abstract class EVMAdapter extends BlockchainAdapter { async estimateGas({ address, to, data, chainNamespace }: any): Promise { @@ -49,9 +50,9 @@ export abstract class EVMAdapter extends BlockchainAdapter { const txParams = { from: address, to: data.to, - value: data.value?.toString(), // hex string or decimal string - gas: data.gas?.toString(), // optional - gasPrice: data.gasPrice?.toString(), // optional + value: NumberUtil.convertNumericToHexString(data.value), + gas: NumberUtil.convertNumericToHexString(data.gas), + gasPrice: NumberUtil.convertNumericToHexString(data.gasPrice), data: data.data, // hex-encoded bytecode type: '0x0' // optional: legacy transaction type }; diff --git a/packages/common/src/utils/NumberUtil.ts b/packages/common/src/utils/NumberUtil.ts index 2f0e44b6..c24fe18d 100644 --- a/packages/common/src/utils/NumberUtil.ts +++ b/packages/common/src/utils/NumberUtil.ts @@ -1,6 +1,12 @@ import * as BigNumber from 'bignumber.js'; export const NumberUtil = { + /** + * Creates a BigNumber instance from a given value. + * If the value is a string, commas are removed before conversion. + * @param value - The input value (string, number, or BigNumber) to convert to a BigNumber. + * @returns A BigNumber instance. + */ bigNumber(value: BigNumber.BigNumber.Value) { if (typeof value === 'string') { return new BigNumber.BigNumber(value.replace(/,/g, '')); @@ -10,10 +16,11 @@ export const NumberUtil = { }, /** - * Multiply two numbers represented as strings with BigNumber to handle decimals correctly - * @param a string - * @param b string - * @returns + * Multiplies two numbers using BigNumber for precision, especially with decimals. + * Handles undefined inputs by returning BigNumber(0). + * @param a - The first multiplicand (string, number, or BigNumber). Commas are removed if it's a string. + * @param b - The second multiplicand (string, number, or BigNumber). Commas are removed if it's a string. + * @returns The product as a BigNumber instance, or BigNumber(0) if either input is undefined. */ multiply(a: BigNumber.BigNumber.Value | undefined, b: BigNumber.BigNumber.Value | undefined) { if (a === undefined || b === undefined) { @@ -26,6 +33,13 @@ export const NumberUtil = { return aBigNumber.multipliedBy(bBigNumber); }, + /** + * Rounds a number to a specified number of decimal places if its string representation meets a certain length threshold. + * @param number - The number to potentially round. + * @param threshold - The minimum string length of the number to trigger rounding. + * @param fixed - The number of decimal places to round to. + * @returns The rounded number (as a string if rounded, otherwise the original number) or the original number. + */ roundNumber(number: number, threshold: number, fixed: number) { const roundedNumber = number.toString().length >= threshold ? Number(number).toFixed(fixed) : number; @@ -33,6 +47,12 @@ export const NumberUtil = { return roundedNumber; }, + /** + * Calculates the next multiple of ten greater than or equal to the given amount. + * Defaults to 10 if no amount is provided or if the calculated multiple is less than 10. + * @param amount - The number for which to find the next multiple of ten. Optional. + * @returns The next multiple of ten, at least 10. + */ nextMultipleOfTen(amount?: number) { if (!amount) return 10; @@ -40,10 +60,10 @@ export const NumberUtil = { }, /** - * Format the given number or string to human readable numbers with the given number of decimals - * @param value - The value to format. It could be a number or string. If it's a string, it will be parsed to a float then formatted. - * @param decimals - number of decimals after dot - * @returns + * Formats a number or string to a human-readable string with a specified number of decimal places, using US locale formatting. + * @param value - The value to format (string, number, or undefined). If undefined, returns '0.00'. + * @param decimals - The number of decimal places to display. Defaults to 2. + * @returns A locale-formatted string representation of the number. */ formatNumberToLocalString(value: string | number | undefined, decimals = 2) { if (value === undefined) { @@ -62,10 +82,11 @@ export const NumberUtil = { minimumFractionDigits: decimals }); }, + /** - * Parse a formatted local string back to a number - * @param value - The formatted string to parse - * @returns + * Parses a locale-formatted numeric string (e.g., with commas) back into a number. + * @param value - The formatted string to parse. If undefined, returns 0. + * @returns The parsed number, or 0 if the input is undefined. */ parseLocalStringToNumber(value: string | undefined) { if (value === undefined) { @@ -74,5 +95,27 @@ export const NumberUtil = { // Remove any commas used as thousand separators and parse the float return parseFloat(value.replace(/,/gu, '')); + }, + + /** + * Converts a numeric value (BigInt, number, or string representation of a number) to a 0x-prefixed hexadecimal string. + * This is often required for Ethereum RPC parameters like value, gas, gasPrice. + * @param value - The value to convert. Can be BigInt, number, or a string (decimal or hex). + * @returns A 0x-prefixed hexadecimal string, or undefined if the input is undefined or null. + * @throws Error if the value cannot be converted to BigInt. + */ + convertNumericToHexString: (value: any): string | undefined => { + if (value === undefined || value === null) { + return undefined; + } + try { + // This handles BigInt, number, or string representation of a number (decimal or hex) + const bigIntValue = BigInt(value); + // Ethereum RPC spec requires "0x0" for zero, and other values to be 0x-prefixed hex. + + return '0x' + bigIntValue.toString(16); + } catch (error) { + throw new Error(`NumberUtil: Invalid parameter, cannot convert to hex: ${value}`); + } } }; diff --git a/packages/core/src/controllers/SwapController.ts b/packages/core/src/controllers/SwapController.ts index 0b8aa16c..eaab6c91 100644 --- a/packages/core/src/controllers/SwapController.ts +++ b/packages/core/src/controllers/SwapController.ts @@ -521,7 +521,8 @@ export const SwapController = { this.setTransactionDetails(); } } catch (error) { - console.log('swapTokens error', error); + SnackController.showError('Failed to get swap quote'); + state.loadingQuote = false; } }, @@ -557,7 +558,6 @@ export const SwapController = { return transaction; } catch (error) { - console.log('getTransaction error', error); RouterController.goBack(); SnackController.showError('Failed to check allowance'); state.loadingBuildTransaction = false; @@ -737,7 +737,6 @@ export const SwapController = { if (!data) { return undefined; } - const { fromAddress, toTokenAmount, isAuthConnector } = this.getParams(); state.loadingTransaction = true; @@ -803,7 +802,6 @@ export const SwapController = { return transactionHash; } catch (err) { - console.log('sendTransactionForSwap error', err); const error = err as TransactionError; state.transactionError = error?.shortMessage; state.loadingTransaction = false; diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index e5308553..be46e00b 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -135,6 +135,7 @@ export const ConstantsUtil = { 'WNT' ], + //TODO: replace with supported chains from backend ACTIVITY_SUPPORTED_CHAINS: [ // Arbitrum 'eip155:42161', diff --git a/packages/ui/src/utils/UiUtil.ts b/packages/ui/src/utils/UiUtil.ts index bca68b00..db9b2011 100644 --- a/packages/ui/src/utils/UiUtil.ts +++ b/packages/ui/src/utils/UiUtil.ts @@ -71,20 +71,18 @@ export const UiUtil = { }, formatNumberToLocalString(value: string | number | undefined, decimals = 2) { - if (value === undefined) { - return '0.00'; - } + let numericValue: number; - if (typeof value === 'number') { - return value.toLocaleString('en-US', { - maximumFractionDigits: decimals, - minimumFractionDigits: decimals - }); + if (value === undefined) { + numericValue = 0; + } else if (typeof value === 'string') { + numericValue = parseFloat(value); + } else { + numericValue = value; } - return parseFloat(value).toLocaleString('en-US', { - maximumFractionDigits: decimals, - minimumFractionDigits: decimals + return numericValue.toLocaleString('en-US', { + maximumFractionDigits: decimals }); } }; From 4c0be74f77f53e7f4f9548584a817f4965360f00 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:16:55 -0300 Subject: [PATCH 65/91] chore: implementing phantom connector + general improvements --- .eslintrc.json | 3 +- apps/native/App.tsx | 30 +- apps/native/app.json | 46 +- apps/native/package.json | 1 + apps/native/src/utils/WagmiUtils.ts | 6 +- apps/native/src/views/SolanaActionsView.tsx | 227 +++++++- packages/appkit/src/AppKit.ts | 28 +- .../src/connectors/WalletConnectConnector.ts | 80 ++- packages/appkit/src/index.ts | 8 +- packages/appkit/src/networks/index.ts | 6 - packages/appkit/src/utils/HelpersUtil.ts | 6 +- packages/common/src/index.ts | 2 + .../src/networks/bitcoin.ts | 2 +- .../{appkit => common}/src/networks/solana.ts | 2 +- packages/common/src/utils/TypeUtil.ts | 67 ++- .../core/src/controllers/OptionsController.ts | 18 +- packages/core/src/utils/TypeUtil.ts | 15 +- packages/solana/package.json | 10 +- .../solana/src/connectors/PhantomConnector.ts | 329 +++++++++++ packages/solana/src/helpers.ts | 16 +- packages/solana/src/index.ts | 8 + packages/solana/src/index.tsx | 2 - .../solana/src/providers/PhantomProvider.ts | 535 ++++++++++++++++++ packages/solana/src/types.ts | 131 +++++ packages/ui/package.json | 1 + yarn.lock | 354 +++++++++++- 26 files changed, 1812 insertions(+), 121 deletions(-) delete mode 100644 packages/appkit/src/networks/index.ts rename packages/{appkit => common}/src/networks/bitcoin.ts (91%) rename packages/{appkit => common}/src/networks/solana.ts (95%) create mode 100644 packages/solana/src/connectors/PhantomConnector.ts create mode 100644 packages/solana/src/index.ts delete mode 100644 packages/solana/src/index.tsx create mode 100644 packages/solana/src/providers/PhantomProvider.ts create mode 100644 packages/solana/src/types.ts diff --git a/.eslintrc.json b/.eslintrc.json index f4fb725c..3e22e1f2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,8 @@ "react-hooks/exhaustive-deps": "warn", "no-console": ["error", { "allow": ["warn"] }], "newline-before-return": "error", - "radix": "off" + "radix": "off", + "dot-notation": "off" }, "parserOptions": { "requireConfigFile": false diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 6f3abd48..004b1530 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -21,17 +21,18 @@ import { Button, Text } from '@reown/appkit-ui-react-native'; // import { siweConfig } from './src/utils/SiweUtils'; // import { AccountView } from './src/views/AccountView'; -// import { chains } from './src/utils/WagmiUtils'; +import { chains } from './src/utils/WagmiUtils'; import { OpenButton } from './src/components/OpenButton'; import { DisconnectButton } from './src/components/DisconnectButton'; // import { EthersAdapter } from '@reown/appkit-ethers-react-native'; -import { SolanaAdapter } from '@reown/appkit-solana-react-native'; +import { SolanaAdapter, PhantomConnector } from '@reown/appkit-solana-react-native'; import { BitcoinAdapter } from '@reown/appkit-bitcoin-react-native'; import { WagmiAdapter } from '@reown/appkit-wagmi-react-native'; -import { mainnet, polygon, avalanche, zora, sepolia } from 'wagmi/chains'; import { ActionsView } from './src/views/ActionsView'; import { WalletInfoView } from './src/views/WalletInfoView'; import { EventsView } from './src/views/EventsView'; +import { getCustomWallets } from './src/utils/misc'; +import AsyncStorage from '@react-native-async-storage/async-storage'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -46,6 +47,18 @@ const metadata = { } }; +const storage = { + setItem: async (key: string, value: string) => { + await AsyncStorage.setItem(key, value); + }, + getItem: async (key: string) => { + return await AsyncStorage.getItem(key); + }, + removeItem: async (key: string) => { + await AsyncStorage.removeItem(key); + } +}; + const clipboardClient = { setString: async (value: string) => { await Clipboard.setStringAsync(value); @@ -60,7 +73,7 @@ const queryClient = new QueryClient(); const wagmiAdapter = new WagmiAdapter({ projectId, - networks: [mainnet, polygon, avalanche, zora, sepolia] + networks: chains }); const solanaAdapter = new SolanaAdapter({ @@ -75,11 +88,14 @@ const appKit = createAppKit({ projectId, adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter], metadata, - networks: [mainnet, polygon, avalanche, zora, sepolia, solana, bitcoin], - defaultNetwork: polygon, + networks: [...chains, solana, bitcoin], + defaultNetwork: chains[2], clipboardClient, debug: true, - enableAnalytics: true + enableAnalytics: true, + customWallets: getCustomWallets(), + storage, + extraConnectors: [new PhantomConnector({ cluster: 'mainnet-beta' })] // tokens: { // 'eip155:1': { // address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' diff --git a/apps/native/app.json b/apps/native/app.json index 95e1fe6d..c71e27f0 100644 --- a/apps/native/app.json +++ b/apps/native/app.json @@ -20,14 +20,53 @@ "policy": "appVersion" }, "owner": "nacho.reown", - "assetBundlePatterns": ["**/*"], - "plugins": ["./expo-plugins/installed-wallets.js"], + "assetBundlePatterns": [ + "**/*" + ], + "plugins": [ + "./expo-plugins/installed-wallets.js" + ], "ios": { "buildNumber": "1", "bundleIdentifier": "com.walletconnect.web3modal.rnsdk", "supportsTablet": true, "infoPlist": { "LSApplicationQueriesSchemes": [ + "metamask", + "trust", + "safe", + "rainbow", + "uniswap", + "zerion", + "imtokenv2", + "argent", + "spot", + "omni", + "dfw", + "tpoutside", + "robinhood-wallet", + "frontier", + "blockchain-wallet", + "safepalwallet", + "bitkeep", + "zengo", + "oneinch", + "bnc", + "exodus", + "ledgerlive", + "mewwallet", + "awallet", + "keyring", + "lobstr", + "ontoprovider", + "mathwallet", + "unstoppabledomains", + "obvious", + "fireblocks-wc", + "ambire", + "internetmoney", + "walletnow", + "bitcoincom", "metamask", "trust", "safe", @@ -63,7 +102,8 @@ "internetmoney", "walletnow", "bitcoincom" - ] + ], + "ITSAppUsesNonExemptEncryption": false } }, "android": { diff --git a/apps/native/package.json b/apps/native/package.json index 616b0b9e..c1f04b40 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -29,6 +29,7 @@ "@reown/appkit-react-native": "1.2.3", "@reown/appkit-solana-react-native": "1.2.3", "@reown/appkit-wagmi-react-native": "1.2.3", + "@solana/web3.js": "^1.98.2", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-query-persist-client": "5.56.2", diff --git a/apps/native/src/utils/WagmiUtils.ts b/apps/native/src/utils/WagmiUtils.ts index f2b1da93..935ef5f4 100644 --- a/apps/native/src/utils/WagmiUtils.ts +++ b/apps/native/src/utils/WagmiUtils.ts @@ -10,8 +10,7 @@ import { zora, base, celo, - aurora, - sepolia + aurora } from 'wagmi/chains'; export const chains: CreateConfigParameters['chains'] = [ @@ -25,6 +24,5 @@ export const chains: CreateConfigParameters['chains'] = [ zora, base, celo, - aurora, - sepolia + aurora ]; diff --git a/apps/native/src/views/SolanaActionsView.tsx b/apps/native/src/views/SolanaActionsView.tsx index 2305ee02..b6432028 100644 --- a/apps/native/src/views/SolanaActionsView.tsx +++ b/apps/native/src/views/SolanaActionsView.tsx @@ -2,6 +2,13 @@ import { StyleSheet } from 'react-native'; import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; import { useAccount, useProvider } from '@reown/appkit-react-native'; import base58 from 'bs58'; +import { + Connection, + SystemProgram, + Transaction, + PublicKey, + LAMPORTS_PER_SOL +} from '@solana/web3.js'; import { ToastUtils } from '../utils/ToastUtils'; @@ -10,45 +17,230 @@ export function SolanaActionsView() { const { address, chainId } = useAccount(); const { provider } = useProvider('solana'); - const onSignSuccess = (data: any) => { - ToastUtils.showSuccessToast('Sign successful', data); + const onSignSuccess = (data: any, title = 'Sign successful') => { + ToastUtils.showSuccessToast(title, data); }; - const onSignError = (error: Error) => { - ToastUtils.showErrorToast('Sign failed', error.message); + const onSignError = (error: Error, title = 'Sign failed') => { + ToastUtils.showErrorToast(title, error.message); }; const signMessage = async () => { try { if (!provider) { - ToastUtils.showErrorToast('Sign failed', 'No provider found'); + ToastUtils.showErrorToast('Sign Message failed', 'No provider found'); return; } - if (!address) { - ToastUtils.showErrorToast('Sign failed', 'No address found'); + ToastUtils.showErrorToast('Sign Message failed', 'No address found'); return; } const encodedMessage = new TextEncoder().encode('Hello from AppKit Solana'); - const params = { message: base58.encode(encodedMessage), pubkey: address + // For Phantom, pubkey is not part of signMessage params directly with session + // For other wallets, it might be needed if they don't infer from session }; - const { signature } = (await provider.request( { method: 'solana_signMessage', params }, chainId - )) as { address: string; signature: string }; + )) as { signature: string }; + onSignSuccess(signature, 'Sign Message successful'); + } catch (error) { + onSignError(error as Error, 'Sign Message failed'); + } + }; + + const signTransaction = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign Transaction failed', 'No provider found'); - onSignSuccess(signature); + return; + } + if (!address) { + ToastUtils.showErrorToast('Sign Transaction failed', 'No address found'); + + return; + } + const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed'); + const recipientPubKey = new PublicKey('ComputeBudget111111111111111111111111111111'); + const senderPubKey = new PublicKey(address); + const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: senderPubKey, + toPubkey: recipientPubKey, + lamports: 0.00001 * LAMPORTS_PER_SOL + }) + ); + transaction.feePayer = senderPubKey; + const { blockhash } = await connection.getLatestBlockhash(); + transaction.recentBlockhash = blockhash; + const serializedTransaction = transaction.serialize({ + requireAllSignatures: false, + verifySignatures: false + }); + const base58EncodedTransaction = base58.encode(serializedTransaction); + const params = { transaction: base58EncodedTransaction }; + const result = (await provider.request( + { + method: 'solana_signTransaction', + params + }, + chainId + )) as { signature?: string; transaction?: string }; + if (result.signature) { + onSignSuccess(`Signature: ${result.signature}`, 'Sign Transaction successful'); + } else if (result.transaction) { + onSignSuccess( + `Signed Tx (bs58): ${result.transaction.substring(0, 60)}...`, + 'Sign Transaction successful' + ); + } else { + onSignSuccess( + 'Transaction signed (no specific signature/tx field in response)', + 'Sign Transaction successful' + ); + } + } catch (error: any) { + onSignError(error as Error, 'Sign Transaction failed'); + } + }; + + const signAndSendTransaction = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign & Send Tx failed', 'No provider found'); + + return; + } + if (!address) { + ToastUtils.showErrorToast('Sign & Send Tx failed', 'No address found'); + + return; + } + const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed'); + const recipientPubKey = new PublicKey('ComputeBudget111111111111111111111111111111'); + const senderPubKey = new PublicKey(address); + const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: senderPubKey, + toPubkey: recipientPubKey, + lamports: 0.00002 * LAMPORTS_PER_SOL // Slightly different amount for distinction + }) + ); + transaction.feePayer = senderPubKey; + const { blockhash } = await connection.getLatestBlockhash(); + transaction.recentBlockhash = blockhash; + const serializedTransaction = transaction.serialize({ + requireAllSignatures: false, + verifySignatures: false + }); + const base58EncodedTransaction = base58.encode(serializedTransaction); + const params = { transaction: base58EncodedTransaction }; + // The result for signAndSendTransaction is typically the transaction signature + const { signature } = (await provider.request( + { + method: 'solana_signAndSendTransaction', + params + }, + chainId + )) as { signature: string }; + onSignSuccess(`Tx Signature: ${signature}`, 'Sign & Send Tx successful'); + // Optionally, you can confirm the transaction here using the signature and connection + // await connection.confirmTransaction(signature, 'confirmed'); + // ToastUtils.showInfoToast('Transaction confirmation pending...'); + } catch (error) { + onSignError(error as Error, 'Sign & Send Tx failed'); + } + }; + + const signAllTransactions = async () => { + try { + if (!provider) { + ToastUtils.showErrorToast('Sign All Txs failed', 'No provider found'); + + return; + } + if (!address) { + ToastUtils.showErrorToast('Sign All Txs failed', 'No address found'); + + return; + } + const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed'); + const senderPubKey = new PublicKey(address); + const recipient1PubKey = new PublicKey('ComputeBudget111111111111111111111111111111'); + const recipient2PubKey = new PublicKey('Vote111111111111111111111111111111111111111'); // Different recipient for variety + + const { blockhash } = await connection.getLatestBlockhash(); + + const tx1 = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: senderPubKey, + toPubkey: recipient1PubKey, + lamports: 0.00003 * LAMPORTS_PER_SOL + }) + ); + tx1.feePayer = senderPubKey; + tx1.recentBlockhash = blockhash; + + const tx2 = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: senderPubKey, + toPubkey: recipient2PubKey, + lamports: 0.00004 * LAMPORTS_PER_SOL + }) + ); + tx2.feePayer = senderPubKey; + tx2.recentBlockhash = blockhash; + + const serializedTx1 = base58.encode( + tx1.serialize({ requireAllSignatures: false, verifySignatures: false }) + ); + const serializedTx2 = base58.encode( + tx2.serialize({ requireAllSignatures: false, verifySignatures: false }) + ); + + const params = { transactions: [serializedTx1, serializedTx2] }; + + // The result for signAllTransactions is typically an array of signed transactions or signatures + const result = (await provider.request( + { + method: 'solana_signAllTransactions', + params + }, + chainId + )) as { transactions?: string[]; signatures?: string[] }; // Adjust based on provider's typical response + + if (result.transactions) { + onSignSuccess( + `Signed ${result.transactions.length} Txs (bs58): Tx1: ${result.transactions[0].substring( + 0, + 30 + )}...`, + 'Sign All Txs successful' + ); + } else if (result.signatures) { + onSignSuccess( + `Signed ${ + result.signatures.length + } Txs (signatures): Sig1: ${result.signatures[0].substring(0, 30)}...`, + 'Sign All Txs successful' + ); + } else { + onSignSuccess( + 'All transactions signed (response format varies)', + 'Sign All Txs successful' + ); + } } catch (error) { - onSignError(error as Error); + onSignError(error as Error, 'Sign All Txs failed'); } }; @@ -56,7 +248,16 @@ export function SolanaActionsView() { Solana Actions + + + ) : null; diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 8e03ffe3..95860889 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -1,4 +1,5 @@ import { + type Features, AccountController, EventsController, ModalController, @@ -6,12 +7,10 @@ import { OptionsController, RouterController, TransactionsController, - type Metadata, StorageUtil, type OptionsControllerState, ThemeController, - ConnectionController, - type Features + ConnectionController } from '@reown/appkit-core-react-native'; import type { @@ -20,6 +19,7 @@ import type { ProposalNamespaces, New_ConnectorType, Namespaces, + Metadata, CaipNetworkId, AppKitNetwork, Provider, @@ -28,7 +28,8 @@ import type { WalletInfo, Network, ChainNamespace, - ConnectOptions + ConnectOptions, + Storage } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; @@ -42,6 +43,7 @@ interface AppKitConfig { metadata: Metadata; adapters: BlockchainAdapter[]; networks: Network[]; + storage: Storage; extraConnectors?: WalletConnector[]; clipboardClient?: OptionsControllerState['clipboardClient']; includeWalletIds?: OptionsControllerState['includeWalletIds']; @@ -253,15 +255,28 @@ export class AppKit { private async createConnector(type: New_ConnectorType): Promise { // Check if an extra connector was provided by the developer const CustomConnector = this.extraConnectors.find( - connector => connector.constructor.name.toLowerCase() === type.toLowerCase() + connector => connector.type.toLowerCase() === type.toLowerCase() ); if (CustomConnector) { + await CustomConnector.init({ + storage: OptionsController.state.storage!, + metadata: this.metadata + }); + return CustomConnector; } // Default to WalletConnectConnector if no custom connector matches - return WalletConnectConnector.create({ projectId: this.projectId, metadata: this.metadata }); + const walletConnectConnector = new WalletConnectConnector({ + projectId: this.projectId + }); + await walletConnectConnector.init({ + storage: OptionsController.state.storage!, + metadata: this.metadata + }); + + return walletConnectConnector; } //TODO: reuse logic with connect method @@ -445,6 +460,7 @@ export class AppKit { OptionsController.setEnableAnalytics(options.enableAnalytics); OptionsController.setDebug(options.debug); OptionsController.setFeatures(options.features); + OptionsController.setStorage(options.storage); ThemeController.setThemeMode(options.themeMode); ThemeController.setThemeVariables(options.themeVariables); diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 09f18468..ff30bf31 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -1,4 +1,4 @@ -import { type Metadata, ConnectionController } from '@reown/appkit-core-react-native'; +import { ConnectionController } from '@reown/appkit-core-react-native'; import { UniversalProvider, type IUniversalProvider } from '@walletconnect/universal-provider'; import { WalletConnector, @@ -8,14 +8,41 @@ import { type WalletInfo, type ChainNamespace, type CaipNetworkId, - type ConnectOptions + type ConnectOptions, + type ConnectorInitOptions, + type Metadata } from '@reown/appkit-common-react-native'; +interface WalletConnectConnectorConfig { + projectId: string; +} + export class WalletConnectConnector extends WalletConnector { - private static universalProviderInstance: IUniversalProvider | null = null; + private readonly config: WalletConnectConnectorConfig; + + constructor(config: WalletConnectConnectorConfig) { + super({ type: 'walletconnect' }); + this.config = config; + } + + override async init(ops: ConnectorInitOptions) { + super.init(ops); + + const provider = await this.getUniversalProvider({ + projectId: this.config.projectId, + metadata: ops.metadata + }); + + this.provider = provider as Provider; + + await this.restoreSession(); + } - private constructor(provider: IUniversalProvider) { - super({ type: 'walletconnect', provider: provider as Provider }); + private async restoreSession(): Promise { + const provider = this.getProvider() as IUniversalProvider; + if (!provider) { + return false; + } if (provider.session?.namespaces) { this.namespaces = provider.session.namespaces as Namespaces; @@ -31,42 +58,30 @@ export class WalletConnectConnector extends WalletConnector { }; } } + + return true; } - private static async getUniversalProvider({ + private async getUniversalProvider({ projectId, metadata }: { projectId: string; metadata: Metadata; }): Promise { - if (!WalletConnectConnector.universalProviderInstance) { - WalletConnectConnector.universalProviderInstance = await UniversalProvider.init({ + if (!this.provider) { + this.provider = (await UniversalProvider.init({ projectId, - metadata - }); + metadata, + storage: this.storage + })) as Provider; } - return WalletConnectConnector.universalProviderInstance; - } - - public static async create({ - projectId, - metadata - }: { - projectId: string; - metadata: Metadata; - }): Promise { - const provider = await WalletConnectConnector.getUniversalProvider({ - projectId, - metadata - }); - - return new WalletConnectConnector(provider); + return this.provider as IUniversalProvider; } override disconnect(): Promise { - return this.provider.disconnect(); + return this.getProvider().disconnect(); } override async connect(opts: ConnectOptions) { @@ -74,7 +89,10 @@ export class WalletConnectConnector extends WalletConnector { ConnectionController.setWcUri(uri); } - this.provider.on('display_uri', onUri); + const provider = this.getProvider() as IUniversalProvider; + + // @ts-ignore + provider.on('display_uri', onUri); const session = await (this.provider as IUniversalProvider).connect({ namespaces: {}, @@ -87,12 +105,16 @@ export class WalletConnectConnector extends WalletConnector { this.namespaces = session?.namespaces as Namespaces; - this.provider.off('display_uri', onUri); + provider.off('display_uri', onUri); return this.namespaces; } override getProvider(): Provider { + if (!this.provider) { + throw new Error('WalletConnectConnector: Provider not initialized. Call init() first.'); + } + return this.provider; } diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 8cce9b03..c6b74231 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -21,8 +21,10 @@ export type * from '@reown/appkit-core-react-native'; export { CoreHelperUtil } from '@reown/appkit-core-react-native'; export * from './AppKit'; -export * from './networks'; export { AppKitProvider } from './AppKitContext'; + +export type { AppKitNetwork } from '@reown/appkit-common-react-native'; + export { WalletConnectConnector } from './connectors/WalletConnectConnector'; /****** Hooks *******/ @@ -31,3 +33,7 @@ export { useProvider } from './hooks/useProvider'; export { useAccount } from './hooks/useAccount'; export { useWalletInfo } from './hooks/useWalletInfo'; export { useAppKitEvents, useAppKitEventSubscription } from './hooks/useAppKitEvents'; + +/********** Networks **********/ +export { solana, solanaDevnet, solanaTestnet } from '@reown/appkit-common-react-native'; +export { bitcoin, bitcoinTestnet } from '@reown/appkit-common-react-native'; diff --git a/packages/appkit/src/networks/index.ts b/packages/appkit/src/networks/index.ts deleted file mode 100644 index 5f2e141a..00000000 --- a/packages/appkit/src/networks/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -// -- Networks --------------------------------------------------------------- -export * from './solana'; -export * from './bitcoin'; - -// -- Types --------------------------------------------------------------- -export type { AppKitNetwork } from '@reown/appkit-common-react-native'; diff --git a/packages/appkit/src/utils/HelpersUtil.ts b/packages/appkit/src/utils/HelpersUtil.ts index 33bd4db5..8b5043c7 100644 --- a/packages/appkit/src/utils/HelpersUtil.ts +++ b/packages/appkit/src/utils/HelpersUtil.ts @@ -1,14 +1,10 @@ import type { Namespace, NamespaceConfig } from '@walletconnect/universal-provider'; - import type { AppKitNetwork, CaipNetworkId, ChainNamespace } from '@reown/appkit-common-react-native'; -import { solana, solanaDevnet } from '../networks/solana'; -// import { EnsController, type OptionsControllerState } from '@reown/appkit-controllers' - -// import { solana, solanaDevnet } from '../networks/index.js' +import { solana, solanaDevnet } from '@reown/appkit-common-react-native'; export const DEFAULT_METHODS = { solana: [ diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index afd1547d..603ae105 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -8,6 +8,8 @@ export { PresetsUtil } from './utils/PresetsUtil'; export { StringUtil } from './utils/StringUtil'; export { ErrorUtil } from './utils/ErrorUtil'; export { erc20ABI } from './contracts/erc20'; +export { solana, solanaDevnet, solanaTestnet } from './networks/solana'; +export { bitcoin, bitcoinTestnet } from './networks/bitcoin'; export { BlockchainAdapter } from './adapters/BlockchainAdapter'; export { EVMAdapter } from './adapters/EvmAdapter'; export { SolanaBaseAdapter } from './adapters/SolanaBaseAdapter'; diff --git a/packages/appkit/src/networks/bitcoin.ts b/packages/common/src/networks/bitcoin.ts similarity index 91% rename from packages/appkit/src/networks/bitcoin.ts rename to packages/common/src/networks/bitcoin.ts index 327bb85f..3d7cc57b 100644 --- a/packages/appkit/src/networks/bitcoin.ts +++ b/packages/common/src/networks/bitcoin.ts @@ -1,4 +1,4 @@ -import type { AppKitNetwork } from '@reown/appkit-common-react-native'; +import type { AppKitNetwork } from '../utils/TypeUtil'; export const bitcoin: AppKitNetwork = { id: '000000000019d6689c085ae165831e93', diff --git a/packages/appkit/src/networks/solana.ts b/packages/common/src/networks/solana.ts similarity index 95% rename from packages/appkit/src/networks/solana.ts rename to packages/common/src/networks/solana.ts index f88cd71c..404c2ef3 100644 --- a/packages/appkit/src/networks/solana.ts +++ b/packages/common/src/networks/solana.ts @@ -1,4 +1,4 @@ -import type { AppKitNetwork } from '@reown/appkit-common-react-native'; +import type { AppKitNetwork } from '../utils/TypeUtil'; export const solana: AppKitNetwork = { id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index a0f39e1c..83d9b323 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -146,6 +146,18 @@ export type Tokens = Record; export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; +export type Metadata = { + name: string; + description: string; + url: string; + icons: string[]; + redirect?: { + native?: string; + universal?: string; + linkMode?: boolean; + }; +}; + //********** Adapter Event Payloads **********// export type AccountsChangedEvent = { accounts: string[]; @@ -202,7 +214,8 @@ export type Namespaces = Record; export type ProposalNamespaces = Record< string, - Omit & Required> + Omit & + Required> & { rpcMap: Record } >; export type ConnectOptions = { @@ -211,15 +224,30 @@ export type ConnectOptions = { universalLink?: string; }; +export type ConnectorInitOptions = { + storage: Storage; + metadata: Metadata; +}; + export abstract class WalletConnector extends EventEmitter { public type: New_ConnectorType; - protected provider: Provider; + protected provider?: Provider; protected namespaces?: Namespaces; protected wallet?: WalletInfo; + protected storage?: Storage; + protected metadata?: Metadata; - constructor({ type, provider }: { type: New_ConnectorType; provider: Provider }) { + constructor({ type }: { type: New_ConnectorType }) { super(); this.type = type; + } + + public async init(ops: ConnectorInitOptions) { + this.storage = ops.storage; + this.metadata = ops.metadata; + } + + public setProvider(provider: Provider) { this.provider = provider; } @@ -252,7 +280,7 @@ export interface RequestArguments { } //TODO: rename this and remove the old one ConnectorType -export type New_ConnectorType = 'walletconnect' | 'coinbase' | 'auth'; +export type New_ConnectorType = 'walletconnect' | 'coinbase' | 'auth' | 'phantom'; //********** Others **********// @@ -275,3 +303,34 @@ export interface WalletInfo { }; [key: string]: unknown; } + +export interface Storage { + /** + * Returns all keys in storage. + */ + getKeys(): Promise; + + /** + * Returns all key-value entries in storage. + */ + getEntries(): Promise<[string, T][]>; + + /** + * Get an item from storage for a given key. + * @param key The key to retrieve. + */ + getItem(key: string): Promise; + + /** + * Set an item in storage for a given key. + * @param key The key to set. + * @param value The value to set. + */ + setItem(key: string, value: T): Promise; + + /** + * Remove an item from storage for a given key. + * @param key The key to remove. + */ + removeItem(key: string): Promise; +} diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index 753ef500..4f646003 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -1,13 +1,6 @@ import { proxy, ref } from 'valtio'; -import type { Tokens } from '@reown/appkit-common-react-native'; -import type { - CustomWallet, - Features, - Metadata, - ProjectId, - SdkType, - SdkVersion -} from '../utils/TypeUtil'; +import type { Tokens, Storage, Metadata } from '@reown/appkit-common-react-native'; +import type { CustomWallet, Features, ProjectId, SdkType, SdkVersion } from '../utils/TypeUtil'; import { ConstantsUtil } from '../utils/ConstantsUtil'; @@ -19,6 +12,7 @@ export interface ClipboardClient { export interface OptionsControllerState { projectId: ProjectId; clipboardClient?: ClipboardClient; + storage?: Storage; includeWalletIds?: string[]; excludeWalletIds?: string[]; featuredWalletIds?: string[]; @@ -103,6 +97,12 @@ export const OptionsController = { state.isOnRampEnabled = isOnRampEnabled; }, + setStorage(storage?: OptionsControllerState['storage']) { + if (storage) { + state.storage = ref(storage); + } + }, + isClipboardAvailable() { return !!state.clipboardClient; }, diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index fd251133..1fb06640 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -6,7 +6,8 @@ import type { SocialProvider, ThemeMode, Transaction, - ConnectorType + ConnectorType, + Metadata } from '@reown/appkit-common-react-native'; import { OnRampErrorType } from './ConstantsUtil'; @@ -339,18 +340,6 @@ export type BlockchainApiOnRampWidgetResponse = { }; // -- OptionsController Types --------------------------------------------------- -export type Metadata = { - name: string; - description: string; - url: string; - icons: string[]; - redirect?: { - native?: string; - universal?: string; - linkMode?: boolean; - }; -}; - export type CustomWallet = Pick< WcWallet, | 'id' diff --git a/packages/solana/package.json b/packages/solana/package.json index 5eea0626..736c470c 100644 --- a/packages/solana/package.json +++ b/packages/solana/package.json @@ -4,7 +4,8 @@ "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", - "source": "src/index.tsx", + "react-native": "src/index.ts", + "source": "src/index.ts", "scripts": { "build": "bob build", "clean": "rm -rf lib", @@ -38,7 +39,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3" - }, - "react-native": "src/index.tsx" + "@reown/appkit-common-react-native": "1.2.3", + "bs58": "6.0.0", + "tweetnacl": "1.0.3" + } } diff --git a/packages/solana/src/connectors/PhantomConnector.ts b/packages/solana/src/connectors/PhantomConnector.ts new file mode 100644 index 00000000..896a8979 --- /dev/null +++ b/packages/solana/src/connectors/PhantomConnector.ts @@ -0,0 +1,329 @@ +import { + WalletConnector, + type AppKitNetwork, + type CaipNetworkId, + type ChainNamespace, + type ConnectOptions, + type Namespaces, + type WalletInfo, + type CaipAddress, + type ConnectorInitOptions, + type Storage, + solana, + solanaDevnet, + solanaTestnet +} from '@reown/appkit-common-react-native'; +import nacl from 'tweetnacl'; +import bs58 from 'bs58'; + +import { PhantomProvider, SOLANA_SIGNING_METHODS } from '../providers/PhantomProvider'; +import type { + PhantomCluster, + PhantomConnectorConfig, + PhantomConnectorSessionData, + PhantomProviderConfig +} from '../types'; + +const SOLANA_CLUSTER_TO_CHAIN_ID_PART: Record = { + 'mainnet-beta': solana.id as string, + 'testnet': solanaTestnet.id as string, + 'devnet': solanaDevnet.id as string +}; + +const PHANTOM_CONNECTOR_STORAGE_KEY = '@appkit/phantom-connector-data'; +const DAPP_KEYPAIR_STORAGE_KEY = '@appkit/phantom-dapp-secret-key'; + +export class PhantomConnector extends WalletConnector { + private readonly config: PhantomConnectorConfig; + + private currentCaipNetworkId: CaipNetworkId | null = null; + private dappEncryptionKeyPair?: nacl.BoxKeyPair; + + private static readonly SUPPORTED_NAMESPACE: ChainNamespace = 'solana'; + + constructor(config: PhantomConnectorConfig) { + super({ type: 'phantom' }); + this.config = config; + } + + override async init(ops: ConnectorInitOptions) { + super.init(ops); + this.storage = ops.storage; + await this.initializeKeyPair(); + + const appScheme = ops.metadata.redirect?.universal; + if (!appScheme) { + throw new Error( + 'Phantom Connector: No universal link found in metadata. Please add redirect.universal to the metadata.' + ); + } + + const providerConfig: PhantomProviderConfig = { + appScheme, + dappUrl: ops.metadata.url, + storage: ops.storage, + dappEncryptionKeyPair: this.dappEncryptionKeyPair! + }; + + this.provider = new PhantomProvider(providerConfig); + this.restoreSession(); + } + + private async initializeKeyPair(): Promise { + try { + const secretKeyB58 = await this.getStorage().getItem(DAPP_KEYPAIR_STORAGE_KEY); + if (secretKeyB58) { + const secretKey = bs58.decode(secretKeyB58); + this.dappEncryptionKeyPair = nacl.box.keyPair.fromSecretKey(secretKey); + } else { + const newKeyPair = nacl.box.keyPair(); + this.dappEncryptionKeyPair = newKeyPair; + await this.getStorage().setItem( + DAPP_KEYPAIR_STORAGE_KEY, + bs58.encode(newKeyPair.secretKey) + ); + } + } catch (error) { + // disconnect and clear session + await this.disconnect(); + throw error; + } + } + + override async connect(opts?: ConnectOptions): Promise { + if (this.isConnected()) { + return this.namespaces; + } + + const defaultChain = + opts?.defaultChain?.split(':')?.[0] === 'solana' + ? opts?.defaultChain?.split(':')[1] + : opts?.namespaces?.['solana']?.chains?.[0]?.split(':')[1]; + + const requestedCluster = + this.config.cluster ?? + (Object.keys(SOLANA_CLUSTER_TO_CHAIN_ID_PART).find( + key => + SOLANA_CLUSTER_TO_CHAIN_ID_PART[key as keyof typeof SOLANA_CLUSTER_TO_CHAIN_ID_PART] === + defaultChain + ) as PhantomCluster | undefined); + + try { + const connectResult = await this.getProvider().connect({ cluster: requestedCluster }); + + const solanaChainIdPart = SOLANA_CLUSTER_TO_CHAIN_ID_PART[connectResult.cluster]; + if (!solanaChainIdPart) { + throw new Error( + `Phantom Connect: Internal - Unknown cluster mapping for ${connectResult.cluster}` + ); + } + this.currentCaipNetworkId = `solana:${solanaChainIdPart}` as CaipNetworkId; + + this.wallet = { + name: 'Phantom Wallet', + id: 'phantom-wallet' + }; + + const userPublicKey = this.getProvider().getUserPublicKey(); + if (!userPublicKey) { + throw new Error('Phantom Connect: Provider failed to return a user public key.'); + } + + const caipAddress = `${this.currentCaipNetworkId}:${userPublicKey}` as CaipAddress; + this.namespaces = { + [PhantomConnector.SUPPORTED_NAMESPACE]: { + accounts: [caipAddress], + methods: Object.values(SOLANA_SIGNING_METHODS), + events: [], + chains: [this.currentCaipNetworkId] + } + }; + + await this.saveSession(); // Save connector-specific session on successful connect + + return this.namespaces; + } catch (error: any) { + this.clearSession(); + throw error; + } + } + + override async disconnect(): Promise { + if (!this.isConnected()) { + return Promise.resolve(); + } + try { + await this.getProvider().disconnect(); + } catch (error: any) { + // console.warn(`PhantomConnector: Error during provider disconnect: ${error.message}. Proceeding with local clear.`); + } + await this.clearSession(); + } + + private async clearSession(): Promise { + this.namespaces = undefined; + this.wallet = undefined; + this.currentCaipNetworkId = null; + await this.clearSessionStorage(); + } + + override getProvider(): PhantomProvider { + if (!this.provider) { + throw new Error('Phantom Connector: Provider not initialized. Call init() first.'); + } + + return this.provider as PhantomProvider; + } + + private getStorage(): Storage { + if (!this.storage) { + throw new Error('Phantom Connector: Storage not initialized. Call init() first.'); + } + + return this.storage; + } + + override getNamespaces(): Namespaces { + if (!this.namespaces) { + throw new Error('Phantom Connector: Not connected. Call connect() first.'); + } + + return this.namespaces; + } + + override getChainId(namespace: ChainNamespace): CaipNetworkId | undefined { + if (namespace === PhantomConnector.SUPPORTED_NAMESPACE) { + return this.currentCaipNetworkId ?? undefined; + } + + return undefined; + } + + override getWalletInfo(): WalletInfo | undefined { + if (!this.isConnected()) { + return undefined; + } + + return this.wallet; + } + + isConnected(): boolean { + // Rely solely on the provider as the source of truth for connection status. + return this.getProvider().isConnected() && !!this.getProvider().getUserPublicKey(); + } + + override async switchNetwork(network: AppKitNetwork): Promise { + const targetClusterName = Object.keys(SOLANA_CLUSTER_TO_CHAIN_ID_PART).find( + key => + SOLANA_CLUSTER_TO_CHAIN_ID_PART[key as keyof typeof SOLANA_CLUSTER_TO_CHAIN_ID_PART] === + network.id + ) as PhantomCluster | undefined; + + if (!targetClusterName) { + throw new Error(`Cannot switch to unsupported network ID: ${network.id}`); + } + + const currentClusterName = Object.keys(SOLANA_CLUSTER_TO_CHAIN_ID_PART).find( + key => + `solana:${ + SOLANA_CLUSTER_TO_CHAIN_ID_PART[key as keyof typeof SOLANA_CLUSTER_TO_CHAIN_ID_PART] + }` === this.currentCaipNetworkId + ) as PhantomCluster | undefined; + + if (targetClusterName === currentClusterName && this.isConnected()) { + return Promise.resolve(); + } + + // For deeplink wallets, switching network effectively means re-connecting to the new cluster. + // We can try to disconnect the current session and then initiate a new connection. + // console.log(`Attempting to switch network to: ${targetClusterName}`); + await this.disconnect(); // Clear current session + + // Create a temporary options object to guide the new connection + const tempConnectOpts: ConnectOptions = { + defaultChain: `solana:${SOLANA_CLUSTER_TO_CHAIN_ID_PART[targetClusterName]}` as CaipNetworkId + }; + + // Attempt to connect to the new cluster + // The connect method will use the defaultChain from opts to determine the cluster. + await this.connect(tempConnectOpts); + + // Verify if the connection was successful and to the correct new network + if ( + !this.isConnected() || + this.getChainId(PhantomConnector.SUPPORTED_NAMESPACE) !== tempConnectOpts.defaultChain + ) { + throw new Error( + `Failed to switch network to ${targetClusterName}. Please try connecting manually.` + ); + } + } + + // Orchestrates session restoration + public async restoreSession(): Promise { + try { + const providerSession = await this.getProvider().restoreSession(); + if (!providerSession) { + return false; + } + + // If provider session is restored, try to restore connector data + const storedConnectorDataJson = await this.getStorage().getItem( + PHANTOM_CONNECTOR_STORAGE_KEY + ); + if (!storedConnectorDataJson) { + return false; // Provider session exists but connector data is missing + } + + const connectorData: PhantomConnectorSessionData = JSON.parse(storedConnectorDataJson); + this.namespaces = connectorData.namespaces; + this.wallet = connectorData.wallet; + this.currentCaipNetworkId = connectorData.currentCaipNetworkId; + + // await this.initializeKeyPair(); + + // Final validation + if (this.isConnected()) { + return true; + } + + // If validation fails, something is out of sync. Clear everything. + await this.disconnect(); + + return false; + } catch (error) { + // On any error, disconnect to ensure a clean state + await this.disconnect(); + + return false; + } + } + + // Saves only connector-specific data + private async saveSession(): Promise { + if (!this.namespaces || !this.wallet || !this.currentCaipNetworkId) { + return; + } + + const connectorData: PhantomConnectorSessionData = { + namespaces: this.namespaces, + wallet: this.wallet, + currentCaipNetworkId: this.currentCaipNetworkId + }; + + try { + await this.getStorage().setItem(PHANTOM_CONNECTOR_STORAGE_KEY, JSON.stringify(connectorData)); + } catch (error) { + // console.error('PhantomConnector: Failed to save session.', error); + } + } + + // Clears only connector-specific data from storage + private async clearSessionStorage(): Promise { + try { + await this.getStorage().removeItem(PHANTOM_CONNECTOR_STORAGE_KEY); + } catch (error) { + // console.error('PhantomConnector: Failed to clear session from storage.', error); + } + } +} diff --git a/packages/solana/src/helpers.ts b/packages/solana/src/helpers.ts index fe9cac9a..b7a02181 100644 --- a/packages/solana/src/helpers.ts +++ b/packages/solana/src/helpers.ts @@ -1,10 +1,4 @@ -export interface TokenInfo { - address: string; - symbol: string; - name: string; - decimals: number; - logoURI?: string; -} +import type { TokenInfo } from './types'; /** * Validates if the given string is a Solana address. @@ -48,6 +42,7 @@ export async function getSolanaNativeBalance(rpcUrl: string, address: string): P } let tokenCache: Record = {}; + /** * Fetch metadata for a Solana SPL token using the Jupiter token list. * @param mint - The token's mint address @@ -71,6 +66,13 @@ export async function getSolanaTokenMetadata(mint: string): Promise; + +function isValidSolanaSigningMethod(method: string): method is SolanaSigningMethod { + return Object.values(SOLANA_SIGNING_METHODS).includes(method as SolanaSigningMethod); +} + +export class PhantomProvider extends EventEmitter implements Provider { + private readonly config: PhantomProviderConfig; + private dappEncryptionKeyPair: nacl.BoxKeyPair; + private currentCluster: PhantomCluster = 'mainnet-beta'; + + private storage: Storage; + + private sessionToken: string | null = null; + private userPublicKey: string | null = null; + private phantomEncryptionPublicKeyBs58: string | null = null; + + constructor(config: PhantomProviderConfig) { + super(); + this.config = config; + this.dappEncryptionKeyPair = config.dappEncryptionKeyPair; + this.storage = config.storage; + } + + getUserPublicKey(): string | null { + return this.userPublicKey; + } + + isConnected(): boolean { + return !!this.sessionToken && !!this.userPublicKey && !!this.dappEncryptionKeyPair; + } + + private buildUrl(rpcMethod: PhantomRpcMethod, params: Record): string { + const query = new URLSearchParams(params).toString(); + + return `${PHANTOM_BASE_URL}/${rpcMethod}?${query}`; + } + + private getRpcMethodName(method: SolanaSigningMethod): PhantomRpcMethod { + switch (method) { + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION: + return 'signTransaction'; + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_AND_SEND_TRANSACTION: + return 'signAndSendTransaction'; + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_ALL_TRANSACTIONS: + return 'signAllTransactions'; + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE: + return 'signMessage'; + default: + // Should not happen due to type constraints on `method` + throw new Error(`Unsupported Solana signing method: ${method}`); + } + } + + private encryptPayload( + payload: Record, + phantomPublicKeyBs58ToEncryptFor: string + ): { nonce: string; encryptedPayload: string } | null { + if (!phantomPublicKeyBs58ToEncryptFor) { + return null; + } + try { + const phantomPublicKeyBytes = bs58.decode(phantomPublicKeyBs58ToEncryptFor); + const nonce = nacl.randomBytes(nacl.box.nonceLength); + const payloadBytes = Buffer.from(JSON.stringify(payload), 'utf8'); + const encryptedPayload = nacl.box( + payloadBytes, + nonce, + phantomPublicKeyBytes, + this.dappEncryptionKeyPair.secretKey + ); + + return { + nonce: bs58.encode(nonce), + encryptedPayload: bs58.encode(encryptedPayload) + }; + } catch (error) { + return null; + } + } + + private decryptPayload( + encryptedDataBs58: string, + nonceBs58: string, + phantomSenderPublicKeyBs58: string + ): T | null { + try { + const encryptedDataBytes = bs58.decode(encryptedDataBs58); + const nonceBytes = bs58.decode(nonceBs58); + const phantomSenderPublicKeyBytes = bs58.decode(phantomSenderPublicKeyBs58); + const decryptedPayloadBytes = nacl.box.open( + encryptedDataBytes, + nonceBytes, + phantomSenderPublicKeyBytes, + this.dappEncryptionKeyPair.secretKey + ); + if (!decryptedPayloadBytes) { + return null; + } + + return JSON.parse(Buffer.from(decryptedPayloadBytes).toString('utf8')) as T; + } catch (error) { + return null; + } + } + + public async restoreSession(): Promise { + try { + const storedSessionJson = await this.storage.getItem(PHANTOM_PROVIDER_STORAGE_KEY); + if (storedSessionJson) { + const session: PhantomSession = JSON.parse(storedSessionJson); + this.setSession(session); + + return true; + } + + return false; + } catch (error) { + // console.error('PhantomProvider: Failed to restore session.', error); + await this.clearSessionStorage(); // Clear potentially corrupt data + + return false; + } + } + + private async saveSession(): Promise { + if (!this.sessionToken || !this.userPublicKey || !this.phantomEncryptionPublicKeyBs58) { + return; // Cannot save incomplete session + } + const session: PhantomSession = { + sessionToken: this.sessionToken, + userPublicKey: this.userPublicKey, + phantomEncryptionPublicKeyBs58: this.phantomEncryptionPublicKeyBs58, + cluster: this.currentCluster + }; + try { + await this.storage.setItem(PHANTOM_PROVIDER_STORAGE_KEY, JSON.stringify(session)); + } catch (error) { + // console.error('PhantomProvider: Failed to save session.', error); + } + } + + private async clearSessionStorage(): Promise { + try { + await this.storage.removeItem(PHANTOM_PROVIDER_STORAGE_KEY); + } catch (error) { + // console.error('PhantomProvider: Failed to clear session storage.', error); + } + } + + public async connect(params?: { + cluster?: PhantomCluster; + }): Promise { + const cluster = params?.cluster ?? 'mainnet-beta'; + this.currentCluster = cluster; + const redirectLink = `${this.config.appScheme}://phantom_connect`; + const connectDeeplinkParams: PhantomConnectParams = { + app_url: this.config.dappUrl, + dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), + redirect_link: redirectLink, + cluster + }; + const url = this.buildUrl('connect', connectDeeplinkParams as any); + + return new Promise((resolve, reject) => { + let subscription: { remove: () => void } | null = null; + const handleDeepLink = async (event: { url: string }) => { + if (subscription) { + subscription.remove(); + } + const fullUrl = event.url; + if (fullUrl.startsWith(redirectLink)) { + const responseUrlParams = new URLSearchParams( + fullUrl.substring(fullUrl.indexOf('?') + 1) + ); + const errorCode = responseUrlParams.get('errorCode'); + const errorMessage = responseUrlParams.get('errorMessage'); + if (errorCode) { + return reject( + new Error( + `Phantom Connection Failed: ${errorMessage || 'Unknown error'} (Code: ${errorCode})` + ) + ); + } + const responsePayload: PhantomDeeplinkResponse = { + phantom_encryption_public_key: responseUrlParams.get('phantom_encryption_public_key')!, + nonce: responseUrlParams.get('nonce')!, + data: responseUrlParams.get('data')! + }; + if ( + !responsePayload.phantom_encryption_public_key || + !responsePayload.nonce || + !responsePayload.data + ) { + return reject(new Error('Phantom Connect: Invalid response - missing parameters.')); + } + const decryptedData = this.decryptPayload( + responsePayload.data, + responsePayload.nonce, + responsePayload.phantom_encryption_public_key + ); + if (!decryptedData || !decryptedData.public_key || !decryptedData.session) { + return reject( + new Error('Phantom Connect: Failed to decrypt or invalid decrypted data.') + ); + } + this.userPublicKey = decryptedData.public_key; + this.sessionToken = decryptedData.session; + this.phantomEncryptionPublicKeyBs58 = responsePayload.phantom_encryption_public_key; + + // Save session on successful connect + this.saveSession(); + + resolve({ + userPublicKey: this.userPublicKey, + sessionToken: this.sessionToken, + phantomEncryptionPublicKeyBs58: this.phantomEncryptionPublicKeyBs58, + cluster + }); + } else { + reject(new Error('Phantom Connect: Unexpected redirect URI.')); + } + }; + subscription = Linking.addEventListener('url', handleDeepLink); + Linking.openURL(url).catch(err => { + if (subscription) { + subscription.remove(); + } + reject(new Error(`Failed to open Phantom wallet: ${err.message}. Is it installed?`)); + }); + }) as Promise; + } + + public async disconnect(): Promise { + if (!this.sessionToken || !this.phantomEncryptionPublicKeyBs58) { + await this.clearSession(); + + return Promise.resolve(); + } + + const payloadToEncrypt = { session: this.sessionToken }; + const encryptedDisconnectPayload = this.encryptPayload( + payloadToEncrypt, + this.phantomEncryptionPublicKeyBs58 + ); + + if (!encryptedDisconnectPayload) { + // console.warn('PhantomProvider: Failed to encrypt disconnect payload. Clearing session locally.'); + await this.clearSession(); + + return Promise.resolve(); // Or reject, depending on desired strictness + } + + const redirectLink = `${this.config.appScheme}://phantom_disconnect`; + const disconnectDeeplinkParams: PhantomDisconnectParams = { + dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), + redirect_link: redirectLink, + payload: encryptedDisconnectPayload.encryptedPayload, + nonce: encryptedDisconnectPayload.nonce + }; + const url = this.buildUrl('disconnect', disconnectDeeplinkParams as any); + + return new Promise((resolve, reject) => { + let subscription: { remove: () => void } | null = null; + const handleDeepLink = (event: { url: string }) => { + if (subscription) { + subscription.remove(); + } + if (event.url.startsWith(redirectLink)) { + this.clearSession(); + resolve(); + } else { + this.clearSession(); + reject(new Error('Phantom Disconnect: Unexpected redirect URI.')); + } + }; + subscription = Linking.addEventListener('url', handleDeepLink); + Linking.openURL(url).catch(err => { + if (subscription) { + subscription.remove(); + } + this.clearSession(); + reject(new Error(`Failed to open Phantom for disconnection: ${err.message}.`)); + }); + }); + } + + public async clearSession(): Promise { + this.sessionToken = null; + this.userPublicKey = null; + this.phantomEncryptionPublicKeyBs58 = null; + await this.clearSessionStorage(); + } + + public setSession(session: PhantomSession): void { + this.sessionToken = session.sessionToken; + this.userPublicKey = session.userPublicKey; + this.phantomEncryptionPublicKeyBs58 = session.phantomEncryptionPublicKeyBs58; + this.currentCluster = session.cluster; + } + + public async request(args: RequestArguments, _chainId?: CaipNetworkId): Promise { + if (!isValidSolanaSigningMethod(args.method)) { + throw new Error( + `PhantomProvider: Unsupported method: ${args.method}. Only Solana signing methods are supported.` + ); + } + const signingMethod = args.method as SolanaSigningMethod; + const requestParams = args.params as any; + + if (!this.isConnected() || !this.sessionToken || !this.phantomEncryptionPublicKeyBs58) { + throw new Error( + 'PhantomProvider: Not connected or session details missing. Cannot process request.' + ); + } + + const rpcMethodName = this.getRpcMethodName(signingMethod); + const redirectSuffix = rpcMethodName.toLowerCase(); + const redirectLink = `${this.config.appScheme}://phantom_${redirectSuffix}`; + let deeplinkUrl = ''; + + switch (signingMethod) { + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_TRANSACTION: + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_AND_SEND_TRANSACTION: { + const typedParams = requestParams as SignTransactionRequestParams; + if (!typedParams || typeof typedParams.transaction !== 'string') { + throw new Error( + `Missing or invalid 'transaction' (base58 string) in params for ${signingMethod}` + ); + } + + const dataToEncrypt = { + session: this.sessionToken!, + transaction: typedParams.transaction + }; + const encryptedData = this.encryptPayload( + dataToEncrypt, + this.phantomEncryptionPublicKeyBs58! + ); + if (!encryptedData) { + throw new Error(`PhantomProvider: Failed to encrypt payload for ${signingMethod}.`); + } + + const signTxDeeplinkParams: PhantomSignTransactionParams = { + dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), + redirect_link: redirectLink, + cluster: this.currentCluster, + payload: encryptedData.encryptedPayload, + nonce: encryptedData.nonce + }; + deeplinkUrl = this.buildUrl(rpcMethodName, signTxDeeplinkParams as any); + break; + } + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_MESSAGE: { + const typedParams = requestParams as SignMessageRequestParams; + if (!typedParams || typeof typedParams.message === 'undefined') { + throw new Error(`Missing 'message' in params for ${signingMethod}`); + } + + let messageBs58: string; + if (typedParams.message instanceof Uint8Array) { + messageBs58 = bs58.encode(typedParams.message); + } else if (typeof typedParams.message === 'string') { + try { + bs58.decode(typedParams.message); + messageBs58 = typedParams.message; + } catch (e) { + messageBs58 = bs58.encode(Buffer.from(typedParams.message)); + } + } else { + throw new Error('Invalid message format for signMessage. Expected Uint8Array or string.'); + } + + const dataToEncrypt = { + message: messageBs58, + session: this.sessionToken!, + display: typedParams.display || 'utf8' + }; + + const encryptedPayloadData = this.encryptPayload( + dataToEncrypt, + this.phantomEncryptionPublicKeyBs58! + ); + + if (!encryptedPayloadData) { + throw new Error('PhantomProvider: Failed to encrypt payload for signMessage.'); + } + + const signMsgDeeplinkQueryPayload: PhantomSignMessageParams = { + dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), + redirect_link: redirectLink, + payload: encryptedPayloadData.encryptedPayload, + nonce: encryptedPayloadData.nonce + }; + deeplinkUrl = this.buildUrl(rpcMethodName, signMsgDeeplinkQueryPayload as any); + break; + } + case SOLANA_SIGNING_METHODS.SOLANA_SIGN_ALL_TRANSACTIONS: { + const typedParams = requestParams as SignAllTransactionsRequestParams; + if ( + !typedParams || + !Array.isArray(typedParams.transactions) || + !typedParams.transactions.every((t: any) => typeof t === 'string') + ) { + throw new Error( + `Missing or invalid 'transactions' (array of base58 strings) in params for ${signingMethod}` + ); + } + + const dataToEncrypt = { + session: this.sessionToken!, + transactions: typedParams.transactions + }; + const encryptedData = this.encryptPayload( + dataToEncrypt, + this.phantomEncryptionPublicKeyBs58! + ); + if (!encryptedData) { + throw new Error(`PhantomProvider: Failed to encrypt payload for ${signingMethod}.`); + } + + const signAllTxDeeplinkParams: PhantomSignAllTransactionsParams = { + dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), + redirect_link: redirectLink, + cluster: this.currentCluster, + payload: encryptedData.encryptedPayload, + nonce: encryptedData.nonce + }; + deeplinkUrl = this.buildUrl(rpcMethodName, signAllTxDeeplinkParams as any); + break; + } + default: { + throw new Error(`PhantomProvider: Unhandled signing method: ${signingMethod}`); + } + } + + return new Promise((resolve, reject) => { + let subscription: { remove: () => void } | null = null; + const handleDeepLink = async (event: { url: string }) => { + if (subscription) { + subscription.remove(); + } + const fullUrl = event.url; + if (fullUrl.startsWith(redirectLink)) { + const responseUrlParams = new URLSearchParams( + fullUrl.substring(fullUrl.indexOf('?') + 1) + ); + const errorCode = responseUrlParams.get('errorCode'); + const errorMessage = responseUrlParams.get('errorMessage'); + if (errorCode) { + return reject( + new Error( + `Phantom ${signingMethod} Failed: ${ + errorMessage || 'Unknown error' + } (Code: ${errorCode})` + ) + ); + } + const responseNonce = responseUrlParams.get('nonce'); + const responseData = responseUrlParams.get('data'); + if (!responseNonce || !responseData) { + return reject( + new Error(`Phantom ${signingMethod}: Invalid response - missing nonce or data.`) + ); + } + const decryptedResult = this.decryptPayload( + responseData, + responseNonce, + this.phantomEncryptionPublicKeyBs58! + ); + if (!decryptedResult) { + return reject( + new Error( + `Phantom ${signingMethod}: Failed to decrypt response or invalid decrypted data.` + ) + ); + } + resolve(decryptedResult as T); + } else { + reject(new Error(`Phantom ${signingMethod}: Unexpected redirect URI.`)); + } + }; + subscription = Linking.addEventListener('url', handleDeepLink); + Linking.openURL(deeplinkUrl).catch(err => { + if (subscription) { + subscription.remove(); + } + reject( + new Error(`Failed to open Phantom for ${signingMethod}: ${err.message}. Is it installed?`) + ); + }); + }); + } +} + +type Values = T[keyof T]; diff --git a/packages/solana/src/types.ts b/packages/solana/src/types.ts new file mode 100644 index 00000000..2be3d686 --- /dev/null +++ b/packages/solana/src/types.ts @@ -0,0 +1,131 @@ +import type { + CaipNetworkId, + Namespaces, + Storage, + WalletInfo +} from '@reown/appkit-common-react-native'; +import type nacl from 'tweetnacl'; + +// --- From helpers --- + +export interface TokenInfo { + address: string; + symbol: string; + name: string; + decimals: number; + logoURI?: string; +} + +// --- From PhantomProvider --- + +export type PhantomCluster = 'mainnet-beta' | 'testnet' | 'devnet'; + +export interface PhantomProviderConfig { + appScheme: string; + dappUrl: string; + storage: Storage; + dappEncryptionKeyPair: nacl.BoxKeyPair; +} + +export type PhantomConnectResult = PhantomSession; + +export interface PhantomSession { + sessionToken: string; + userPublicKey: string; + phantomEncryptionPublicKeyBs58: string; + cluster: PhantomCluster; +} + +export interface SignTransactionRequestParams { + transaction: string; +} +export interface SignMessageRequestParams { + message: Uint8Array | string; + display?: 'utf8' | 'hex'; +} +export interface SignAllTransactionsRequestParams { + transactions: string[]; +} + +export interface PhantomDeeplinkResponse { + phantom_encryption_public_key?: string; + nonce: string; + data: string; +} + +export interface DecryptedConnectData { + public_key: string; + session: string; +} + +export interface PhantomProviderConfig { + appScheme: string; + dappUrl: string; + storage: Storage; + dappEncryptionKeyPair: nacl.BoxKeyPair; +} + +export interface PhantomSession { + sessionToken: string; + userPublicKey: string; + phantomEncryptionPublicKeyBs58: string; + cluster: PhantomCluster; +} + +// Actual method names used in Phantom deeplink URLs +export type PhantomRpcMethod = + | 'connect' + | 'disconnect' + | 'signTransaction' + | 'signAndSendTransaction' + | 'signAllTransactions' + | 'signMessage'; + +export interface PhantomSignTransactionParams { + dapp_encryption_public_key: string; + redirect_link: string; + payload: string; // Encrypted JSON: { session: string, transaction: string } + nonce: string; + cluster?: PhantomCluster; +} + +export interface PhantomSignAllTransactionsParams { + dapp_encryption_public_key: string; + redirect_link: string; + payload: string; // Encrypted JSON: { session: string, transactions: string[] } + nonce: string; + cluster?: PhantomCluster; +} + +export interface PhantomSignMessageParams { + dapp_encryption_public_key: string; + redirect_link: string; + payload: string; // Encrypted JSON string: { message: string, session: string, display: 'utf8'|'hex' } + nonce: string; +} + +export interface PhantomConnectParams { + app_url: string; + dapp_encryption_public_key: string; + redirect_link: string; + cluster?: PhantomCluster; +} + +export interface PhantomDisconnectParams { + dapp_encryption_public_key: string; + redirect_link: string; + payload: string; // Encrypted { session: string } + nonce: string; +} + +// --- From PhantomConnector --- + +export interface PhantomConnectorConfig { + cluster?: PhantomCluster; +} + +export interface PhantomConnectorSessionData { + namespaces: Namespaces; + wallet: WalletInfo; + currentCaipNetworkId: CaipNetworkId; +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 7cc4a080..943ef506 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -38,6 +38,7 @@ "access": "public" }, "dependencies": { + "@reown/appkit-common-react-native": "1.2.3", "polished": "4.3.1", "qrcode": "1.5.3" }, diff --git a/yarn.lock b/yarn.lock index 29249c28..24dcba37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,6 +113,7 @@ __metadata: "@reown/appkit-react-native": "npm:1.2.3" "@reown/appkit-solana-react-native": "npm:1.2.3" "@reown/appkit-wagmi-react-native": "npm:1.2.3" + "@solana/web3.js": "npm:^1.98.2" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" "@tanstack/react-query-persist-client": "npm:5.56.2" @@ -6138,6 +6139,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.4.2": + version: 1.9.1 + resolution: "@noble/curves@npm:1.9.1" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: 39c84dbfecdca80cfde2ecea4b06ef2ec1255a4df40158d22491d1400057a283f57b2b26c8b1331006e6e061db791f31d47764961c239437032e2f45e8888c1e + languageName: node + linkType: hard + "@noble/curves@npm:^1.6.0, @noble/curves@npm:~1.6.0": version: 1.6.0 resolution: "@noble/curves@npm:1.6.0" @@ -7321,6 +7331,8 @@ __metadata: resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" + bs58: "npm:6.0.0" + tweetnacl: "npm:1.0.3" languageName: unknown linkType: soft @@ -7328,6 +7340,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-ui-react-native@workspace:packages/ui" dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" polished: "npm:4.3.1" qrcode: "npm:1.5.3" peerDependencies: @@ -7651,6 +7664,75 @@ __metadata: languageName: node linkType: hard +"@solana/buffer-layout@npm:^4.0.1": + version: 4.0.1 + resolution: "@solana/buffer-layout@npm:4.0.1" + dependencies: + buffer: "npm:~6.0.3" + checksum: 6535f3908cf6dfc405b665795f0c2eaa0482a8c6b1811403945cf7b450e7eb7b40acce3e8af046f2fcc3eea1a15e61d48c418315d813bee4b720d56b00053305 + languageName: node + linkType: hard + +"@solana/codecs-core@npm:2.1.1": + version: 2.1.1 + resolution: "@solana/codecs-core@npm:2.1.1" + dependencies: + "@solana/errors": "npm:2.1.1" + peerDependencies: + typescript: ">=5.3.3" + checksum: 5748a2cbd433a77c739787c8f7659350e933d7d1d161e1826353eb36563deb84d147c19d0b0bfcc8ce1b34c74279f18276efa6a5315f6888905e1174d48567fb + languageName: node + linkType: hard + +"@solana/codecs-numbers@npm:^2.1.0": + version: 2.1.1 + resolution: "@solana/codecs-numbers@npm:2.1.1" + dependencies: + "@solana/codecs-core": "npm:2.1.1" + "@solana/errors": "npm:2.1.1" + peerDependencies: + typescript: ">=5.3.3" + checksum: 0db7965df9f07c49d91919b0c675b97acbb1686f4d7b47581b16fc20e6e06526b715d06914c07dab556eb51c3a95fa551f1687b65ec405f4f75bed9391e89bfd + languageName: node + linkType: hard + +"@solana/errors@npm:2.1.1": + version: 2.1.1 + resolution: "@solana/errors@npm:2.1.1" + dependencies: + chalk: "npm:^5.4.1" + commander: "npm:^13.1.0" + peerDependencies: + typescript: ">=5.3.3" + bin: + errors: bin/cli.mjs + checksum: 2246339f5a8e4bb654e6ce584dd9bac4e453abda185556b9841c8a8209dd4ab6f4c39b23bb85e52d4e6a1d7676d22e680528a2b5c3ede35d3822aed26390576c + languageName: node + linkType: hard + +"@solana/web3.js@npm:^1.98.2": + version: 1.98.2 + resolution: "@solana/web3.js@npm:1.98.2" + dependencies: + "@babel/runtime": "npm:^7.25.0" + "@noble/curves": "npm:^1.4.2" + "@noble/hashes": "npm:^1.4.0" + "@solana/buffer-layout": "npm:^4.0.1" + "@solana/codecs-numbers": "npm:^2.1.0" + agentkeepalive: "npm:^4.5.0" + bn.js: "npm:^5.2.1" + borsh: "npm:^0.7.0" + bs58: "npm:^4.0.1" + buffer: "npm:6.0.3" + fast-stable-stringify: "npm:^1.0.0" + jayson: "npm:^4.1.1" + node-fetch: "npm:^2.7.0" + rpc-websockets: "npm:^9.0.2" + superstruct: "npm:^2.0.2" + checksum: 04230d8f9d3f1aa7665d8acf9f54342c022bd84070790909f5b6ff17d27b03e95373d3491f4a25f4ee2e10a9e82765ee541db33fd9f63be2efa49a4490bc1a0e + languageName: node + linkType: hard + "@storybook/addon-actions@npm:8.3.0": version: 8.3.0 resolution: "@storybook/addon-actions@npm:8.3.0" @@ -8189,6 +8271,15 @@ __metadata: languageName: node linkType: hard +"@swc/helpers@npm:^0.5.11": + version: 0.5.17 + resolution: "@swc/helpers@npm:0.5.17" + dependencies: + tslib: "npm:^2.8.0" + checksum: fe1f33ebb968558c5a0c595e54f2e479e4609bff844f9ca9a2d1ffd8dd8504c26f862a11b031f48f75c95b0381c2966c3dd156e25942f90089badd24341e7dbb + languageName: node + linkType: hard + "@tanstack/query-async-storage-persister@npm:^5.40.0": version: 5.40.0 resolution: "@tanstack/query-async-storage-persister@npm:5.40.0" @@ -8447,6 +8538,15 @@ __metadata: languageName: node linkType: hard +"@types/connect@npm:^3.4.33": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.9 resolution: "@types/debug@npm:4.1.9" @@ -8656,7 +8756,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^12.7.1": +"@types/node@npm:^12.12.54, @types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" checksum: 3b190bb0410047d489c49bbaab592d2e6630de6a50f00ba3d7d513d59401d279972a8f5a598b5bb8ddc1702f8a2f4ec57a65d93852f9c329639738e7053637d1 @@ -8815,6 +8915,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^8.3.4": + version: 8.3.4 + resolution: "@types/uuid@npm:8.3.4" + checksum: b9ac98f82fcf35962317ef7dc44d9ac9e0f6fdb68121d384c88fe12ea318487d5585d3480fa003cf28be86a3bbe213ca688ba786601dce4a97724765eb5b1cf2 + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.1": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -8822,6 +8929,24 @@ __metadata: languageName: node linkType: hard +"@types/ws@npm:^7.4.4": + version: 7.4.7 + resolution: "@types/ws@npm:7.4.7" + dependencies: + "@types/node": "npm:*" + checksum: f1f53febd8623a85cef2652949acd19d83967e350ea15a851593e3033501750a1e04f418552e487db90a3d48611a1cff3ffcf139b94190c10f2fd1e1dc95ff10 + languageName: node + linkType: hard + +"@types/ws@npm:^8.2.2": + version: 8.18.1 + resolution: "@types/ws@npm:8.18.1" + dependencies: + "@types/node": "npm:*" + checksum: 61aff1129143fcc4312f083bc9e9e168aa3026b7dd6e70796276dcfb2c8211c4292603f9c4864fae702f2ed86e4abd4d38aa421831c2fd7f856c931a481afbab + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -9834,6 +9959,15 @@ __metadata: languageName: node linkType: hard +"agentkeepalive@npm:^4.5.0": + version: 4.6.0 + resolution: "agentkeepalive@npm:4.6.0" + dependencies: + humanize-ms: "npm:^1.2.1" + checksum: 235c182432f75046835b05f239708107138a40103deee23b6a08caee5136873709155753b394ec212e49e60e94a378189562cb01347765515cff61b692c69187 + languageName: node + linkType: hard + "aggregate-error@npm:^3.0.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -10637,6 +10771,15 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^3.0.2": + version: 3.0.11 + resolution: "base-x@npm:3.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 4c5b8cd9cef285973b0460934be4fc890eedfd22a8aca527fac3527f041c5d1c912f7b9a6816f19e43e69dc7c29a5deabfa326bd3d6a57ee46af0ad46e3991d5 + languageName: node + linkType: hard + "base-x@npm:^5.0.0": version: 5.0.1 resolution: "base-x@npm:5.0.1" @@ -10761,6 +10904,13 @@ __metadata: languageName: node linkType: hard +"bn.js@npm:^5.2.0": + version: 5.2.2 + resolution: "bn.js@npm:5.2.2" + checksum: cb97827d476aab1a0194df33cd84624952480d92da46e6b4a19c32964aa01553a4a613502396712704da2ec8f831cf98d02e74ca03398404bd78a037ba93f2ab + languageName: node + linkType: hard + "body-parser@npm:1.20.3": version: 1.20.3 resolution: "body-parser@npm:1.20.3" @@ -10788,6 +10938,17 @@ __metadata: languageName: node linkType: hard +"borsh@npm:^0.7.0": + version: 0.7.0 + resolution: "borsh@npm:0.7.0" + dependencies: + bn.js: "npm:^5.2.0" + bs58: "npm:^4.0.0" + text-encoding-utf-8: "npm:^1.0.2" + checksum: 513b3e51823d2bf5be77cec27742419d2b0427504825dd7ceb00dedb820f246a4762f04b83d5e3aa39c8e075b3cbaeb7ca3c90bd1cbeecccb4a510575be8c581 + languageName: node + linkType: hard + "bowser@npm:^2.9.0": version: 2.11.0 resolution: "bowser@npm:2.11.0" @@ -10984,6 +11145,15 @@ __metadata: languageName: node linkType: hard +"bs58@npm:^4.0.0, bs58@npm:^4.0.1": + version: 4.0.1 + resolution: "bs58@npm:4.0.1" + dependencies: + base-x: "npm:^3.0.2" + checksum: 613a1b1441e754279a0e3f44d1faeb8c8e838feef81e550efe174ff021dd2e08a4c9ae5805b52dfdde79f97b5c0918c78dd24a0eb726c4a94365f0984a0ffc65 + languageName: node + linkType: hard + "bs58check@npm:^4.0.0": version: 4.0.0 resolution: "bs58check@npm:4.0.0" @@ -11034,7 +11204,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:6.0.3, buffer@npm:^6.0.3": +"buffer@npm:6.0.3, buffer@npm:^6.0.3, buffer@npm:~6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" dependencies: @@ -11054,6 +11224,16 @@ __metadata: languageName: node linkType: hard +"bufferutil@npm:^4.0.1": + version: 4.0.9 + resolution: "bufferutil@npm:4.0.9" + dependencies: + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: f8a93279fc9bdcf32b42eba97edc672b39ca0fe5c55a8596099886cffc76ea9dd78e0f6f51ecee3b5ee06d2d564aa587036b5d4ea39b8b5ac797262a363cdf7d + languageName: node + linkType: hard + "bufferutil@npm:^4.0.8": version: 4.0.8 resolution: "bufferutil@npm:4.0.8" @@ -11302,6 +11482,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:^5.4.1": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -11664,7 +11851,14 @@ __metadata: languageName: node linkType: hard -"commander@npm:^2.20.0": +"commander@npm:^13.1.0": + version: 13.1.0 + resolution: "commander@npm:13.1.0" + checksum: 7b8c5544bba704fbe84b7cab2e043df8586d5c114a4c5b607f83ae5060708940ed0b5bd5838cf8ce27539cde265c1cbd59ce3c8c6b017ed3eec8943e3a415164 + languageName: node + linkType: hard + +"commander@npm:^2.20.0, commander@npm:^2.20.3": version: 2.20.3 resolution: "commander@npm:2.20.3" checksum: 74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 @@ -12317,6 +12511,13 @@ __metadata: languageName: node linkType: hard +"delay@npm:^5.0.0": + version: 5.0.0 + resolution: "delay@npm:5.0.0" + checksum: 01cdc4cd0cd35fb622518a3df848e67e09763a38e7cdada2232b6fda9ddda72eddcf74f0e24211200fbe718434f2335f2a2633875a6c96037fefa6de42896ad7 + languageName: node + linkType: hard + "delayed-stream@npm:~1.0.0": version: 1.0.0 resolution: "delayed-stream@npm:1.0.0" @@ -13008,6 +13209,22 @@ __metadata: languageName: node linkType: hard +"es6-promise@npm:^4.0.3": + version: 4.2.8 + resolution: "es6-promise@npm:4.2.8" + checksum: 2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3 + languageName: node + linkType: hard + +"es6-promisify@npm:^5.0.0": + version: 5.0.0 + resolution: "es6-promisify@npm:5.0.0" + dependencies: + es6-promise: "npm:^4.0.3" + checksum: 23284c6a733cbf7842ec98f41eac742c9f288a78753c4fe46652bae826446ced7615b9e8a5c5f121a08812b1cd478ea58630f3e1c3d70835bd5dcd69c7cd75c9 + languageName: node + linkType: hard + "esbuild-register@npm:^3.5.0": version: 3.6.0 resolution: "esbuild-register@npm:3.6.0" @@ -14036,6 +14253,13 @@ __metadata: languageName: node linkType: hard +"eyes@npm:^0.1.8": + version: 0.1.8 + resolution: "eyes@npm:0.1.8" + checksum: 4c79a9cbf45746d8c9f48cc957e35ad8ea336add1c7b8d5a0e002efc791a7a62b27b2188184ef1a1eea7bc3cd06b161791421e0e6c5fe78309705a162c53eea8 + languageName: node + linkType: hard + "fast-base64-decode@npm:^1.0.0": version: 1.0.0 resolution: "fast-base64-decode@npm:1.0.0" @@ -14125,6 +14349,13 @@ __metadata: languageName: node linkType: hard +"fast-stable-stringify@npm:^1.0.0": + version: 1.0.0 + resolution: "fast-stable-stringify@npm:1.0.0" + checksum: 1d773440c7a9615950577665074746c2e92edafceefa789616ecb6166229e0ccc6dae206ca9b9f7da0d274ba5779162aab2d07940a0f6e52a41a4e555392eb3b + languageName: node + linkType: hard + "fast-text-encoding@npm:1.0.6": version: 1.0.6 resolution: "fast-text-encoding@npm:1.0.6" @@ -15333,6 +15564,15 @@ __metadata: languageName: node linkType: hard +"humanize-ms@npm:^1.2.1": + version: 1.2.1 + resolution: "humanize-ms@npm:1.2.1" + dependencies: + ms: "npm:^2.0.0" + checksum: f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a + languageName: node + linkType: hard + "hyphenate-style-name@npm:^1.0.3": version: 1.0.4 resolution: "hyphenate-style-name@npm:1.0.4" @@ -16028,6 +16268,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:^4.0.1": + version: 4.0.1 + resolution: "isomorphic-ws@npm:4.0.1" + peerDependencies: + ws: "*" + checksum: 7cb90dc2f0eb409825558982fb15d7c1d757a88595efbab879592f9d2b63820d6bbfb5571ab8abe36c715946e165a413a99f6aafd9f40ab1f514d73487bc9996 + languageName: node + linkType: hard + "isows@npm:1.0.4": version: 1.0.4 resolution: "isows@npm:1.0.4" @@ -16137,6 +16386,28 @@ __metadata: languageName: node linkType: hard +"jayson@npm:^4.1.1": + version: 4.2.0 + resolution: "jayson@npm:4.2.0" + dependencies: + "@types/connect": "npm:^3.4.33" + "@types/node": "npm:^12.12.54" + "@types/ws": "npm:^7.4.4" + commander: "npm:^2.20.3" + delay: "npm:^5.0.0" + es6-promisify: "npm:^5.0.0" + eyes: "npm:^0.1.8" + isomorphic-ws: "npm:^4.0.1" + json-stringify-safe: "npm:^5.0.1" + stream-json: "npm:^1.9.1" + uuid: "npm:^8.3.2" + ws: "npm:^7.5.10" + bin: + jayson: bin/jayson.js + checksum: 062f525a0d15232c4361d10e0cd26960e998897e483408de03101e147c7bdf275db525bc1d5cc8aff4b777d1b1389004c8e9a5715304aedcf9930557787df6e3 + languageName: node + linkType: hard + "jest-changed-files@npm:^29.7.0": version: 29.7.0 resolution: "jest-changed-files@npm:29.7.0" @@ -16855,6 +17126,13 @@ __metadata: languageName: node linkType: hard +"json-stringify-safe@npm:^5.0.1": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 + languageName: node + linkType: hard + "json5@npm:^2.1.1, json5@npm:^2.2.1, json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" @@ -18371,7 +18649,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 @@ -18539,7 +18817,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.5.0": +"node-fetch@npm:^2.5.0, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -21060,6 +21338,28 @@ __metadata: languageName: node linkType: hard +"rpc-websockets@npm:^9.0.2": + version: 9.1.1 + resolution: "rpc-websockets@npm:9.1.1" + dependencies: + "@swc/helpers": "npm:^0.5.11" + "@types/uuid": "npm:^8.3.4" + "@types/ws": "npm:^8.2.2" + buffer: "npm:^6.0.3" + bufferutil: "npm:^4.0.1" + eventemitter3: "npm:^5.0.1" + utf-8-validate: "npm:^5.0.2" + uuid: "npm:^8.3.2" + ws: "npm:^8.5.0" + dependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: c1c9d78e90bf1c9c9f0607d924f4ff00e42d5b27b76053a384ae37479656912f06b726c1bbc463dbb8b420fcc1bbcaca487db35227a04ecd55e39ecff5cd5653 + languageName: node + linkType: hard + "run-applescript@npm:^5.0.0": version: 5.0.0 resolution: "run-applescript@npm:5.0.0" @@ -21722,6 +22022,22 @@ __metadata: languageName: node linkType: hard +"stream-chain@npm:^2.2.5": + version: 2.2.5 + resolution: "stream-chain@npm:2.2.5" + checksum: c512f50190d7c92d688fa64e7af540c51b661f9c2b775fc72bca38ea9bca515c64c22c2197b1be463741daacbaaa2dde8a8ea24ebda46f08391224f15249121a + languageName: node + linkType: hard + +"stream-json@npm:^1.9.1": + version: 1.9.1 + resolution: "stream-json@npm:1.9.1" + dependencies: + stream-chain: "npm:^2.2.5" + checksum: 0521e5cb3fb6b0e2561d715975e891bd81fa77d0239c8d0b1756846392bc3c7cdd7f1ddb0fe7ed77e6fdef58daab9e665d3b39f7d677bd0859e65a2bff59b92c + languageName: node + linkType: hard + "stream-shift@npm:^1.0.0": version: 1.0.1 resolution: "stream-shift@npm:1.0.1" @@ -22021,6 +22337,13 @@ __metadata: languageName: node linkType: hard +"superstruct@npm:^2.0.2": + version: 2.0.2 + resolution: "superstruct@npm:2.0.2" + checksum: c6853db5240b4920f47b3c864dd1e23ede6819ea399ad29a65387d746374f6958c5f1c5b7e5bb152d9db117a74973e5005056d9bb83c24e26f18ec6bfae4a718 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -22241,6 +22564,13 @@ __metadata: languageName: node linkType: hard +"text-encoding-utf-8@npm:^1.0.2": + version: 1.0.2 + resolution: "text-encoding-utf-8@npm:1.0.2" + checksum: 87a64b394c850e8387c2ca7fc6929a26ce97fb598f1c55cd0fdaec4b8e2c3ed6770f65b2f3309c9175ef64ac5e403c8e48b53ceeb86d2897940c5e19cc00bb99 + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -22527,6 +22857,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.8.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -22616,6 +22953,13 @@ __metadata: languageName: node linkType: hard +"tweetnacl@npm:1.0.3": + version: 1.0.3 + resolution: "tweetnacl@npm:1.0.3" + checksum: 069d9df51e8ad4a89fbe6f9806c68e06c65be3c7d42f0701cc43dba5f0d6064686b238bbff206c5addef8854e3ce00c643bff59432ea2f2c639feab0ee1a93f9 + languageName: node + linkType: hard + "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" From 5d9a00fd436d45cd8183828b9ac876363bcda20b Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:20:12 -0300 Subject: [PATCH 66/91] chore: move storage logic to a util file --- apps/native/App.tsx | 14 +--------- apps/native/src/utils/StorageUtil.ts | 33 ++++++++++++++++++++++ apps/native/src/utils/misc.ts | 41 ++++------------------------ packages/appkit/src/index.ts | 2 +- 4 files changed, 40 insertions(+), 50 deletions(-) create mode 100644 apps/native/src/utils/StorageUtil.ts diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 004b1530..0a074c6b 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -32,7 +32,7 @@ import { ActionsView } from './src/views/ActionsView'; import { WalletInfoView } from './src/views/WalletInfoView'; import { EventsView } from './src/views/EventsView'; import { getCustomWallets } from './src/utils/misc'; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { storage } from './src/utils/StorageUtil'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -47,18 +47,6 @@ const metadata = { } }; -const storage = { - setItem: async (key: string, value: string) => { - await AsyncStorage.setItem(key, value); - }, - getItem: async (key: string) => { - return await AsyncStorage.getItem(key); - }, - removeItem: async (key: string) => { - await AsyncStorage.removeItem(key); - } -}; - const clipboardClient = { setString: async (value: string) => { await Clipboard.setStringAsync(value); diff --git a/apps/native/src/utils/StorageUtil.ts b/apps/native/src/utils/StorageUtil.ts new file mode 100644 index 00000000..b123bd9e --- /dev/null +++ b/apps/native/src/utils/StorageUtil.ts @@ -0,0 +1,33 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { safeJsonParse, safeJsonStringify } from '@walletconnect/safe-json'; +import { Storage } from '@reown/appkit-react-native'; + +export const storage: Storage = { + getKeys: async () => { + return AsyncStorage.getAllKeys() as Promise; + }, + getEntries: async (): Promise<[string, T][]> => { + function parseEntry(entry: [string, string | null]): [string, any] { + return [entry[0], safeJsonParse(entry[1] ?? '')]; + } + + const keys = await AsyncStorage.getAllKeys(); + const entries = await AsyncStorage.multiGet(keys); + + return entries.map(parseEntry); + }, + setItem: async (key: string, value: any) => { + return await AsyncStorage.setItem(key, safeJsonStringify(value)); + }, + getItem: async (key: string): Promise => { + const item = await AsyncStorage.getItem(key); + if (typeof item === 'undefined' || item === null) { + return undefined; + } + + return safeJsonParse(item) as T; + }, + removeItem: async (key: string) => { + return await AsyncStorage.removeItem(key); + } +}; diff --git a/apps/native/src/utils/misc.ts b/apps/native/src/utils/misc.ts index 8df316f1..e4284e56 100644 --- a/apps/native/src/utils/misc.ts +++ b/apps/native/src/utils/misc.ts @@ -1,44 +1,13 @@ -import { Platform } from 'react-native'; - export const getCustomWallets = () => { const wallets = [ { - id: 'rn-wallet', - name: 'Wallet(RN)', - image_url: - 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', - mobile_link: 'rn-web3wallet://', - link_mode: 'https://appkit-lab.reown.com/rn_walletkit' - }, - { - id: 'flutter-wallet', - name: 'Wallet(Flutter)', - image_url: - 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', - mobile_link: 'wcflutterwallet://', - link_mode: 'https://appkit-lab.reown.com/flutter_walletkit' + id: 'phantom-wallet', + name: 'Phantom', + image_url: 'https://avatars.githubusercontent.com/u/124594793?s=200&v=4', + mobile_link: 'phantom://', + link_mode: '' } ]; - if (Platform.OS === 'android') { - wallets.push({ - id: 'android-wallet', - name: 'Wallet(Android)', - image_url: - 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', - mobile_link: 'kotlin-web3wallet://', - link_mode: 'https://appkit-lab.reown.com/wallet' - }); - } else if (Platform.OS === 'ios') { - wallets.push({ - id: 'ios-wallet', - name: 'Wallet(iOS)', - image_url: - 'https://github.com/reown-com/reown-docs/blob/main/static/assets/home/walletkitLogo.png?raw=true', - mobile_link: 'walletapp://', - link_mode: 'https://appkit-lab.reown.com/wallet' - }); - } - return wallets; }; diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index c6b74231..58cae8b3 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -23,7 +23,7 @@ export { CoreHelperUtil } from '@reown/appkit-core-react-native'; export * from './AppKit'; export { AppKitProvider } from './AppKitContext'; -export type { AppKitNetwork } from '@reown/appkit-common-react-native'; +export type { AppKitNetwork, Storage } from '@reown/appkit-common-react-native'; export { WalletConnectConnector } from './connectors/WalletConnectConnector'; From 7e7e55f9446ce8944bf8439b0a09e3f743f7ccbf Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:22:31 -0300 Subject: [PATCH 67/91] chore: use phantom connector for phantomwallet --- .../appkit/src/views/w3m-connecting-view/index.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 8bd17423..88fbdf6a 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -50,10 +50,16 @@ export function ConnectingView() { ConnectionController.setWcError(false); // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); - //TODO: check linkmode - const wcPromise = connect('walletconnect'); - ConnectionController.setWcPromise(wcPromise); - await wcPromise; + let connectPromise: Promise; + // TODO: check phantom wallet id from cloud + if (data?.wallet?.id === 'phantom-wallet') { + connectPromise = connect('phantom'); + } else { + //TODO: check linkmode + connectPromise = connect('walletconnect'); + } + ConnectionController.setWcPromise(connectPromise); + await connectPromise; // await ConnectionController.state.wcPromise; // ConnectorController.setConnectedConnector('WALLET_CONNECT'); AccountController.setIsConnected(true); From ed0c1513277e043894da79b9fda8f5cd9bac4dc6 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:52:23 -0300 Subject: [PATCH 68/91] chore: setting storage correctly --- packages/appkit/src/AppKit.ts | 8 ++++---- packages/solana/src/connectors/PhantomConnector.ts | 11 +++++------ packages/solana/src/providers/PhantomProvider.ts | 5 ++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 95860889..1cb13caf 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -260,8 +260,8 @@ export class AppKit { if (CustomConnector) { await CustomConnector.init({ - storage: OptionsController.state.storage!, - metadata: this.metadata + storage: this.config.storage, + metadata: this.config.metadata }); return CustomConnector; @@ -272,8 +272,8 @@ export class AppKit { projectId: this.projectId }); await walletConnectConnector.init({ - storage: OptionsController.state.storage!, - metadata: this.metadata + storage: this.config.storage, + metadata: this.config.metadata }); return walletConnectConnector; diff --git a/packages/solana/src/connectors/PhantomConnector.ts b/packages/solana/src/connectors/PhantomConnector.ts index 896a8979..73a388dc 100644 --- a/packages/solana/src/connectors/PhantomConnector.ts +++ b/packages/solana/src/connectors/PhantomConnector.ts @@ -41,9 +41,9 @@ export class PhantomConnector extends WalletConnector { private static readonly SUPPORTED_NAMESPACE: ChainNamespace = 'solana'; - constructor(config: PhantomConnectorConfig) { + constructor(config?: PhantomConnectorConfig) { super({ type: 'phantom' }); - this.config = config; + this.config = config ?? { cluster: 'mainnet-beta' }; } override async init(ops: ConnectorInitOptions) { @@ -268,14 +268,13 @@ export class PhantomConnector extends WalletConnector { } // If provider session is restored, try to restore connector data - const storedConnectorDataJson = await this.getStorage().getItem( + const connectorData = await this.getStorage().getItem( PHANTOM_CONNECTOR_STORAGE_KEY ); - if (!storedConnectorDataJson) { + if (!connectorData) { return false; // Provider session exists but connector data is missing } - const connectorData: PhantomConnectorSessionData = JSON.parse(storedConnectorDataJson); this.namespaces = connectorData.namespaces; this.wallet = connectorData.wallet; this.currentCaipNetworkId = connectorData.currentCaipNetworkId; @@ -312,7 +311,7 @@ export class PhantomConnector extends WalletConnector { }; try { - await this.getStorage().setItem(PHANTOM_CONNECTOR_STORAGE_KEY, JSON.stringify(connectorData)); + await this.getStorage().setItem(PHANTOM_CONNECTOR_STORAGE_KEY, connectorData); } catch (error) { // console.error('PhantomConnector: Failed to save session.', error); } diff --git a/packages/solana/src/providers/PhantomProvider.ts b/packages/solana/src/providers/PhantomProvider.ts index 340efc86..eb79fae0 100644 --- a/packages/solana/src/providers/PhantomProvider.ts +++ b/packages/solana/src/providers/PhantomProvider.ts @@ -145,9 +145,8 @@ export class PhantomProvider extends EventEmitter implements Provider { public async restoreSession(): Promise { try { - const storedSessionJson = await this.storage.getItem(PHANTOM_PROVIDER_STORAGE_KEY); - if (storedSessionJson) { - const session: PhantomSession = JSON.parse(storedSessionJson); + const session = await this.storage.getItem(PHANTOM_PROVIDER_STORAGE_KEY); + if (session) { this.setSession(session); return true; From 514043424f165adfae6f455ced6e1935e86f50c1 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 6 Jun 2025 17:14:38 -0300 Subject: [PATCH 69/91] chore: restore session in phantom connector --- packages/appkit/src/AppKit.ts | 2 -- packages/solana/src/connectors/PhantomConnector.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 1cb13caf..228afe21 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -63,7 +63,6 @@ interface AppKitConfig { export class AppKit { private projectId: string; - private metadata: Metadata; private adapters: BlockchainAdapter[]; private networks: AppKitNetwork[]; private defaultNetwork?: AppKitNetwork; @@ -73,7 +72,6 @@ export class AppKit { constructor(config: AppKitConfig) { this.projectId = config.projectId; - this.metadata = config.metadata; this.adapters = config.adapters; // Validate adapters to ensure no duplicate chainNamespaces diff --git a/packages/solana/src/connectors/PhantomConnector.ts b/packages/solana/src/connectors/PhantomConnector.ts index 73a388dc..9301c27c 100644 --- a/packages/solana/src/connectors/PhantomConnector.ts +++ b/packages/solana/src/connectors/PhantomConnector.ts @@ -66,7 +66,7 @@ export class PhantomConnector extends WalletConnector { }; this.provider = new PhantomProvider(providerConfig); - this.restoreSession(); + await this.restoreSession(); } private async initializeKeyPair(): Promise { From 5f71dfb2a4ac33e329951ccd2b1c6a736a7fa562 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:56:05 -0300 Subject: [PATCH 70/91] chore: changeset file --- .changeset/forty-zoos-double.md | 20 -------------------- .changeset/four-brooms-grin.md | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 .changeset/forty-zoos-double.md create mode 100644 .changeset/four-brooms-grin.md diff --git a/.changeset/forty-zoos-double.md b/.changeset/forty-zoos-double.md deleted file mode 100644 index c3d8e7e5..00000000 --- a/.changeset/forty-zoos-double.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@reown/appkit-scaffold-utils-react-native': major -'@reown/appkit-bitcoin-react-native': major -'@reown/appkit-ethers5-react-native': major -'@reown/appkit-react-native': major -'@reown/appkit-common-react-native': major -'@reown/appkit-ethers-react-native': major -'@reown/appkit-solana-react-native': major -'@reown/appkit-wagmi-react-native': major -'@reown/appkit-core-react-native': major -'@reown/appkit-siwe-react-native': major -'@reown/appkit-ui-react-native': major -'@reown/appkit-auth-ethers-react-native': major -'@reown/appkit-auth-wagmi-react-native': major -'@reown/appkit-coinbase-ethers-react-native': major -'@reown/appkit-coinbase-wagmi-react-native': major -'@reown/appkit-wallet-react-native': major ---- - -feat: added multichain support diff --git a/.changeset/four-brooms-grin.md b/.changeset/four-brooms-grin.md new file mode 100644 index 00000000..6c845722 --- /dev/null +++ b/.changeset/four-brooms-grin.md @@ -0,0 +1,15 @@ +--- +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-bitcoin-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-solana-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +feat: phantom wallet support From db3997807b6627d35fe99a071b2d4f15e747b799 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:15:52 -0300 Subject: [PATCH 71/91] chore: removed extra params from redirect link --- .../solana/src/providers/PhantomProvider.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/solana/src/providers/PhantomProvider.ts b/packages/solana/src/providers/PhantomProvider.ts index eb79fae0..3d416565 100644 --- a/packages/solana/src/providers/PhantomProvider.ts +++ b/packages/solana/src/providers/PhantomProvider.ts @@ -191,11 +191,10 @@ export class PhantomProvider extends EventEmitter implements Provider { }): Promise { const cluster = params?.cluster ?? 'mainnet-beta'; this.currentCluster = cluster; - const redirectLink = `${this.config.appScheme}://phantom_connect`; const connectDeeplinkParams: PhantomConnectParams = { app_url: this.config.dappUrl, dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), - redirect_link: redirectLink, + redirect_link: this.config.appScheme, cluster }; const url = this.buildUrl('connect', connectDeeplinkParams as any); @@ -207,7 +206,7 @@ export class PhantomProvider extends EventEmitter implements Provider { subscription.remove(); } const fullUrl = event.url; - if (fullUrl.startsWith(redirectLink)) { + if (fullUrl.startsWith(this.config.appScheme)) { const responseUrlParams = new URLSearchParams( fullUrl.substring(fullUrl.indexOf('?') + 1) ); @@ -289,10 +288,9 @@ export class PhantomProvider extends EventEmitter implements Provider { return Promise.resolve(); // Or reject, depending on desired strictness } - const redirectLink = `${this.config.appScheme}://phantom_disconnect`; const disconnectDeeplinkParams: PhantomDisconnectParams = { dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), - redirect_link: redirectLink, + redirect_link: this.config.appScheme, payload: encryptedDisconnectPayload.encryptedPayload, nonce: encryptedDisconnectPayload.nonce }; @@ -304,7 +302,7 @@ export class PhantomProvider extends EventEmitter implements Provider { if (subscription) { subscription.remove(); } - if (event.url.startsWith(redirectLink)) { + if (event.url.startsWith(this.config.appScheme)) { this.clearSession(); resolve(); } else { @@ -353,8 +351,6 @@ export class PhantomProvider extends EventEmitter implements Provider { } const rpcMethodName = this.getRpcMethodName(signingMethod); - const redirectSuffix = rpcMethodName.toLowerCase(); - const redirectLink = `${this.config.appScheme}://phantom_${redirectSuffix}`; let deeplinkUrl = ''; switch (signingMethod) { @@ -381,7 +377,7 @@ export class PhantomProvider extends EventEmitter implements Provider { const signTxDeeplinkParams: PhantomSignTransactionParams = { dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), - redirect_link: redirectLink, + redirect_link: this.config.appScheme, cluster: this.currentCluster, payload: encryptedData.encryptedPayload, nonce: encryptedData.nonce @@ -426,7 +422,7 @@ export class PhantomProvider extends EventEmitter implements Provider { const signMsgDeeplinkQueryPayload: PhantomSignMessageParams = { dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), - redirect_link: redirectLink, + redirect_link: this.config.appScheme, payload: encryptedPayloadData.encryptedPayload, nonce: encryptedPayloadData.nonce }; @@ -459,7 +455,7 @@ export class PhantomProvider extends EventEmitter implements Provider { const signAllTxDeeplinkParams: PhantomSignAllTransactionsParams = { dapp_encryption_public_key: bs58.encode(this.dappEncryptionKeyPair.publicKey), - redirect_link: redirectLink, + redirect_link: this.config.appScheme, cluster: this.currentCluster, payload: encryptedData.encryptedPayload, nonce: encryptedData.nonce @@ -479,7 +475,7 @@ export class PhantomProvider extends EventEmitter implements Provider { subscription.remove(); } const fullUrl = event.url; - if (fullUrl.startsWith(redirectLink)) { + if (fullUrl.startsWith(this.config.appScheme)) { const responseUrlParams = new URLSearchParams( fullUrl.substring(fullUrl.indexOf('?') + 1) ); From aa017fd29f675dafde931fe91cd2e510e47a8da5 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:01:37 -0300 Subject: [PATCH 72/91] chore: bump version --- .changeset/four-brooms-grin.md | 15 -- .changeset/pre.json | 25 ---- .changeset/silly-snails-carry.md | 13 -- apps/gallery/package.json | 2 +- apps/native/package.json | 12 +- package.json | 2 +- packages/appkit/CHANGELOG.md | 16 +++ packages/appkit/package.json | 10 +- packages/auth-ethers/CHANGELOG.md | 9 ++ packages/auth-ethers/package.json | 6 +- packages/auth-wagmi/CHANGELOG.md | 10 ++ packages/auth-wagmi/package.json | 8 +- packages/bitcoin/CHANGELOG.md | 14 ++ packages/bitcoin/package.json | 4 +- packages/coinbase-ethers/CHANGELOG.md | 8 ++ packages/coinbase-ethers/package.json | 4 +- packages/coinbase-wagmi/CHANGELOG.md | 8 ++ packages/coinbase-wagmi/package.json | 4 +- packages/common/CHANGELOG.md | 10 ++ packages/common/package.json | 2 +- packages/common/src/utils/ConstantsUtil.ts | 2 +- packages/core/CHANGELOG.md | 13 ++ packages/core/package.json | 4 +- packages/ethers/CHANGELOG.md | 16 +++ packages/ethers/package.json | 10 +- packages/ethers5/CHANGELOG.md | 11 ++ packages/ethers5/package.json | 8 +- packages/scaffold-utils/CHANGELOG.md | 14 ++ packages/scaffold-utils/package.json | 6 +- packages/siwe/CHANGELOG.md | 11 ++ packages/siwe/package.json | 8 +- packages/solana/CHANGELOG.md | 14 ++ packages/solana/package.json | 4 +- packages/ui/CHANGELOG.md | 13 ++ packages/ui/package.json | 4 +- packages/wagmi/CHANGELOG.md | 16 +++ packages/wagmi/package.json | 10 +- packages/wallet/CHANGELOG.md | 9 ++ packages/wallet/package.json | 6 +- yarn.lock | 155 ++++++++++++++------- 40 files changed, 354 insertions(+), 162 deletions(-) delete mode 100644 .changeset/four-brooms-grin.md delete mode 100644 .changeset/pre.json delete mode 100644 .changeset/silly-snails-carry.md create mode 100644 packages/bitcoin/CHANGELOG.md create mode 100644 packages/solana/CHANGELOG.md diff --git a/.changeset/four-brooms-grin.md b/.changeset/four-brooms-grin.md deleted file mode 100644 index 6c845722..00000000 --- a/.changeset/four-brooms-grin.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@reown/appkit-scaffold-utils-react-native': patch -'@reown/appkit-bitcoin-react-native': patch -'@reown/appkit-ethers5-react-native': patch -'@reown/appkit-react-native': patch -'@reown/appkit-common-react-native': patch -'@reown/appkit-ethers-react-native': patch -'@reown/appkit-solana-react-native': patch -'@reown/appkit-wagmi-react-native': patch -'@reown/appkit-core-react-native': patch -'@reown/appkit-siwe-react-native': patch -'@reown/appkit-ui-react-native': patch ---- - -feat: phantom wallet support diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index 6c6bbbce..00000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "mode": "exit", - "tag": "alpha", - "initialVersions": { - "@apps/gallery": "1.0.8", - "@apps/native": "1.0.8", - "@reown/appkit-react-native": "1.2.3", - "@reown/appkit-auth-ethers-react-native": "1.2.3", - "@reown/appkit-auth-wagmi-react-native": "1.2.3", - "@reown/appkit-bitcoin-react-native": "1.2.3", - "@reown/appkit-coinbase-ethers-react-native": "1.2.3", - "@reown/appkit-coinbase-wagmi-react-native": "1.2.3", - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-core-react-native": "1.2.3", - "@reown/appkit-ethers-react-native": "1.2.3", - "@reown/appkit-ethers5-react-native": "1.2.3", - "@reown/appkit-scaffold-utils-react-native": "1.2.3", - "@reown/appkit-siwe-react-native": "1.2.3", - "@reown/appkit-solana-react-native": "1.2.3", - "@reown/appkit-ui-react-native": "1.2.3", - "@reown/appkit-wagmi-react-native": "1.2.3", - "@reown/appkit-wallet-react-native": "1.2.3" - }, - "changesets": [] -} diff --git a/.changeset/silly-snails-carry.md b/.changeset/silly-snails-carry.md deleted file mode 100644 index 30a4d40f..00000000 --- a/.changeset/silly-snails-carry.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@reown/appkit-scaffold-utils-react-native': major -'@reown/appkit-bitcoin-react-native': major -'@reown/appkit-react-native': major -'@reown/appkit-common-react-native': major -'@reown/appkit-ethers-react-native': major -'@reown/appkit-solana-react-native': major -'@reown/appkit-wagmi-react-native': major -'@reown/appkit-core-react-native': major -'@reown/appkit-ui-react-native': major ---- - -BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha.0. Includes duplicate adapter validation and other API refinements. diff --git a/apps/gallery/package.json b/apps/gallery/package.json index 8cf47ccd..b51e19db 100644 --- a/apps/gallery/package.json +++ b/apps/gallery/package.json @@ -36,7 +36,7 @@ "build:gallery": "storybook build -o out" }, "dependencies": { - "@reown/appkit-ui-react-native": "1.2.3", + "@reown/appkit-ui-react-native": "2.0.0", "@storybook/theming": "^8.3.0" } } diff --git a/apps/native/package.json b/apps/native/package.json index c1f04b40..61225223 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -23,12 +23,12 @@ "@expo/metro-runtime": "~4.0.1", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@reown/appkit-auth-wagmi-react-native": "1.2.3", - "@reown/appkit-bitcoin-react-native": "1.2.3", - "@reown/appkit-ethers-react-native": "1.2.3", - "@reown/appkit-react-native": "1.2.3", - "@reown/appkit-solana-react-native": "1.2.3", - "@reown/appkit-wagmi-react-native": "1.2.3", + "@reown/appkit-auth-wagmi-react-native": "1.2.4", + "@reown/appkit-bitcoin-react-native": "2.0.0", + "@reown/appkit-ethers-react-native": "2.0.0", + "@reown/appkit-react-native": "2.0.0", + "@reown/appkit-solana-react-native": "2.0.0", + "@reown/appkit-wagmi-react-native": "2.0.0", "@solana/web3.js": "^1.98.2", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", diff --git a/package.json b/package.json index 54fba8f5..205dc8cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "appkit-react-native", - "version": "1.2.3", + "version": "2.0.0", "private": true, "workspaces": [ "packages/core", diff --git a/packages/appkit/CHANGELOG.md b/packages/appkit/CHANGELOG.md index cc85a1b2..b169c3ed 100644 --- a/packages/appkit/CHANGELOG.md +++ b/packages/appkit/CHANGELOG.md @@ -1,5 +1,21 @@ # @reown/appkit-scaffold-react-native +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-core-react-native@2.0.0 + - @reown/appkit-siwe-react-native@1.2.4 + - @reown/appkit-ui-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/appkit/package.json b/packages/appkit/package.json index 516a1ce4..73348854 100644 --- a/packages/appkit/package.json +++ b/packages/appkit/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -37,10 +37,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-core-react-native": "1.2.3", - "@reown/appkit-siwe-react-native": "1.2.3", - "@reown/appkit-ui-react-native": "1.2.3", + "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-core-react-native": "2.0.0", + "@reown/appkit-siwe-react-native": "1.2.4", + "@reown/appkit-ui-react-native": "2.0.0", "@walletconnect/universal-provider": "2.20.2", "valtio": "^1.13.2" }, diff --git a/packages/auth-ethers/CHANGELOG.md b/packages/auth-ethers/CHANGELOG.md index b23b2741..54853863 100644 --- a/packages/auth-ethers/CHANGELOG.md +++ b/packages/auth-ethers/CHANGELOG.md @@ -1,5 +1,14 @@ # @reown/appkit-auth-ethers-react-native +## 1.2.4 + +### Patch Changes + +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-wallet-react-native@1.2.4 + ## 1.2.3 ### Patch Changes diff --git a/packages/auth-ethers/package.json b/packages/auth-ethers/package.json index 99d8454e..321fd531 100644 --- a/packages/auth-ethers/package.json +++ b/packages/auth-ethers/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-auth-ethers-react-native", - "version": "1.2.3", + "version": "1.2.4", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -36,8 +36,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-wallet-react-native": "1.2.3" + "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-wallet-react-native": "1.2.4" }, "peerDependencies": { "ethers": ">=5" diff --git a/packages/auth-wagmi/CHANGELOG.md b/packages/auth-wagmi/CHANGELOG.md index 69cd183e..92cda17f 100644 --- a/packages/auth-wagmi/CHANGELOG.md +++ b/packages/auth-wagmi/CHANGELOG.md @@ -1,5 +1,15 @@ # @reown/appkit-auth-wagmi-react-native +## 1.2.4 + +### Patch Changes + +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-core-react-native@2.0.0 + - @reown/appkit-wallet-react-native@1.2.4 + ## 1.2.3 ### Patch Changes diff --git a/packages/auth-wagmi/package.json b/packages/auth-wagmi/package.json index ddfe43f8..7c17ec85 100644 --- a/packages/auth-wagmi/package.json +++ b/packages/auth-wagmi/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-auth-wagmi-react-native", - "version": "1.2.3", + "version": "1.2.4", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -36,9 +36,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-core-react-native": "1.2.3", - "@reown/appkit-wallet-react-native": "1.2.3" + "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-core-react-native": "2.0.0", + "@reown/appkit-wallet-react-native": "1.2.4" }, "peerDependencies": { "wagmi": ">=2" diff --git a/packages/bitcoin/CHANGELOG.md b/packages/bitcoin/CHANGELOG.md new file mode 100644 index 00000000..8211e922 --- /dev/null +++ b/packages/bitcoin/CHANGELOG.md @@ -0,0 +1,14 @@ +# @reown/appkit-bitcoin-react-native + +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json index 33339838..06ee1418 100644 --- a/packages/bitcoin/package.json +++ b/packages/bitcoin/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-bitcoin-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,6 +39,6 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3" + "@reown/appkit-common-react-native": "2.0.0" } } diff --git a/packages/coinbase-ethers/CHANGELOG.md b/packages/coinbase-ethers/CHANGELOG.md index cad9c3f2..6f8ccf5d 100644 --- a/packages/coinbase-ethers/CHANGELOG.md +++ b/packages/coinbase-ethers/CHANGELOG.md @@ -1,5 +1,13 @@ # @reown/appkit-coinbase-ethers-react-native +## 1.2.4 + +### Patch Changes + +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/coinbase-ethers/package.json b/packages/coinbase-ethers/package.json index d6d75d8b..07b68bf9 100644 --- a/packages/coinbase-ethers/package.json +++ b/packages/coinbase-ethers/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-coinbase-ethers-react-native", - "version": "1.2.3", + "version": "1.2.4", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3" + "@reown/appkit-common-react-native": "2.0.0" }, "peerDependencies": { "@coinbase/wallet-mobile-sdk": ">=1.0.10", diff --git a/packages/coinbase-wagmi/CHANGELOG.md b/packages/coinbase-wagmi/CHANGELOG.md index 6d259aa2..847be95a 100644 --- a/packages/coinbase-wagmi/CHANGELOG.md +++ b/packages/coinbase-wagmi/CHANGELOG.md @@ -1,5 +1,13 @@ # @reown/appkit-coinbase-wagmi-react-native +## 1.2.4 + +### Patch Changes + +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/coinbase-wagmi/package.json b/packages/coinbase-wagmi/package.json index f58e678e..d8d4806d 100644 --- a/packages/coinbase-wagmi/package.json +++ b/packages/coinbase-wagmi/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-coinbase-wagmi-react-native", - "version": "1.2.3", + "version": "1.2.4", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3" + "@reown/appkit-common-react-native": "2.0.0" }, "peerDependencies": { "@coinbase/wallet-mobile-sdk": ">=1.0.10", diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index f6ee3ebd..854f41c9 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -1,5 +1,15 @@ # @reown/appkit-common-react-native +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support + ## 1.2.3 ### Patch Changes diff --git a/packages/common/package.json b/packages/common/package.json index 5f7053df..fe593e09 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-common-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", diff --git a/packages/common/src/utils/ConstantsUtil.ts b/packages/common/src/utils/ConstantsUtil.ts index 3b297540..809c651e 100644 --- a/packages/common/src/utils/ConstantsUtil.ts +++ b/packages/common/src/utils/ConstantsUtil.ts @@ -1,5 +1,5 @@ export const ConstantsUtil = { - VERSION: '1.2.3', + VERSION: '2.0.0', EIP155: 'eip155', ADD_CHAIN_METHOD: 'wallet_addEthereumChain', diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index d207ba5e..0c9af542 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,18 @@ # @reown/appkit-core-react-native +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 453fa5ce..50ff398c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-core-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,7 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-common-react-native": "2.0.0", "countries-and-timezones": "3.7.2", "valtio": "1.11.2" }, diff --git a/packages/ethers/CHANGELOG.md b/packages/ethers/CHANGELOG.md index 37c98c80..8f040b98 100644 --- a/packages/ethers/CHANGELOG.md +++ b/packages/ethers/CHANGELOG.md @@ -1,5 +1,21 @@ # @reown/appkit-ethers-react-native +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-scaffold-utils-react-native@2.0.0 + - @reown/appkit-react-native@2.0.0 + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-siwe-react-native@1.2.4 + ## 1.2.3 ### Patch Changes diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 98b610f9..ab9ce493 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-ethers-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,10 +38,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-react-native": "1.2.3", - "@reown/appkit-scaffold-utils-react-native": "1.2.3", - "@reown/appkit-siwe-react-native": "1.2.3", + "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-react-native": "2.0.0", + "@reown/appkit-scaffold-utils-react-native": "2.0.0", + "@reown/appkit-siwe-react-native": "1.2.4", "@walletconnect/ethereum-provider": "2.20.2" }, "peerDependencies": { diff --git a/packages/ethers5/CHANGELOG.md b/packages/ethers5/CHANGELOG.md index 1462a363..4475825f 100644 --- a/packages/ethers5/CHANGELOG.md +++ b/packages/ethers5/CHANGELOG.md @@ -1,5 +1,16 @@ # @reown/appkit-ethers5-react-native +## 1.2.4 + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-scaffold-utils-react-native@2.0.0 + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-siwe-react-native@1.2.4 + ## 1.2.3 ### Patch Changes diff --git a/packages/ethers5/package.json b/packages/ethers5/package.json index 63bd9136..1abea30d 100644 --- a/packages/ethers5/package.json +++ b/packages/ethers5/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-ethers5-react-native", - "version": "1.2.3", + "version": "1.2.4", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,10 +38,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-common-react-native": "2.0.0", "@reown/appkit-scaffold-react-native": "1.2.3", - "@reown/appkit-scaffold-utils-react-native": "1.2.3", - "@reown/appkit-siwe-react-native": "1.2.3", + "@reown/appkit-scaffold-utils-react-native": "2.0.0", + "@reown/appkit-siwe-react-native": "1.2.4", "@walletconnect/ethereum-provider": "2.20.2" }, "peerDependencies": { diff --git a/packages/scaffold-utils/CHANGELOG.md b/packages/scaffold-utils/CHANGELOG.md index 050f4119..87c52ee7 100644 --- a/packages/scaffold-utils/CHANGELOG.md +++ b/packages/scaffold-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @reown/appkit-scaffold-utils-react-native +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-core-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index 3f3a1a94..562842ba 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-scaffold-utils-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -35,8 +35,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-core-react-native": "1.2.3" + "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-core-react-native": "2.0.0" }, "react-native": "src/index.ts", "react-native-builder-bob": { diff --git a/packages/siwe/CHANGELOG.md b/packages/siwe/CHANGELOG.md index 1cc19459..b10db18b 100644 --- a/packages/siwe/CHANGELOG.md +++ b/packages/siwe/CHANGELOG.md @@ -1,5 +1,16 @@ # @reown/appkit-siwe-react-native +## 1.2.4 + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-core-react-native@2.0.0 + - @reown/appkit-ui-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/siwe/package.json b/packages/siwe/package.json index 47901ab0..10b75000 100644 --- a/packages/siwe/package.json +++ b/packages/siwe/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-siwe-react-native", - "version": "1.2.3", + "version": "1.2.4", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-core-react-native": "1.2.3", - "@reown/appkit-ui-react-native": "1.2.3", + "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-core-react-native": "2.0.0", + "@reown/appkit-ui-react-native": "2.0.0", "valtio": "1.11.2" }, "peerDependencies": { diff --git a/packages/solana/CHANGELOG.md b/packages/solana/CHANGELOG.md new file mode 100644 index 00000000..e362d275 --- /dev/null +++ b/packages/solana/CHANGELOG.md @@ -0,0 +1,14 @@ +# @reown/appkit-solana-react-native + +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 diff --git a/packages/solana/package.json b/packages/solana/package.json index 736c470c..2f7f0fcb 100644 --- a/packages/solana/package.json +++ b/packages/solana/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-solana-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-common-react-native": "2.0.0", "bs58": "6.0.0", "tweetnacl": "1.0.3" } diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index bfad2478..cbe125fe 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,18 @@ # @reown/appkit-ui-react-native +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-common-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index 943ef506..523c45b9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-ui-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,7 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", + "@reown/appkit-common-react-native": "2.0.0", "polished": "4.3.1", "qrcode": "1.5.3" }, diff --git a/packages/wagmi/CHANGELOG.md b/packages/wagmi/CHANGELOG.md index 7be6ad60..fe41f3a7 100644 --- a/packages/wagmi/CHANGELOG.md +++ b/packages/wagmi/CHANGELOG.md @@ -1,5 +1,21 @@ # @reown/appkit-wagmi-react-native +## 2.0.0 + +### Major Changes + +- 40d26c1: BREAKING CHANGE: Prepare for AppKit v2.0.0-alpha Includes duplicate adapter validation and other API refinements. + +### Patch Changes + +- 5f71dfb: feat: phantom wallet support +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-scaffold-utils-react-native@2.0.0 + - @reown/appkit-react-native@2.0.0 + - @reown/appkit-common-react-native@2.0.0 + - @reown/appkit-siwe-react-native@1.2.4 + ## 1.2.3 ### Patch Changes diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index 5635649a..3329bc8c 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-wagmi-react-native", - "version": "1.2.3", + "version": "2.0.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,10 +39,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.3", - "@reown/appkit-react-native": "1.2.3", - "@reown/appkit-scaffold-utils-react-native": "1.2.3", - "@reown/appkit-siwe-react-native": "1.2.3" + "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-react-native": "2.0.0", + "@reown/appkit-scaffold-utils-react-native": "2.0.0", + "@reown/appkit-siwe-react-native": "1.2.4" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index 4b97eaa7..2b5c2d55 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -1,5 +1,14 @@ # @reown/appkit-wallet-react-native +## 1.2.4 + +### Patch Changes + +- Updated dependencies [5f71dfb] +- Updated dependencies [40d26c1] + - @reown/appkit-core-react-native@2.0.0 + - @reown/appkit-ui-react-native@2.0.0 + ## 1.2.3 ### Patch Changes diff --git a/packages/wallet/package.json b/packages/wallet/package.json index 483ceb77..c7848b4b 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-wallet-react-native", - "version": "1.2.3", + "version": "1.2.4", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -35,8 +35,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-core-react-native": "1.2.3", - "@reown/appkit-ui-react-native": "1.2.3", + "@reown/appkit-core-react-native": "2.0.0", + "@reown/appkit-ui-react-native": "2.0.0", "zod": "3.22.4" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 24dcba37..ae186f3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -71,7 +71,7 @@ __metadata: "@babel/preset-react": "npm:^7.22.5" "@babel/preset-typescript": "npm:7.24.7" "@chromatic-com/storybook": "npm:^1" - "@reown/appkit-ui-react-native": "npm:1.2.3" + "@reown/appkit-ui-react-native": "npm:2.0.0" "@storybook/addon-essentials": "npm:^8.3.0" "@storybook/addon-interactions": "npm:^8.3.0" "@storybook/addon-links": "npm:^8.3.0" @@ -107,12 +107,12 @@ __metadata: "@playwright/test": "npm:^1.49.1" "@react-native-async-storage/async-storage": "npm:2.1.2" "@react-native-community/netinfo": "npm:11.4.1" - "@reown/appkit-auth-wagmi-react-native": "npm:1.2.3" - "@reown/appkit-bitcoin-react-native": "npm:1.2.3" - "@reown/appkit-ethers-react-native": "npm:1.2.3" - "@reown/appkit-react-native": "npm:1.2.3" - "@reown/appkit-solana-react-native": "npm:1.2.3" - "@reown/appkit-wagmi-react-native": "npm:1.2.3" + "@reown/appkit-auth-wagmi-react-native": "npm:1.2.4" + "@reown/appkit-bitcoin-react-native": "npm:2.0.0" + "@reown/appkit-ethers-react-native": "npm:2.0.0" + "@reown/appkit-react-native": "npm:2.0.0" + "@reown/appkit-solana-react-native": "npm:2.0.0" + "@reown/appkit-wagmi-react-native": "npm:2.0.0" "@solana/web3.js": "npm:^1.98.2" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" @@ -7111,30 +7111,30 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-auth-ethers-react-native@workspace:packages/auth-ethers" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-wallet-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-wallet-react-native": "npm:1.2.4" peerDependencies: ethers: ">=5" languageName: unknown linkType: soft -"@reown/appkit-auth-wagmi-react-native@npm:1.2.3, @reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi": +"@reown/appkit-auth-wagmi-react-native@npm:1.2.4, @reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi": version: 0.0.0-use.local resolution: "@reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-core-react-native": "npm:1.2.3" - "@reown/appkit-wallet-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-core-react-native": "npm:2.0.0" + "@reown/appkit-wallet-react-native": "npm:1.2.4" peerDependencies: wagmi: ">=2" languageName: unknown linkType: soft -"@reown/appkit-bitcoin-react-native@npm:1.2.3, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": +"@reown/appkit-bitcoin-react-native@npm:2.0.0, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": version: 0.0.0-use.local resolution: "@reown/appkit-bitcoin-react-native@workspace:packages/bitcoin" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" languageName: unknown linkType: soft @@ -7142,7 +7142,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" peerDependencies: "@coinbase/wallet-mobile-sdk": ">=1.0.10" ethers: ">=5" @@ -7153,14 +7153,24 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-wagmi-react-native@workspace:packages/coinbase-wagmi" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" peerDependencies: "@coinbase/wallet-mobile-sdk": ">=1.0.10" wagmi: ">=2" languageName: unknown linkType: soft -"@reown/appkit-common-react-native@npm:1.2.3, @reown/appkit-common-react-native@workspace:packages/common": +"@reown/appkit-common-react-native@npm:1.2.3": + version: 1.2.3 + resolution: "@reown/appkit-common-react-native@npm:1.2.3" + dependencies: + bignumber.js: "npm:9.1.2" + dayjs: "npm:1.11.10" + checksum: 074c7d35c8d4cc9bf72d46b930eb6b4b969cf4b2bfe209c8a55560f54db90c52c46460808794ac214594d4a5ad43e806fea9d30a3e97be5b4f1a656aabd2843d + languageName: node + linkType: hard + +"@reown/appkit-common-react-native@npm:2.0.0, @reown/appkit-common-react-native@workspace:packages/common": version: 0.0.0-use.local resolution: "@reown/appkit-common-react-native@workspace:packages/common" dependencies: @@ -7193,11 +7203,26 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-core-react-native@npm:1.2.3, @reown/appkit-core-react-native@workspace:packages/core": +"@reown/appkit-core-react-native@npm:1.2.3": + version: 1.2.3 + resolution: "@reown/appkit-core-react-native@npm:1.2.3" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.3" + valtio: "npm:1.11.2" + peerDependencies: + "@react-native-async-storage/async-storage": ">=1.17.0" + "@walletconnect/react-native-compat": ">=2.13.1" + react: ">=17" + react-native: ">=0.68.5" + checksum: 74dbc78f16d571bceca93d786790094ba1022e68f6dca9090f1a6f5fb7afd8aa7acf90b5cc66fd1598a06071d6cefe30045347011ff2781f77105b1028f8049d + languageName: node + linkType: hard + +"@reown/appkit-core-react-native@npm:2.0.0, @reown/appkit-core-react-native@workspace:packages/core": version: 0.0.0-use.local resolution: "@reown/appkit-core-react-native@workspace:packages/core" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" countries-and-timezones: "npm:3.7.2" valtio: "npm:1.11.2" peerDependencies: @@ -7208,14 +7233,14 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-ethers-react-native@npm:1.2.3, @reown/appkit-ethers-react-native@workspace:packages/ethers": +"@reown/appkit-ethers-react-native@npm:2.0.0, @reown/appkit-ethers-react-native@workspace:packages/ethers": version: 0.0.0-use.local resolution: "@reown/appkit-ethers-react-native@workspace:packages/ethers" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" - "@reown/appkit-siwe-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-react-native": "npm:2.0.0" + "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0" + "@reown/appkit-siwe-react-native": "npm:1.2.4" "@walletconnect/ethereum-provider": "npm:2.20.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -7231,10 +7256,10 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-ethers5-react-native@workspace:packages/ethers5" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" "@reown/appkit-scaffold-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" - "@reown/appkit-siwe-react-native": "npm:1.2.3" + "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0" + "@reown/appkit-siwe-react-native": "npm:1.2.4" "@walletconnect/ethereum-provider": "npm:2.20.2" ethers: "npm:5.7.2" peerDependencies: @@ -7257,14 +7282,14 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-react-native@npm:1.2.3, @reown/appkit-react-native@workspace:packages/appkit": +"@reown/appkit-react-native@npm:2.0.0, @reown/appkit-react-native@workspace:packages/appkit": version: 0.0.0-use.local resolution: "@reown/appkit-react-native@workspace:packages/appkit" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-core-react-native": "npm:1.2.3" - "@reown/appkit-siwe-react-native": "npm:1.2.3" - "@reown/appkit-ui-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-core-react-native": "npm:2.0.0" + "@reown/appkit-siwe-react-native": "npm:1.2.4" + "@reown/appkit-ui-react-native": "npm:2.0.0" "@walletconnect/universal-provider": "npm:2.20.2" valtio: "npm:^1.13.2" peerDependencies: @@ -7304,18 +7329,18 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-scaffold-utils-react-native@npm:1.2.3, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": +"@reown/appkit-scaffold-utils-react-native@npm:2.0.0, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-core-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-core-react-native": "npm:2.0.0" languageName: unknown linkType: soft -"@reown/appkit-siwe-react-native@npm:1.2.3, @reown/appkit-siwe-react-native@workspace:packages/siwe": - version: 0.0.0-use.local - resolution: "@reown/appkit-siwe-react-native@workspace:packages/siwe" +"@reown/appkit-siwe-react-native@npm:1.2.3": + version: 1.2.3 + resolution: "@reown/appkit-siwe-react-native@npm:1.2.3" dependencies: "@reown/appkit-common-react-native": "npm:1.2.3" "@reown/appkit-core-react-native": "npm:1.2.3" @@ -7323,24 +7348,52 @@ __metadata: valtio: "npm:1.11.2" peerDependencies: "@walletconnect/utils": ">=2.16.1" + checksum: 7c4b52d0a3e182586c838bf1dff9165710e13988a834e893eea94f5e765d67743418f55f9faff1747a75d7acc88c41d538527bfbfd16e2d7b2f44b77db07e4f7 + languageName: node + linkType: hard + +"@reown/appkit-siwe-react-native@npm:1.2.4, @reown/appkit-siwe-react-native@workspace:packages/siwe": + version: 0.0.0-use.local + resolution: "@reown/appkit-siwe-react-native@workspace:packages/siwe" + dependencies: + "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-core-react-native": "npm:2.0.0" + "@reown/appkit-ui-react-native": "npm:2.0.0" + valtio: "npm:1.11.2" + peerDependencies: + "@walletconnect/utils": ">=2.16.1" languageName: unknown linkType: soft -"@reown/appkit-solana-react-native@npm:1.2.3, @reown/appkit-solana-react-native@workspace:packages/solana": +"@reown/appkit-solana-react-native@npm:2.0.0, @reown/appkit-solana-react-native@workspace:packages/solana": version: 0.0.0-use.local resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" bs58: "npm:6.0.0" tweetnacl: "npm:1.0.3" languageName: unknown linkType: soft -"@reown/appkit-ui-react-native@npm:1.2.3, @reown/appkit-ui-react-native@workspace:packages/ui": +"@reown/appkit-ui-react-native@npm:1.2.3": + version: 1.2.3 + resolution: "@reown/appkit-ui-react-native@npm:1.2.3" + dependencies: + polished: "npm:4.3.1" + qrcode: "npm:1.5.3" + peerDependencies: + react: ">=17" + react-native: ">=0.68.5" + react-native-svg: ">=13" + checksum: 6e38ad8b6882c39084fc2e6eb01cf1662737f9e66046ec2b8a50de5af0c37eafc15c55aee833b9de58425fc7d340ff8ff3490ef23e79cc2cbc881de416a07d83 + languageName: node + linkType: hard + +"@reown/appkit-ui-react-native@npm:2.0.0, @reown/appkit-ui-react-native@workspace:packages/ui": version: 0.0.0-use.local resolution: "@reown/appkit-ui-react-native@workspace:packages/ui" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" polished: "npm:4.3.1" qrcode: "npm:1.5.3" peerDependencies: @@ -7381,14 +7434,14 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-wagmi-react-native@npm:1.2.3, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": +"@reown/appkit-wagmi-react-native@npm:2.0.0, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": version: 0.0.0-use.local resolution: "@reown/appkit-wagmi-react-native@workspace:packages/wagmi" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-utils-react-native": "npm:1.2.3" - "@reown/appkit-siwe-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-react-native": "npm:2.0.0" + "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0" + "@reown/appkit-siwe-react-native": "npm:1.2.4" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" "@react-native-community/netinfo": "*" @@ -7401,12 +7454,12 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-wallet-react-native@npm:1.2.3, @reown/appkit-wallet-react-native@workspace:packages/wallet": +"@reown/appkit-wallet-react-native@npm:1.2.4, @reown/appkit-wallet-react-native@workspace:packages/wallet": version: 0.0.0-use.local resolution: "@reown/appkit-wallet-react-native@workspace:packages/wallet" dependencies: - "@reown/appkit-core-react-native": "npm:1.2.3" - "@reown/appkit-ui-react-native": "npm:1.2.3" + "@reown/appkit-core-react-native": "npm:2.0.0" + "@reown/appkit-ui-react-native": "npm:2.0.0" zod: "npm:3.22.4" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" From b09d2f16a24af08040d650f68a5de02e9e020bed Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:02:08 -0300 Subject: [PATCH 73/91] chore: prettier --- .github/workflows/alpha-release.yml | 2 +- apps/native/app.json | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml index b7d80fbd..74033417 100644 --- a/.github/workflows/alpha-release.yml +++ b/.github/workflows/alpha-release.yml @@ -76,4 +76,4 @@ jobs: - name: Push Changes and Tags run: | git push --follow-tags - shell: bash \ No newline at end of file + shell: bash diff --git a/apps/native/app.json b/apps/native/app.json index c71e27f0..2505febf 100644 --- a/apps/native/app.json +++ b/apps/native/app.json @@ -20,12 +20,8 @@ "policy": "appVersion" }, "owner": "nacho.reown", - "assetBundlePatterns": [ - "**/*" - ], - "plugins": [ - "./expo-plugins/installed-wallets.js" - ], + "assetBundlePatterns": ["**/*"], + "plugins": ["./expo-plugins/installed-wallets.js"], "ios": { "buildNumber": "1", "bundleIdentifier": "com.walletconnect.web3modal.rnsdk", From c24e9e04ab416d76e35ea2663b6409726d7c7cd2 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:46:24 -0300 Subject: [PATCH 74/91] chore: fixed versions --- apps/gallery/package.json | 2 +- apps/native/package.json | 10 +- package.json | 2 +- packages/appkit/package.json | 8 +- packages/auth-ethers/package.json | 2 +- packages/auth-wagmi/package.json | 4 +- packages/bitcoin/package.json | 4 +- packages/coinbase-ethers/package.json | 2 +- packages/coinbase-wagmi/package.json | 2 +- packages/common/package.json | 2 +- packages/core/package.json | 4 +- packages/ethers/package.json | 8 +- packages/ethers5/package.json | 6 +- packages/scaffold-utils/package.json | 6 +- packages/siwe/package.json | 6 +- packages/solana/package.json | 4 +- packages/ui/package.json | 4 +- packages/wagmi/package.json | 8 +- packages/wallet/package.json | 4 +- yarn.lock | 152 +++++++++++++------------- 20 files changed, 118 insertions(+), 122 deletions(-) diff --git a/apps/gallery/package.json b/apps/gallery/package.json index b51e19db..8c9d5270 100644 --- a/apps/gallery/package.json +++ b/apps/gallery/package.json @@ -36,7 +36,7 @@ "build:gallery": "storybook build -o out" }, "dependencies": { - "@reown/appkit-ui-react-native": "2.0.0", + "@reown/appkit-ui-react-native": "2.0.0-alpha.0", "@storybook/theming": "^8.3.0" } } diff --git a/apps/native/package.json b/apps/native/package.json index 61225223..d801ce9b 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -24,11 +24,11 @@ "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", "@reown/appkit-auth-wagmi-react-native": "1.2.4", - "@reown/appkit-bitcoin-react-native": "2.0.0", - "@reown/appkit-ethers-react-native": "2.0.0", - "@reown/appkit-react-native": "2.0.0", - "@reown/appkit-solana-react-native": "2.0.0", - "@reown/appkit-wagmi-react-native": "2.0.0", + "@reown/appkit-bitcoin-react-native": "2.0.0-alpha.0", + "@reown/appkit-ethers-react-native": "2.0.0-alpha.0", + "@reown/appkit-react-native": "2.0.0-alpha.0", + "@reown/appkit-solana-react-native": "2.0.0-alpha.0", + "@reown/appkit-wagmi-react-native": "2.0.0-alpha.0", "@solana/web3.js": "^1.98.2", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", diff --git a/package.json b/package.json index 205dc8cc..48051b21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "appkit-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "private": true, "workspaces": [ "packages/core", diff --git a/packages/appkit/package.json b/packages/appkit/package.json index 73348854..60c3d4b4 100644 --- a/packages/appkit/package.json +++ b/packages/appkit/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -37,10 +37,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", - "@reown/appkit-core-react-native": "2.0.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-core-react-native": "2.0.0-alpha.0", "@reown/appkit-siwe-react-native": "1.2.4", - "@reown/appkit-ui-react-native": "2.0.0", + "@reown/appkit-ui-react-native": "2.0.0-alpha.0", "@walletconnect/universal-provider": "2.20.2", "valtio": "^1.13.2" }, diff --git a/packages/auth-ethers/package.json b/packages/auth-ethers/package.json index 321fd531..efc6cc52 100644 --- a/packages/auth-ethers/package.json +++ b/packages/auth-ethers/package.json @@ -36,7 +36,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-common-react-native": "1.2.4", "@reown/appkit-wallet-react-native": "1.2.4" }, "peerDependencies": { diff --git a/packages/auth-wagmi/package.json b/packages/auth-wagmi/package.json index 7c17ec85..f4170a13 100644 --- a/packages/auth-wagmi/package.json +++ b/packages/auth-wagmi/package.json @@ -36,8 +36,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", - "@reown/appkit-core-react-native": "2.0.0", + "@reown/appkit-common-react-native": "1.2.4", + "@reown/appkit-core-react-native": "1.2.4", "@reown/appkit-wallet-react-native": "1.2.4" }, "peerDependencies": { diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json index 06ee1418..e933db0a 100644 --- a/packages/bitcoin/package.json +++ b/packages/bitcoin/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-bitcoin-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,6 +39,6 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0" + "@reown/appkit-common-react-native": "2.0.0-alpha.0" } } diff --git a/packages/coinbase-ethers/package.json b/packages/coinbase-ethers/package.json index 07b68bf9..4ca42a42 100644 --- a/packages/coinbase-ethers/package.json +++ b/packages/coinbase-ethers/package.json @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0" + "@reown/appkit-common-react-native": "1.2.4" }, "peerDependencies": { "@coinbase/wallet-mobile-sdk": ">=1.0.10", diff --git a/packages/coinbase-wagmi/package.json b/packages/coinbase-wagmi/package.json index d8d4806d..c7802339 100644 --- a/packages/coinbase-wagmi/package.json +++ b/packages/coinbase-wagmi/package.json @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0" + "@reown/appkit-common-react-native": "1.2.4" }, "peerDependencies": { "@coinbase/wallet-mobile-sdk": ">=1.0.10", diff --git a/packages/common/package.json b/packages/common/package.json index fe593e09..5c53b4be 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-common-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", diff --git a/packages/core/package.json b/packages/core/package.json index 50ff398c..08c4b31f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-core-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,7 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.0", "countries-and-timezones": "3.7.2", "valtio": "1.11.2" }, diff --git a/packages/ethers/package.json b/packages/ethers/package.json index ab9ce493..b1fe06ad 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-ethers-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,9 +38,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", - "@reown/appkit-react-native": "2.0.0", - "@reown/appkit-scaffold-utils-react-native": "2.0.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-react-native": "2.0.0-alpha.0", + "@reown/appkit-scaffold-utils-react-native": "2.0.0-alpha.0", "@reown/appkit-siwe-react-native": "1.2.4", "@walletconnect/ethereum-provider": "2.20.2" }, diff --git a/packages/ethers5/package.json b/packages/ethers5/package.json index 1abea30d..dde33257 100644 --- a/packages/ethers5/package.json +++ b/packages/ethers5/package.json @@ -38,9 +38,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", - "@reown/appkit-scaffold-react-native": "1.2.3", - "@reown/appkit-scaffold-utils-react-native": "2.0.0", + "@reown/appkit-common-react-native": "1.2.4", + "@reown/appkit-scaffold-react-native": "1.2.4", + "@reown/appkit-scaffold-utils-react-native": "1.2.4", "@reown/appkit-siwe-react-native": "1.2.4", "@walletconnect/ethereum-provider": "2.20.2" }, diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index 562842ba..b5d872a6 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-scaffold-utils-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -35,8 +35,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", - "@reown/appkit-core-react-native": "2.0.0" + "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-core-react-native": "2.0.0-alpha.0" }, "react-native": "src/index.ts", "react-native-builder-bob": { diff --git a/packages/siwe/package.json b/packages/siwe/package.json index 10b75000..14a00bed 100644 --- a/packages/siwe/package.json +++ b/packages/siwe/package.json @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", - "@reown/appkit-core-react-native": "2.0.0", - "@reown/appkit-ui-react-native": "2.0.0", + "@reown/appkit-common-react-native": "1.2.4", + "@reown/appkit-core-react-native": "1.2.4", + "@reown/appkit-ui-react-native": "1.2.4", "valtio": "1.11.2" }, "peerDependencies": { diff --git a/packages/solana/package.json b/packages/solana/package.json index 2f7f0fcb..3da37c28 100644 --- a/packages/solana/package.json +++ b/packages/solana/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-solana-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.0", "bs58": "6.0.0", "tweetnacl": "1.0.3" } diff --git a/packages/ui/package.json b/packages/ui/package.json index 523c45b9..b052e5a3 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-ui-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,7 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.0", "polished": "4.3.1", "qrcode": "1.5.3" }, diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index 3329bc8c..5e842c4a 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-wagmi-react-native", - "version": "2.0.0", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0", - "@reown/appkit-react-native": "2.0.0", - "@reown/appkit-scaffold-utils-react-native": "2.0.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-react-native": "2.0.0-alpha.0", + "@reown/appkit-scaffold-utils-react-native": "2.0.0-alpha.0", "@reown/appkit-siwe-react-native": "1.2.4" }, "peerDependencies": { diff --git a/packages/wallet/package.json b/packages/wallet/package.json index c7848b4b..f49b737b 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -35,8 +35,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-core-react-native": "2.0.0", - "@reown/appkit-ui-react-native": "2.0.0", + "@reown/appkit-core-react-native": "1.2.4", + "@reown/appkit-ui-react-native": "1.2.4", "zod": "3.22.4" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index ae186f3e..1c20f277 100644 --- a/yarn.lock +++ b/yarn.lock @@ -71,7 +71,7 @@ __metadata: "@babel/preset-react": "npm:^7.22.5" "@babel/preset-typescript": "npm:7.24.7" "@chromatic-com/storybook": "npm:^1" - "@reown/appkit-ui-react-native": "npm:2.0.0" + "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.0" "@storybook/addon-essentials": "npm:^8.3.0" "@storybook/addon-interactions": "npm:^8.3.0" "@storybook/addon-links": "npm:^8.3.0" @@ -108,11 +108,11 @@ __metadata: "@react-native-async-storage/async-storage": "npm:2.1.2" "@react-native-community/netinfo": "npm:11.4.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.2.4" - "@reown/appkit-bitcoin-react-native": "npm:2.0.0" - "@reown/appkit-ethers-react-native": "npm:2.0.0" - "@reown/appkit-react-native": "npm:2.0.0" - "@reown/appkit-solana-react-native": "npm:2.0.0" - "@reown/appkit-wagmi-react-native": "npm:2.0.0" + "@reown/appkit-bitcoin-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-ethers-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-solana-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-wagmi-react-native": "npm:2.0.0-alpha.0" "@solana/web3.js": "npm:^1.98.2" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" @@ -7111,7 +7111,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-auth-ethers-react-native@workspace:packages/auth-ethers" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:1.2.4" "@reown/appkit-wallet-react-native": "npm:1.2.4" peerDependencies: ethers: ">=5" @@ -7122,19 +7122,19 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" - "@reown/appkit-core-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:1.2.4" + "@reown/appkit-core-react-native": "npm:1.2.4" "@reown/appkit-wallet-react-native": "npm:1.2.4" peerDependencies: wagmi: ">=2" languageName: unknown linkType: soft -"@reown/appkit-bitcoin-react-native@npm:2.0.0, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": +"@reown/appkit-bitcoin-react-native@npm:2.0.0-alpha.0, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": version: 0.0.0-use.local resolution: "@reown/appkit-bitcoin-react-native@workspace:packages/bitcoin" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" languageName: unknown linkType: soft @@ -7142,7 +7142,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:1.2.4" peerDependencies: "@coinbase/wallet-mobile-sdk": ">=1.0.10" ethers: ">=5" @@ -7153,24 +7153,24 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-wagmi-react-native@workspace:packages/coinbase-wagmi" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:1.2.4" peerDependencies: "@coinbase/wallet-mobile-sdk": ">=1.0.10" wagmi: ">=2" languageName: unknown linkType: soft -"@reown/appkit-common-react-native@npm:1.2.3": - version: 1.2.3 - resolution: "@reown/appkit-common-react-native@npm:1.2.3" +"@reown/appkit-common-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-common-react-native@npm:1.2.4" dependencies: bignumber.js: "npm:9.1.2" dayjs: "npm:1.11.10" - checksum: 074c7d35c8d4cc9bf72d46b930eb6b4b969cf4b2bfe209c8a55560f54db90c52c46460808794ac214594d4a5ad43e806fea9d30a3e97be5b4f1a656aabd2843d + checksum: fc26b9943788fd78bf8d745e439575acaa30e5c3775d2ab83444591eba363ec9412ba14df669175c04f3236df9c2732cd3ee61786b34cc6e121b0ad2ee2880f9 languageName: node linkType: hard -"@reown/appkit-common-react-native@npm:2.0.0, @reown/appkit-common-react-native@workspace:packages/common": +"@reown/appkit-common-react-native@npm:2.0.0-alpha.0, @reown/appkit-common-react-native@workspace:packages/common": version: 0.0.0-use.local resolution: "@reown/appkit-common-react-native@workspace:packages/common" dependencies: @@ -7203,26 +7203,26 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-core-react-native@npm:1.2.3": - version: 1.2.3 - resolution: "@reown/appkit-core-react-native@npm:1.2.3" +"@reown/appkit-core-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-core-react-native@npm:1.2.4" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:1.2.4" valtio: "npm:1.11.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" "@walletconnect/react-native-compat": ">=2.13.1" react: ">=17" react-native: ">=0.68.5" - checksum: 74dbc78f16d571bceca93d786790094ba1022e68f6dca9090f1a6f5fb7afd8aa7acf90b5cc66fd1598a06071d6cefe30045347011ff2781f77105b1028f8049d + checksum: 6d678d0eb1392f06cc5821a12b45483823c80b203448e5dfaf101a67e62966449ddd38da2b8a1e2aefa799bed672c806b0f58293a3dfdd24b6662af71711c234 languageName: node linkType: hard -"@reown/appkit-core-react-native@npm:2.0.0, @reown/appkit-core-react-native@workspace:packages/core": +"@reown/appkit-core-react-native@npm:2.0.0-alpha.0, @reown/appkit-core-react-native@workspace:packages/core": version: 0.0.0-use.local resolution: "@reown/appkit-core-react-native@workspace:packages/core" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" countries-and-timezones: "npm:3.7.2" valtio: "npm:1.11.2" peerDependencies: @@ -7233,13 +7233,13 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-ethers-react-native@npm:2.0.0, @reown/appkit-ethers-react-native@workspace:packages/ethers": +"@reown/appkit-ethers-react-native@npm:2.0.0-alpha.0, @reown/appkit-ethers-react-native@workspace:packages/ethers": version: 0.0.0-use.local resolution: "@reown/appkit-ethers-react-native@workspace:packages/ethers" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" - "@reown/appkit-react-native": "npm:2.0.0" - "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0-alpha.0" "@reown/appkit-siwe-react-native": "npm:1.2.4" "@walletconnect/ethereum-provider": "npm:2.20.2" peerDependencies: @@ -7256,9 +7256,9 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-ethers5-react-native@workspace:packages/ethers5" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" - "@reown/appkit-scaffold-react-native": "npm:1.2.3" - "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:1.2.4" + "@reown/appkit-scaffold-react-native": "npm:1.2.4" + "@reown/appkit-scaffold-utils-react-native": "npm:1.2.4" "@reown/appkit-siwe-react-native": "npm:1.2.4" "@walletconnect/ethereum-provider": "npm:2.20.2" ethers: "npm:5.7.2" @@ -7282,14 +7282,14 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-react-native@npm:2.0.0, @reown/appkit-react-native@workspace:packages/appkit": +"@reown/appkit-react-native@npm:2.0.0-alpha.0, @reown/appkit-react-native@workspace:packages/appkit": version: 0.0.0-use.local resolution: "@reown/appkit-react-native@workspace:packages/appkit" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" - "@reown/appkit-core-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-core-react-native": "npm:2.0.0-alpha.0" "@reown/appkit-siwe-react-native": "npm:1.2.4" - "@reown/appkit-ui-react-native": "npm:2.0.0" + "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.0" "@walletconnect/universal-provider": "npm:2.20.2" valtio: "npm:^1.13.2" peerDependencies: @@ -7299,19 +7299,19 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-scaffold-react-native@npm:1.2.3": - version: 1.2.3 - resolution: "@reown/appkit-scaffold-react-native@npm:1.2.3" +"@reown/appkit-scaffold-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-scaffold-react-native@npm:1.2.4" dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-core-react-native": "npm:1.2.3" - "@reown/appkit-siwe-react-native": "npm:1.2.3" - "@reown/appkit-ui-react-native": "npm:1.2.3" + "@reown/appkit-common-react-native": "npm:1.2.4" + "@reown/appkit-core-react-native": "npm:1.2.4" + "@reown/appkit-siwe-react-native": "npm:1.2.4" + "@reown/appkit-ui-react-native": "npm:1.2.4" peerDependencies: react: ">=17" react-native: ">=0.68.5" react-native-modal: ">=13" - checksum: a8cd99392bc1b2afa69adf904d9b970bbf707b1aea41d147ff63d884e49a77da3cb6482ffde9f61244196d884c906d99c9c90a164af3146c4e68ad5f4f1bd730 + checksum: 5c1ea20025f393798dd682d002dcc7c832290d64b949bcb71b8a22ef875dd67139df8862028cf4d6333712bd0b434fa0e2cb18179a46e929604596cf64e6bb5d languageName: node linkType: hard @@ -7329,55 +7329,51 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-scaffold-utils-react-native@npm:2.0.0, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": +"@reown/appkit-scaffold-utils-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-scaffold-utils-react-native@npm:1.2.4" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.4" + "@reown/appkit-scaffold-react-native": "npm:1.2.4" + checksum: 74554b052fdda9e113f2c9dd88f54377bc06bc5f56194c49ea1583dba2bac7149a6091957625ad5bed549c67534c972943e931ed03db13a40094093f8f055ff5 + languageName: node + linkType: hard + +"@reown/appkit-scaffold-utils-react-native@npm:2.0.0-alpha.0, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" - "@reown/appkit-core-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-core-react-native": "npm:2.0.0-alpha.0" languageName: unknown linkType: soft -"@reown/appkit-siwe-react-native@npm:1.2.3": - version: 1.2.3 - resolution: "@reown/appkit-siwe-react-native@npm:1.2.3" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.3" - "@reown/appkit-core-react-native": "npm:1.2.3" - "@reown/appkit-ui-react-native": "npm:1.2.3" - valtio: "npm:1.11.2" - peerDependencies: - "@walletconnect/utils": ">=2.16.1" - checksum: 7c4b52d0a3e182586c838bf1dff9165710e13988a834e893eea94f5e765d67743418f55f9faff1747a75d7acc88c41d538527bfbfd16e2d7b2f44b77db07e4f7 - languageName: node - linkType: hard - "@reown/appkit-siwe-react-native@npm:1.2.4, @reown/appkit-siwe-react-native@workspace:packages/siwe": version: 0.0.0-use.local resolution: "@reown/appkit-siwe-react-native@workspace:packages/siwe" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" - "@reown/appkit-core-react-native": "npm:2.0.0" - "@reown/appkit-ui-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:1.2.4" + "@reown/appkit-core-react-native": "npm:1.2.4" + "@reown/appkit-ui-react-native": "npm:1.2.4" valtio: "npm:1.11.2" peerDependencies: "@walletconnect/utils": ">=2.16.1" languageName: unknown linkType: soft -"@reown/appkit-solana-react-native@npm:2.0.0, @reown/appkit-solana-react-native@workspace:packages/solana": +"@reown/appkit-solana-react-native@npm:2.0.0-alpha.0, @reown/appkit-solana-react-native@workspace:packages/solana": version: 0.0.0-use.local resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" bs58: "npm:6.0.0" tweetnacl: "npm:1.0.3" languageName: unknown linkType: soft -"@reown/appkit-ui-react-native@npm:1.2.3": - version: 1.2.3 - resolution: "@reown/appkit-ui-react-native@npm:1.2.3" +"@reown/appkit-ui-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-ui-react-native@npm:1.2.4" dependencies: polished: "npm:4.3.1" qrcode: "npm:1.5.3" @@ -7385,15 +7381,15 @@ __metadata: react: ">=17" react-native: ">=0.68.5" react-native-svg: ">=13" - checksum: 6e38ad8b6882c39084fc2e6eb01cf1662737f9e66046ec2b8a50de5af0c37eafc15c55aee833b9de58425fc7d340ff8ff3490ef23e79cc2cbc881de416a07d83 + checksum: 62987750079871d916656b02998cbbf41bc1b026a3e86f6577e9d3f932751920b7a42e33b9b4d992973cc34abd1228d24ada46e3b878ba151dc8bb82255c772f languageName: node linkType: hard -"@reown/appkit-ui-react-native@npm:2.0.0, @reown/appkit-ui-react-native@workspace:packages/ui": +"@reown/appkit-ui-react-native@npm:2.0.0-alpha.0, @reown/appkit-ui-react-native@workspace:packages/ui": version: 0.0.0-use.local resolution: "@reown/appkit-ui-react-native@workspace:packages/ui" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" polished: "npm:4.3.1" qrcode: "npm:1.5.3" peerDependencies: @@ -7434,13 +7430,13 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-wagmi-react-native@npm:2.0.0, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": +"@reown/appkit-wagmi-react-native@npm:2.0.0-alpha.0, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": version: 0.0.0-use.local resolution: "@reown/appkit-wagmi-react-native@workspace:packages/wagmi" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0" - "@reown/appkit-react-native": "npm:2.0.0" - "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0-alpha.0" "@reown/appkit-siwe-react-native": "npm:1.2.4" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -7458,8 +7454,8 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-wallet-react-native@workspace:packages/wallet" dependencies: - "@reown/appkit-core-react-native": "npm:2.0.0" - "@reown/appkit-ui-react-native": "npm:2.0.0" + "@reown/appkit-core-react-native": "npm:1.2.4" + "@reown/appkit-ui-react-native": "npm:1.2.4" zod: "npm:3.22.4" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" From f39727b4785f1c14affa016f72078f531f728297 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:49:40 -0300 Subject: [PATCH 75/91] chore: add changeset file --- .changeset/brown-snakes-own.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/brown-snakes-own.md diff --git a/.changeset/brown-snakes-own.md b/.changeset/brown-snakes-own.md new file mode 100644 index 00000000..33392658 --- /dev/null +++ b/.changeset/brown-snakes-own.md @@ -0,0 +1,13 @@ +--- +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-bitcoin-react-native': patch +'@reown/appkit-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-solana-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: bump alpha From c2c30c91d1cecb2794a67b2bf8a456a6f163a4de Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:59:17 -0300 Subject: [PATCH 76/91] chore: solve versions --- package.json | 6 - packages/common/src/utils/ConstantsUtil.ts | 2 +- packages/siwe/package.json | 8 +- yarn.lock | 654 +-------------------- 4 files changed, 34 insertions(+), 636 deletions(-) diff --git a/package.json b/package.json index 48051b21..017acadb 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,8 @@ "packages/appkit", "packages/ui", "packages/common", - "packages/wallet", "packages/scaffold-utils", "packages/siwe", - "packages/coinbase-wagmi", - "packages/auth-wagmi", - "packages/auth-ethers", - "packages/coinbase-ethers", - "packages/ethers5", "packages/ethers", "packages/solana", "packages/bitcoin", diff --git a/packages/common/src/utils/ConstantsUtil.ts b/packages/common/src/utils/ConstantsUtil.ts index 809c651e..61388f2f 100644 --- a/packages/common/src/utils/ConstantsUtil.ts +++ b/packages/common/src/utils/ConstantsUtil.ts @@ -1,5 +1,5 @@ export const ConstantsUtil = { - VERSION: '2.0.0', + VERSION: '2.0.0-alpha.0', EIP155: 'eip155', ADD_CHAIN_METHOD: 'wallet_addEthereumChain', diff --git a/packages/siwe/package.json b/packages/siwe/package.json index 14a00bed..3ba8aff1 100644 --- a/packages/siwe/package.json +++ b/packages/siwe/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-siwe-react-native", - "version": "1.2.4", + "version": "2.0.0-alpha.0", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "1.2.4", - "@reown/appkit-core-react-native": "1.2.4", - "@reown/appkit-ui-react-native": "1.2.4", + "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-core-react-native": "2.0.0-alpha.0", + "@reown/appkit-ui-react-native": "2.0.0-alpha.0", "valtio": "1.11.2" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 1c20f277..0cd9badc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4507,408 +4507,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:5.7.0, @ethersproject/abi@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/abi@npm:5.7.0" - dependencies: - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - checksum: 7de51bf52ff03df2526546dacea6e74f15d4c5ef762d931552082b9600dcefd8e333599f02d7906ba89f7b7f48c45ab72cee76f397212b4f17fa9d9ff5615916 - languageName: node - linkType: hard - -"@ethersproject/abstract-provider@npm:5.7.0, @ethersproject/abstract-provider@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/abstract-provider@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/networks": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@ethersproject/web": "npm:^5.7.0" - checksum: a5708e2811b90ddc53d9318ce152511a32dd4771aa2fb59dbe9e90468bb75ca6e695d958bf44d13da684dc3b6aab03f63d425ff7591332cb5d7ddaf68dff7224 - languageName: node - linkType: hard - -"@ethersproject/abstract-signer@npm:5.7.0, @ethersproject/abstract-signer@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/abstract-signer@npm:5.7.0" - dependencies: - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - checksum: e174966b3be17269a5974a3ae5eef6d15ac62ee8c300ceace26767f218f6bbf3de66f29d9a9c9ca300fa8551aab4c92e28d2cc772f5475fdeaa78d9b5be0e745 - languageName: node - linkType: hard - -"@ethersproject/address@npm:5.7.0, @ethersproject/address@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/address@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/rlp": "npm:^5.7.0" - checksum: db5da50abeaae8f6cf17678323e8d01cad697f9a184b0593c62b71b0faa8d7e5c2ba14da78a998d691773ed6a8eb06701f65757218e0eaaeb134e5c5f3e5a908 - languageName: node - linkType: hard - -"@ethersproject/base64@npm:5.7.0, @ethersproject/base64@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/base64@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - checksum: 4f748cd82af60ff1866db699fbf2bf057feff774ea0a30d1f03ea26426f53293ea10cc8265cda1695301da61093bedb8cc0d38887f43ed9dad96b78f19d7337e - languageName: node - linkType: hard - -"@ethersproject/basex@npm:5.7.0, @ethersproject/basex@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/basex@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - checksum: 02304de77477506ad798eb5c68077efd2531624380d770ef4a823e631a288fb680107a0f9dc4a6339b2a0b0f5b06ee77f53429afdad8f950cde0f3e40d30167d - languageName: node - linkType: hard - -"@ethersproject/bignumber@npm:5.7.0, @ethersproject/bignumber@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/bignumber@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - bn.js: "npm:^5.2.1" - checksum: 14263cdc91a7884b141d9300f018f76f69839c47e95718ef7161b11d2c7563163096fee69724c5fa8ef6f536d3e60f1c605819edbc478383a2b98abcde3d37b2 - languageName: node - linkType: hard - -"@ethersproject/bytes@npm:5.7.0, @ethersproject/bytes@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/bytes@npm:5.7.0" - dependencies: - "@ethersproject/logger": "npm:^5.7.0" - checksum: 07dd1f0341b3de584ef26c8696674ff2bb032f4e99073856fc9cd7b4c54d1d846cabe149e864be267934658c3ce799e5ea26babe01f83af0e1f06c51e5ac791f - languageName: node - linkType: hard - -"@ethersproject/constants@npm:5.7.0, @ethersproject/constants@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/constants@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" - checksum: 6df63ab753e152726b84595250ea722165a5744c046e317df40a6401f38556385a37c84dadf5b11ca651c4fb60f967046125369c57ac84829f6b30e69a096273 - languageName: node - linkType: hard - -"@ethersproject/contracts@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/contracts@npm:5.7.0" - dependencies: - "@ethersproject/abi": "npm:^5.7.0" - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - checksum: 97a10361dddaccfb3e9e20e24d071cfa570050adcb964d3452c5f7c9eaaddb4e145ec9cf928e14417948701b89e81d4907800e799a6083123e4d13a576842f41 - languageName: node - linkType: hard - -"@ethersproject/hash@npm:5.7.0, @ethersproject/hash@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/hash@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/base64": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - checksum: 1a631dae34c4cf340dde21d6940dd1715fc7ae483d576f7b8ef9e8cb1d0e30bd7e8d30d4a7d8dc531c14164602323af2c3d51eb2204af18b2e15167e70c9a5ef - languageName: node - linkType: hard - -"@ethersproject/hdnode@npm:5.7.0, @ethersproject/hdnode@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/hdnode@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/basex": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/pbkdf2": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - "@ethersproject/signing-key": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@ethersproject/wordlists": "npm:^5.7.0" - checksum: 36d5c13fe69b1e0a18ea98537bc560d8ba166e012d63faac92522a0b5f405eb67d8848c5aca69e2470f62743aaef2ac36638d9e27fd8c68f51506eb61479d51d - languageName: node - linkType: hard - -"@ethersproject/json-wallets@npm:5.7.0, @ethersproject/json-wallets@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/json-wallets@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/hdnode": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/pbkdf2": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/random": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - aes-js: "npm:3.0.0" - scrypt-js: "npm:3.0.1" - checksum: f1a84d19ff38d3506f453abc4702107cbc96a43c000efcd273a056371363767a06a8d746f84263b1300266eb0c329fe3b49a9b39a37aadd016433faf9e15a4bb - languageName: node - linkType: hard - -"@ethersproject/keccak256@npm:5.7.0, @ethersproject/keccak256@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/keccak256@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - js-sha3: "npm:0.8.0" - checksum: 3b1a91706ff11f5ab5496840b9c36cedca27db443186d28b94847149fd16baecdc13f6fc5efb8359506392f2aba559d07e7f9c1e17a63f9d5de9f8053cfcb033 - languageName: node - linkType: hard - -"@ethersproject/logger@npm:5.7.0, @ethersproject/logger@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/logger@npm:5.7.0" - checksum: d03d460fb2d4a5e71c627b7986fb9e50e1b59a6f55e8b42a545b8b92398b961e7fd294bd9c3d8f92b35d0f6ff9d15aa14c95eab378f8ea194e943c8ace343501 - languageName: node - linkType: hard - -"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": - version: 5.7.1 - resolution: "@ethersproject/networks@npm:5.7.1" - dependencies: - "@ethersproject/logger": "npm:^5.7.0" - checksum: 9efcdce27f150459e85d74af3f72d5c32898823a99f5410e26bf26cca2d21fb14e403377314a93aea248e57fb2964e19cee2c3f7bfc586ceba4c803a8f1b75c0 - languageName: node - linkType: hard - -"@ethersproject/pbkdf2@npm:5.7.0, @ethersproject/pbkdf2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/pbkdf2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - checksum: e5a29cf28b4f4ca1def94d37cfb6a9c05c896106ed64881707813de01c1e7ded613f1e95febcccda4de96aae929068831d72b9d06beef1377b5a1a13a0eb3ff5 - languageName: node - linkType: hard - -"@ethersproject/properties@npm:5.7.0, @ethersproject/properties@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/properties@npm:5.7.0" - dependencies: - "@ethersproject/logger": "npm:^5.7.0" - checksum: 4fe5d36e5550b8e23a305aa236a93e8f04d891d8198eecdc8273914c761b0e198fd6f757877406ee3eb05033ec271132a3e5998c7bd7b9a187964fb4f67b1373 - languageName: node - linkType: hard - -"@ethersproject/providers@npm:5.7.2": - version: 5.7.2 - resolution: "@ethersproject/providers@npm:5.7.2" - dependencies: - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/base64": "npm:^5.7.0" - "@ethersproject/basex": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/networks": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/random": "npm:^5.7.0" - "@ethersproject/rlp": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@ethersproject/web": "npm:^5.7.0" - bech32: "npm:1.1.4" - ws: "npm:7.4.6" - checksum: 4c8d19e6b31f769c24042fb2d02e483a4ee60dcbfca9e3291f0a029b24337c47d1ea719a390be856f8fd02997125819e834415e77da4fb2023369712348dae4c - languageName: node - linkType: hard - -"@ethersproject/random@npm:5.7.0, @ethersproject/random@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/random@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - checksum: 23e572fc55372653c22062f6a153a68c2e2d3200db734cd0d39621fbfd0ca999585bed2d5682e3ac65d87a2893048375682e49d1473d9965631ff56d2808580b - languageName: node - linkType: hard - -"@ethersproject/rlp@npm:5.7.0, @ethersproject/rlp@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/rlp@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - checksum: bc863d21dcf7adf6a99ae75c41c4a3fb99698cfdcfc6d5d82021530f3d3551c6305bc7b6f0475ad6de6f69e91802b7e872bee48c0596d98969aefcf121c2a044 - languageName: node - linkType: hard - -"@ethersproject/sha2@npm:5.7.0, @ethersproject/sha2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/sha2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - hash.js: "npm:1.1.7" - checksum: 0e7f9ce6b1640817b921b9c6dd9dab8d5bf5a0ce7634d6a7d129b7366a576c2f90dcf4bcb15a0aa9310dde67028f3a44e4fcc2f26b565abcd2a0f465116ff3b1 - languageName: node - linkType: hard - -"@ethersproject/signing-key@npm:5.7.0, @ethersproject/signing-key@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/signing-key@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - bn.js: "npm:^5.2.1" - elliptic: "npm:6.5.4" - hash.js: "npm:1.1.7" - checksum: fe2ca55bcdb6e370d81372191d4e04671234a2da872af20b03c34e6e26b97dc07c1ee67e91b673680fb13344c9d5d7eae52f1fa6117733a3d68652b778843e09 - languageName: node - linkType: hard - -"@ethersproject/solidity@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/solidity@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/sha2": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - checksum: bedf9918911144b0ec352b8aa7fa44abf63f0b131629c625672794ee196ba7d3992b0e0d3741935ca176813da25b9bcbc81aec454652c63113bdc3a1706beac6 - languageName: node - linkType: hard - -"@ethersproject/strings@npm:5.7.0, @ethersproject/strings@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/strings@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - checksum: 570d87040ccc7d94de9861f76fc2fba6c0b84c5d6104a99a5c60b8a2401df2e4f24bf9c30afa536163b10a564a109a96f02e6290b80e8f0c610426f56ad704d1 - languageName: node - linkType: hard - -"@ethersproject/transactions@npm:5.7.0, @ethersproject/transactions@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/transactions@npm:5.7.0" - dependencies: - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/rlp": "npm:^5.7.0" - "@ethersproject/signing-key": "npm:^5.7.0" - checksum: aa4d51379caab35b9c468ed1692a23ae47ce0de121890b4f7093c982ee57e30bd2df0c743faed0f44936d7e59c55fffd80479f2c28ec6777b8de06bfb638c239 - languageName: node - linkType: hard - -"@ethersproject/units@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/units@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/constants": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - checksum: 4da2fdefe2a506cc9f8b408b2c8638ab35b843ec413d52713143f08501a55ff67a808897f9a91874774fb526423a0821090ba294f93e8bf4933a57af9677ac5e - languageName: node - linkType: hard - -"@ethersproject/wallet@npm:5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wallet@npm:5.7.0" - dependencies: - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - "@ethersproject/address": "npm:^5.7.0" - "@ethersproject/bignumber": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/hdnode": "npm:^5.7.0" - "@ethersproject/json-wallets": "npm:^5.7.0" - "@ethersproject/keccak256": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/random": "npm:^5.7.0" - "@ethersproject/signing-key": "npm:^5.7.0" - "@ethersproject/transactions": "npm:^5.7.0" - "@ethersproject/wordlists": "npm:^5.7.0" - checksum: f872b957db46f9de247d39a398538622b6c7a12f93d69bec5f47f9abf0701ef1edc10497924dd1c14a68109284c39a1686fa85586d89b3ee65df49002c40ba4c - languageName: node - linkType: hard - -"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": - version: 5.7.1 - resolution: "@ethersproject/web@npm:5.7.1" - dependencies: - "@ethersproject/base64": "npm:^5.7.0" - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - checksum: c82d6745c7f133980e8dab203955260e07da22fa544ccafdd0f21c79fae127bd6ef30957319e37b1cc80cddeb04d6bfb60f291bb14a97c9093d81ce50672f453 - languageName: node - linkType: hard - -"@ethersproject/wordlists@npm:5.7.0, @ethersproject/wordlists@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wordlists@npm:5.7.0" - dependencies: - "@ethersproject/bytes": "npm:^5.7.0" - "@ethersproject/hash": "npm:^5.7.0" - "@ethersproject/logger": "npm:^5.7.0" - "@ethersproject/properties": "npm:^5.7.0" - "@ethersproject/strings": "npm:^5.7.0" - checksum: da4f3eca6d691ebf4f578e6b2ec3a76dedba791be558f6cf7e10cd0bfbaeab5a6753164201bb72ced745fb02b6ef7ef34edcb7e6065ce2b624c6556a461c3f70 - languageName: node - linkType: hard - "@expo/bunyan@npm:^4.0.0": version: 4.0.0 resolution: "@expo/bunyan@npm:4.0.0" @@ -7107,28 +6705,18 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-auth-ethers-react-native@workspace:packages/auth-ethers": - version: 0.0.0-use.local - resolution: "@reown/appkit-auth-ethers-react-native@workspace:packages/auth-ethers" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.4" - "@reown/appkit-wallet-react-native": "npm:1.2.4" - peerDependencies: - ethers: ">=5" - languageName: unknown - linkType: soft - -"@reown/appkit-auth-wagmi-react-native@npm:1.2.4, @reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi": - version: 0.0.0-use.local - resolution: "@reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi" +"@reown/appkit-auth-wagmi-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-auth-wagmi-react-native@npm:1.2.4" dependencies: "@reown/appkit-common-react-native": "npm:1.2.4" "@reown/appkit-core-react-native": "npm:1.2.4" "@reown/appkit-wallet-react-native": "npm:1.2.4" peerDependencies: wagmi: ">=2" - languageName: unknown - linkType: soft + checksum: d21527699be64a3e67e91514fb5fe45b27c1415f8b045f8dee28803ced65d8c8ee43cb69c55349e92032fbb42f13d304633582f7a1e89cf6332e393a5e758a8d + languageName: node + linkType: hard "@reown/appkit-bitcoin-react-native@npm:2.0.0-alpha.0, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": version: 0.0.0-use.local @@ -7138,28 +6726,6 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers": - version: 0.0.0-use.local - resolution: "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.4" - peerDependencies: - "@coinbase/wallet-mobile-sdk": ">=1.0.10" - ethers: ">=5" - languageName: unknown - linkType: soft - -"@reown/appkit-coinbase-wagmi-react-native@workspace:packages/coinbase-wagmi": - version: 0.0.0-use.local - resolution: "@reown/appkit-coinbase-wagmi-react-native@workspace:packages/coinbase-wagmi" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.4" - peerDependencies: - "@coinbase/wallet-mobile-sdk": ">=1.0.10" - wagmi: ">=2" - languageName: unknown - linkType: soft - "@reown/appkit-common-react-native@npm:1.2.4": version: 1.2.4 resolution: "@reown/appkit-common-react-native@npm:1.2.4" @@ -7252,27 +6818,6 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-ethers5-react-native@workspace:packages/ethers5": - version: 0.0.0-use.local - resolution: "@reown/appkit-ethers5-react-native@workspace:packages/ethers5" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.4" - "@reown/appkit-scaffold-react-native": "npm:1.2.4" - "@reown/appkit-scaffold-utils-react-native": "npm:1.2.4" - "@reown/appkit-siwe-react-native": "npm:1.2.4" - "@walletconnect/ethereum-provider": "npm:2.20.2" - ethers: "npm:5.7.2" - peerDependencies: - "@react-native-async-storage/async-storage": ">=1.17.0" - "@react-native-community/netinfo": "*" - "@walletconnect/react-native-compat": ">=2.13.1" - ethers: ">=5.7.2 <6.0.0" - react: ">=17" - react-native: ">=0.68.5" - react-native-get-random-values: "*" - languageName: unknown - linkType: soft - "@reown/appkit-polyfills@npm:1.7.3": version: 1.7.3 resolution: "@reown/appkit-polyfills@npm:1.7.3" @@ -7299,22 +6844,6 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-scaffold-react-native@npm:1.2.4": - version: 1.2.4 - resolution: "@reown/appkit-scaffold-react-native@npm:1.2.4" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.4" - "@reown/appkit-core-react-native": "npm:1.2.4" - "@reown/appkit-siwe-react-native": "npm:1.2.4" - "@reown/appkit-ui-react-native": "npm:1.2.4" - peerDependencies: - react: ">=17" - react-native: ">=0.68.5" - react-native-modal: ">=13" - checksum: 5c1ea20025f393798dd682d002dcc7c832290d64b949bcb71b8a22ef875dd67139df8862028cf4d6333712bd0b434fa0e2cb18179a46e929604596cf64e6bb5d - languageName: node - linkType: hard - "@reown/appkit-scaffold-ui@npm:1.7.3": version: 1.7.3 resolution: "@reown/appkit-scaffold-ui@npm:1.7.3" @@ -7329,16 +6858,6 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-scaffold-utils-react-native@npm:1.2.4": - version: 1.2.4 - resolution: "@reown/appkit-scaffold-utils-react-native@npm:1.2.4" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.4" - "@reown/appkit-scaffold-react-native": "npm:1.2.4" - checksum: 74554b052fdda9e113f2c9dd88f54377bc06bc5f56194c49ea1583dba2bac7149a6091957625ad5bed549c67534c972943e931ed03db13a40094093f8f055ff5 - languageName: node - linkType: hard - "@reown/appkit-scaffold-utils-react-native@npm:2.0.0-alpha.0, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" @@ -7348,9 +6867,9 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-siwe-react-native@npm:1.2.4, @reown/appkit-siwe-react-native@workspace:packages/siwe": - version: 0.0.0-use.local - resolution: "@reown/appkit-siwe-react-native@workspace:packages/siwe" +"@reown/appkit-siwe-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-siwe-react-native@npm:1.2.4" dependencies: "@reown/appkit-common-react-native": "npm:1.2.4" "@reown/appkit-core-react-native": "npm:1.2.4" @@ -7358,6 +6877,20 @@ __metadata: valtio: "npm:1.11.2" peerDependencies: "@walletconnect/utils": ">=2.16.1" + checksum: c87cad0009de3bd17a65c9bd2fc7685e02c5e3b9b3a974492bcfeb175c641a6de440a8943c812c1cacecb3265a92fc83a63d3ea67fea718af4c6f51b366f98ce + languageName: node + linkType: hard + +"@reown/appkit-siwe-react-native@workspace:packages/siwe": + version: 0.0.0-use.local + resolution: "@reown/appkit-siwe-react-native@workspace:packages/siwe" + dependencies: + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-core-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.0" + valtio: "npm:1.11.2" + peerDependencies: + "@walletconnect/utils": ">=2.16.1" languageName: unknown linkType: soft @@ -7450,9 +6983,9 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-wallet-react-native@npm:1.2.4, @reown/appkit-wallet-react-native@workspace:packages/wallet": - version: 0.0.0-use.local - resolution: "@reown/appkit-wallet-react-native@workspace:packages/wallet" +"@reown/appkit-wallet-react-native@npm:1.2.4": + version: 1.2.4 + resolution: "@reown/appkit-wallet-react-native@npm:1.2.4" dependencies: "@reown/appkit-core-react-native": "npm:1.2.4" "@reown/appkit-ui-react-native": "npm:1.2.4" @@ -7460,8 +6993,9 @@ __metadata: peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" react-native-webview: ">=11" - languageName: unknown - linkType: soft + checksum: debf13a78ab507b7855cbb7e73fbf91c6ed51e900fff29f8266c9e2bcf824c43245dd1c8b87fc8c7b13a94dc12818d94bbd93fc16fd683aa2513641754b599c6 + languageName: node + linkType: hard "@reown/appkit-wallet@npm:1.7.3": version: 1.7.3 @@ -9985,13 +9519,6 @@ __metadata: languageName: node linkType: hard -"aes-js@npm:3.0.0": - version: 3.0.0 - resolution: "aes-js@npm:3.0.0" - checksum: 87dd5b2363534b867db7cef8bc85a90c355460783744877b2db7c8be09740aac5750714f9e00902822f692662bda74cdf40e03fbb5214ffec75c2666666288b8 - languageName: node - linkType: hard - "aes-js@npm:4.0.0-beta.5": version: 4.0.0-beta.5 resolution: "aes-js@npm:4.0.0-beta.5" @@ -10843,13 +10370,6 @@ __metadata: languageName: node linkType: hard -"bech32@npm:1.1.4": - version: 1.1.4 - resolution: "bech32@npm:1.1.4" - checksum: 5f62ca47b8df99ace9c0e0d8deb36a919d91bf40066700aaa9920a45f86bb10eb56d537d559416fd8703aa0fb60dddb642e58f049701e7291df678b2033e5ee5 - languageName: node - linkType: hard - "bech32@npm:^2.0.0": version: 2.0.0 resolution: "bech32@npm:2.0.0" @@ -10946,13 +10466,6 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^4.11.9": - version: 4.12.0 - resolution: "bn.js@npm:4.12.0" - checksum: 9736aaa317421b6b3ed038ff3d4491935a01419ac2d83ddcfebc5717385295fcfcf0c57311d90fe49926d0abbd7a9dbefdd8861e6129939177f7e67ebc645b21 - languageName: node - linkType: hard - "bn.js@npm:^5.2.0": version: 5.2.2 resolution: "bn.js@npm:5.2.2" @@ -11078,13 +10591,6 @@ __metadata: languageName: node linkType: hard -"brorand@npm:^1.1.0": - version: 1.1.0 - resolution: "brorand@npm:1.1.0" - checksum: 6f366d7c4990f82c366e3878492ba9a372a73163c09871e80d82fb4ae0d23f9f8924cb8a662330308206e6b3b76ba1d528b4601c9ef73c2166b440b2ea3b7571 - languageName: node - linkType: hard - "browser-assert@npm:^1.2.1": version: 1.2.1 resolution: "browser-assert@npm:1.2.1" @@ -12926,21 +12432,6 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.6.1": - version: 6.6.1 - resolution: "elliptic@npm:6.6.1" - dependencies: - bn.js: "npm:^4.11.9" - brorand: "npm:^1.1.0" - hash.js: "npm:^1.0.0" - hmac-drbg: "npm:^1.0.1" - inherits: "npm:^2.0.4" - minimalistic-assert: "npm:^1.0.1" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 8b24ef782eec8b472053793ea1e91ae6bee41afffdfcb78a81c0a53b191e715cbe1292aa07165958a9bbe675bd0955142560b1a007ffce7d6c765bcaf951a867 - languageName: node - linkType: hard - "email-addresses@npm:^5.0.0": version: 5.0.0 resolution: "email-addresses@npm:5.0.0" @@ -13793,44 +13284,6 @@ __metadata: languageName: node linkType: hard -"ethers@npm:5.7.2": - version: 5.7.2 - resolution: "ethers@npm:5.7.2" - dependencies: - "@ethersproject/abi": "npm:5.7.0" - "@ethersproject/abstract-provider": "npm:5.7.0" - "@ethersproject/abstract-signer": "npm:5.7.0" - "@ethersproject/address": "npm:5.7.0" - "@ethersproject/base64": "npm:5.7.0" - "@ethersproject/basex": "npm:5.7.0" - "@ethersproject/bignumber": "npm:5.7.0" - "@ethersproject/bytes": "npm:5.7.0" - "@ethersproject/constants": "npm:5.7.0" - "@ethersproject/contracts": "npm:5.7.0" - "@ethersproject/hash": "npm:5.7.0" - "@ethersproject/hdnode": "npm:5.7.0" - "@ethersproject/json-wallets": "npm:5.7.0" - "@ethersproject/keccak256": "npm:5.7.0" - "@ethersproject/logger": "npm:5.7.0" - "@ethersproject/networks": "npm:5.7.1" - "@ethersproject/pbkdf2": "npm:5.7.0" - "@ethersproject/properties": "npm:5.7.0" - "@ethersproject/providers": "npm:5.7.2" - "@ethersproject/random": "npm:5.7.0" - "@ethersproject/rlp": "npm:5.7.0" - "@ethersproject/sha2": "npm:5.7.0" - "@ethersproject/signing-key": "npm:5.7.0" - "@ethersproject/solidity": "npm:5.7.0" - "@ethersproject/strings": "npm:5.7.0" - "@ethersproject/transactions": "npm:5.7.0" - "@ethersproject/units": "npm:5.7.0" - "@ethersproject/wallet": "npm:5.7.0" - "@ethersproject/web": "npm:5.7.1" - "@ethersproject/wordlists": "npm:5.7.0" - checksum: 90629a4cdb88cde7a7694f5610a83eb00d7fbbaea687446b15631397988f591c554dd68dfa752ddf00aabefd6285e5b298be44187e960f5e4962684e10b39962 - languageName: node - linkType: hard - "ethers@npm:6.13.5": version: 6.13.5 resolution: "ethers@npm:6.13.5" @@ -15318,16 +14771,6 @@ __metadata: languageName: node linkType: hard -"hash.js@npm:1.1.7, hash.js@npm:^1.0.0, hash.js@npm:^1.0.3": - version: 1.1.7 - resolution: "hash.js@npm:1.1.7" - dependencies: - inherits: "npm:^2.0.3" - minimalistic-assert: "npm:^1.0.1" - checksum: 41ada59494eac5332cfc1ce6b7ebdd7b88a3864a6d6b08a3ea8ef261332ed60f37f10877e0c825aaa4bddebf164fbffa618286aeeec5296675e2671cbfa746c4 - languageName: node - linkType: hard - "hasown@npm:^2.0.0": version: 2.0.2 resolution: "hasown@npm:2.0.2" @@ -15446,17 +14889,6 @@ __metadata: languageName: node linkType: hard -"hmac-drbg@npm:^1.0.1": - version: 1.0.1 - resolution: "hmac-drbg@npm:1.0.1" - dependencies: - hash.js: "npm:^1.0.3" - minimalistic-assert: "npm:^1.0.0" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: f3d9ba31b40257a573f162176ac5930109816036c59a09f901eb2ffd7e5e705c6832bedfff507957125f2086a0ab8f853c0df225642a88bf1fcaea945f20600d - languageName: node - linkType: hard - "hosted-git-info@npm:^7.0.0": version: 7.0.2 resolution: "hosted-git-info@npm:7.0.2" @@ -17005,13 +16437,6 @@ __metadata: languageName: node linkType: hard -"js-sha3@npm:0.8.0": - version: 0.8.0 - resolution: "js-sha3@npm:0.8.0" - checksum: 43a21dc7967c871bd2c46cb1c2ae97441a97169f324e509f382d43330d8f75cf2c96dba7c806ab08a425765a9c847efdd4bffbac2d99c3a4f3de6c0218f40533 - languageName: node - linkType: hard - "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -18478,20 +17903,6 @@ __metadata: languageName: node linkType: hard -"minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": - version: 1.0.1 - resolution: "minimalistic-assert@npm:1.0.1" - checksum: 96730e5601cd31457f81a296f521eb56036e6f69133c0b18c13fe941109d53ad23a4204d946a0d638d7f3099482a0cec8c9bb6d642604612ce43ee536be3dddd - languageName: node - linkType: hard - -"minimalistic-crypto-utils@npm:^1.0.1": - version: 1.0.1 - resolution: "minimalistic-crypto-utils@npm:1.0.1" - checksum: 790ecec8c5c73973a4fbf2c663d911033e8494d5fb0960a4500634766ab05d6107d20af896ca2132e7031741f19888154d44b2408ada0852446705441383e9f8 - languageName: node - linkType: hard - "minimatch@npm:2 || 3, minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -21542,13 +20953,6 @@ __metadata: languageName: node linkType: hard -"scrypt-js@npm:3.0.1": - version: 3.0.1 - resolution: "scrypt-js@npm:3.0.1" - checksum: e2941e1c8b5c84c7f3732b0153fee624f5329fc4e772a06270ee337d4d2df4174b8abb5e6ad53804a29f53890ecbc78f3775a319323568c0313040c0e55f5b10 - languageName: node - linkType: hard - "selfsigned@npm:^2.4.1": version: 2.4.1 resolution: "selfsigned@npm:2.4.1" From 815f47cc160712912917fb26e0f24083aa6043b9 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:23:36 -0300 Subject: [PATCH 77/91] chore: bump version --- .prettierignore | 8 ++ apps/gallery/package.json | 2 +- apps/native/package.json | 10 +-- package.json | 2 +- packages/appkit/CHANGELOG.md | 11 +++ packages/appkit/package.json | 10 +-- packages/bitcoin/CHANGELOG.md | 8 ++ packages/bitcoin/package.json | 4 +- packages/common/CHANGELOG.md | 6 ++ packages/common/package.json | 2 +- packages/common/src/utils/ConstantsUtil.ts | 2 +- packages/core/CHANGELOG.md | 8 ++ packages/core/package.json | 4 +- packages/ethers/CHANGELOG.md | 11 +++ packages/ethers/package.json | 10 +-- packages/scaffold-utils/CHANGELOG.md | 9 +++ packages/scaffold-utils/package.json | 6 +- packages/siwe/CHANGELOG.md | 9 +++ packages/siwe/package.json | 8 +- packages/solana/CHANGELOG.md | 8 ++ packages/solana/package.json | 4 +- packages/ui/CHANGELOG.md | 8 ++ packages/ui/package.json | 4 +- packages/wagmi/CHANGELOG.md | 11 +++ packages/wagmi/package.json | 10 +-- yarn.lock | 88 +++++++++------------- 26 files changed, 173 insertions(+), 90 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..d02f9259 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +.changeset/ +.github/ +.maestro/ +.turbo/ +.vscode/ +.yarn/ +.yarnrc.yml +.yarn.lock \ No newline at end of file diff --git a/apps/gallery/package.json b/apps/gallery/package.json index 8c9d5270..d1374890 100644 --- a/apps/gallery/package.json +++ b/apps/gallery/package.json @@ -36,7 +36,7 @@ "build:gallery": "storybook build -o out" }, "dependencies": { - "@reown/appkit-ui-react-native": "2.0.0-alpha.0", + "@reown/appkit-ui-react-native": "2.0.0-alpha.1", "@storybook/theming": "^8.3.0" } } diff --git a/apps/native/package.json b/apps/native/package.json index d801ce9b..c3302938 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -24,11 +24,11 @@ "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", "@reown/appkit-auth-wagmi-react-native": "1.2.4", - "@reown/appkit-bitcoin-react-native": "2.0.0-alpha.0", - "@reown/appkit-ethers-react-native": "2.0.0-alpha.0", - "@reown/appkit-react-native": "2.0.0-alpha.0", - "@reown/appkit-solana-react-native": "2.0.0-alpha.0", - "@reown/appkit-wagmi-react-native": "2.0.0-alpha.0", + "@reown/appkit-bitcoin-react-native": "2.0.0-alpha.1", + "@reown/appkit-ethers-react-native": "2.0.0-alpha.1", + "@reown/appkit-react-native": "2.0.0-alpha.1", + "@reown/appkit-solana-react-native": "2.0.0-alpha.1", + "@reown/appkit-wagmi-react-native": "2.0.0-alpha.1", "@solana/web3.js": "^1.98.2", "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", diff --git a/package.json b/package.json index 017acadb..34d43fde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "appkit-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "private": true, "workspaces": [ "packages/core", diff --git a/packages/appkit/CHANGELOG.md b/packages/appkit/CHANGELOG.md index b169c3ed..8a21837a 100644 --- a/packages/appkit/CHANGELOG.md +++ b/packages/appkit/CHANGELOG.md @@ -1,5 +1,16 @@ # @reown/appkit-scaffold-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-common-react-native@2.0.0-alpha.1 + - @reown/appkit-core-react-native@2.0.0-alpha.1 + - @reown/appkit-ui-react-native@2.0.0-alpha.1 + - @reown/appkit-siwe-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/appkit/package.json b/packages/appkit/package.json index 60c3d4b4..7b34211f 100644 --- a/packages/appkit/package.json +++ b/packages/appkit/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -37,10 +37,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", - "@reown/appkit-core-react-native": "2.0.0-alpha.0", - "@reown/appkit-siwe-react-native": "1.2.4", - "@reown/appkit-ui-react-native": "2.0.0-alpha.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.1", + "@reown/appkit-core-react-native": "2.0.0-alpha.1", + "@reown/appkit-siwe-react-native": "2.0.0-alpha.1", + "@reown/appkit-ui-react-native": "2.0.0-alpha.1", "@walletconnect/universal-provider": "2.20.2", "valtio": "^1.13.2" }, diff --git a/packages/bitcoin/CHANGELOG.md b/packages/bitcoin/CHANGELOG.md index 8211e922..f84eb65a 100644 --- a/packages/bitcoin/CHANGELOG.md +++ b/packages/bitcoin/CHANGELOG.md @@ -1,5 +1,13 @@ # @reown/appkit-bitcoin-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-common-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json index e933db0a..322a1cd3 100644 --- a/packages/bitcoin/package.json +++ b/packages/bitcoin/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-bitcoin-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,6 +39,6 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0" + "@reown/appkit-common-react-native": "2.0.0-alpha.1" } } diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 854f41c9..c9fa45aa 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -1,5 +1,11 @@ # @reown/appkit-common-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha + ## 2.0.0 ### Major Changes diff --git a/packages/common/package.json b/packages/common/package.json index 5c53b4be..7e32047a 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-common-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", diff --git a/packages/common/src/utils/ConstantsUtil.ts b/packages/common/src/utils/ConstantsUtil.ts index 61388f2f..742fa391 100644 --- a/packages/common/src/utils/ConstantsUtil.ts +++ b/packages/common/src/utils/ConstantsUtil.ts @@ -1,5 +1,5 @@ export const ConstantsUtil = { - VERSION: '2.0.0-alpha.0', + VERSION: '2.0.0-alpha.1', EIP155: 'eip155', ADD_CHAIN_METHOD: 'wallet_addEthereumChain', diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 0c9af542..ac87f07d 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,13 @@ # @reown/appkit-core-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-common-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/core/package.json b/packages/core/package.json index 08c4b31f..52483a2f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-core-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,7 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.1", "countries-and-timezones": "3.7.2", "valtio": "1.11.2" }, diff --git a/packages/ethers/CHANGELOG.md b/packages/ethers/CHANGELOG.md index 8f040b98..dc371ccb 100644 --- a/packages/ethers/CHANGELOG.md +++ b/packages/ethers/CHANGELOG.md @@ -1,5 +1,16 @@ # @reown/appkit-ethers-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-scaffold-utils-react-native@2.0.0-alpha.1 + - @reown/appkit-react-native@2.0.0-alpha.1 + - @reown/appkit-common-react-native@2.0.0-alpha.1 + - @reown/appkit-siwe-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/ethers/package.json b/packages/ethers/package.json index b1fe06ad..f88d7c9f 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-ethers-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,10 +38,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", - "@reown/appkit-react-native": "2.0.0-alpha.0", - "@reown/appkit-scaffold-utils-react-native": "2.0.0-alpha.0", - "@reown/appkit-siwe-react-native": "1.2.4", + "@reown/appkit-common-react-native": "2.0.0-alpha.1", + "@reown/appkit-react-native": "2.0.0-alpha.1", + "@reown/appkit-scaffold-utils-react-native": "2.0.0-alpha.1", + "@reown/appkit-siwe-react-native": "2.0.0-alpha.1", "@walletconnect/ethereum-provider": "2.20.2" }, "peerDependencies": { diff --git a/packages/scaffold-utils/CHANGELOG.md b/packages/scaffold-utils/CHANGELOG.md index 87c52ee7..5dca8abd 100644 --- a/packages/scaffold-utils/CHANGELOG.md +++ b/packages/scaffold-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @reown/appkit-scaffold-utils-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-common-react-native@2.0.0-alpha.1 + - @reown/appkit-core-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index b5d872a6..33d71f9c 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-scaffold-utils-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -35,8 +35,8 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", - "@reown/appkit-core-react-native": "2.0.0-alpha.0" + "@reown/appkit-common-react-native": "2.0.0-alpha.1", + "@reown/appkit-core-react-native": "2.0.0-alpha.1" }, "react-native": "src/index.ts", "react-native-builder-bob": { diff --git a/packages/siwe/CHANGELOG.md b/packages/siwe/CHANGELOG.md index b10db18b..ea38d5d1 100644 --- a/packages/siwe/CHANGELOG.md +++ b/packages/siwe/CHANGELOG.md @@ -1,5 +1,14 @@ # @reown/appkit-siwe-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- Updated dependencies [f39727b] + - @reown/appkit-common-react-native@2.0.0-alpha.1 + - @reown/appkit-core-react-native@2.0.0-alpha.1 + - @reown/appkit-ui-react-native@2.0.0-alpha.1 + ## 1.2.4 ### Patch Changes diff --git a/packages/siwe/package.json b/packages/siwe/package.json index 3ba8aff1..d3f89181 100644 --- a/packages/siwe/package.json +++ b/packages/siwe/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-siwe-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", - "@reown/appkit-core-react-native": "2.0.0-alpha.0", - "@reown/appkit-ui-react-native": "2.0.0-alpha.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.1", + "@reown/appkit-core-react-native": "2.0.0-alpha.1", + "@reown/appkit-ui-react-native": "2.0.0-alpha.1", "valtio": "1.11.2" }, "peerDependencies": { diff --git a/packages/solana/CHANGELOG.md b/packages/solana/CHANGELOG.md index e362d275..64fda6ea 100644 --- a/packages/solana/CHANGELOG.md +++ b/packages/solana/CHANGELOG.md @@ -1,5 +1,13 @@ # @reown/appkit-solana-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-common-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/solana/package.json b/packages/solana/package.json index 3da37c28..1365cc4d 100644 --- a/packages/solana/package.json +++ b/packages/solana/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-solana-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.1", "bs58": "6.0.0", "tweetnacl": "1.0.3" } diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index cbe125fe..eea0f2be 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,13 @@ # @reown/appkit-ui-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-common-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index b052e5a3..b0a8d9cc 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-ui-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -38,7 +38,7 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", + "@reown/appkit-common-react-native": "2.0.0-alpha.1", "polished": "4.3.1", "qrcode": "1.5.3" }, diff --git a/packages/wagmi/CHANGELOG.md b/packages/wagmi/CHANGELOG.md index fe41f3a7..7e7ef3a9 100644 --- a/packages/wagmi/CHANGELOG.md +++ b/packages/wagmi/CHANGELOG.md @@ -1,5 +1,16 @@ # @reown/appkit-wagmi-react-native +## 2.0.0-alpha.1 + +### Patch Changes + +- f39727b: chore: bump alpha +- Updated dependencies [f39727b] + - @reown/appkit-scaffold-utils-react-native@2.0.0-alpha.1 + - @reown/appkit-react-native@2.0.0-alpha.1 + - @reown/appkit-common-react-native@2.0.0-alpha.1 + - @reown/appkit-siwe-react-native@2.0.0-alpha.1 + ## 2.0.0 ### Major Changes diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index 5e842c4a..4274f53e 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -1,6 +1,6 @@ { "name": "@reown/appkit-wagmi-react-native", - "version": "2.0.0-alpha.0", + "version": "2.0.0-alpha.1", "main": "lib/commonjs/index.js", "types": "lib/typescript/index.d.ts", "module": "lib/module/index.js", @@ -39,10 +39,10 @@ "access": "public" }, "dependencies": { - "@reown/appkit-common-react-native": "2.0.0-alpha.0", - "@reown/appkit-react-native": "2.0.0-alpha.0", - "@reown/appkit-scaffold-utils-react-native": "2.0.0-alpha.0", - "@reown/appkit-siwe-react-native": "1.2.4" + "@reown/appkit-common-react-native": "2.0.0-alpha.1", + "@reown/appkit-react-native": "2.0.0-alpha.1", + "@reown/appkit-scaffold-utils-react-native": "2.0.0-alpha.1", + "@reown/appkit-siwe-react-native": "2.0.0-alpha.1" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/yarn.lock b/yarn.lock index 0cd9badc..51a1abaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -71,7 +71,7 @@ __metadata: "@babel/preset-react": "npm:^7.22.5" "@babel/preset-typescript": "npm:7.24.7" "@chromatic-com/storybook": "npm:^1" - "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.1" "@storybook/addon-essentials": "npm:^8.3.0" "@storybook/addon-interactions": "npm:^8.3.0" "@storybook/addon-links": "npm:^8.3.0" @@ -108,11 +108,11 @@ __metadata: "@react-native-async-storage/async-storage": "npm:2.1.2" "@react-native-community/netinfo": "npm:11.4.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.2.4" - "@reown/appkit-bitcoin-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-ethers-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-solana-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-wagmi-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-bitcoin-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-ethers-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-solana-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-wagmi-react-native": "npm:2.0.0-alpha.1" "@solana/web3.js": "npm:^1.98.2" "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" @@ -6718,11 +6718,11 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-bitcoin-react-native@npm:2.0.0-alpha.0, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": +"@reown/appkit-bitcoin-react-native@npm:2.0.0-alpha.1, @reown/appkit-bitcoin-react-native@workspace:packages/bitcoin": version: 0.0.0-use.local resolution: "@reown/appkit-bitcoin-react-native@workspace:packages/bitcoin" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" languageName: unknown linkType: soft @@ -6736,7 +6736,7 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-common-react-native@npm:2.0.0-alpha.0, @reown/appkit-common-react-native@workspace:packages/common": +"@reown/appkit-common-react-native@npm:2.0.0-alpha.1, @reown/appkit-common-react-native@workspace:packages/common": version: 0.0.0-use.local resolution: "@reown/appkit-common-react-native@workspace:packages/common" dependencies: @@ -6784,11 +6784,11 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-core-react-native@npm:2.0.0-alpha.0, @reown/appkit-core-react-native@workspace:packages/core": +"@reown/appkit-core-react-native@npm:2.0.0-alpha.1, @reown/appkit-core-react-native@workspace:packages/core": version: 0.0.0-use.local resolution: "@reown/appkit-core-react-native@workspace:packages/core" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" countries-and-timezones: "npm:3.7.2" valtio: "npm:1.11.2" peerDependencies: @@ -6799,14 +6799,14 @@ __metadata: languageName: unknown linkType: soft -"@reown/appkit-ethers-react-native@npm:2.0.0-alpha.0, @reown/appkit-ethers-react-native@workspace:packages/ethers": +"@reown/appkit-ethers-react-native@npm:2.0.0-alpha.1, @reown/appkit-ethers-react-native@workspace:packages/ethers": version: 0.0.0-use.local resolution: "@reown/appkit-ethers-react-native@workspace:packages/ethers" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-siwe-react-native": "npm:1.2.4" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-siwe-react-native": "npm:2.0.0-alpha.1" "@walletconnect/ethereum-provider": "npm:2.20.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -6827,14 +6827,14 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-react-native@npm:2.0.0-alpha.0, @reown/appkit-react-native@workspace:packages/appkit": +"@reown/appkit-react-native@npm:2.0.0-alpha.1, @reown/appkit-react-native@workspace:packages/appkit": version: 0.0.0-use.local resolution: "@reown/appkit-react-native@workspace:packages/appkit" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-core-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-siwe-react-native": "npm:1.2.4" - "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-core-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-siwe-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.1" "@walletconnect/universal-provider": "npm:2.20.2" valtio: "npm:^1.13.2" peerDependencies: @@ -6858,47 +6858,33 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-scaffold-utils-react-native@npm:2.0.0-alpha.0, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": +"@reown/appkit-scaffold-utils-react-native@npm:2.0.0-alpha.1, @reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils": version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-core-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-core-react-native": "npm:2.0.0-alpha.1" languageName: unknown linkType: soft -"@reown/appkit-siwe-react-native@npm:1.2.4": - version: 1.2.4 - resolution: "@reown/appkit-siwe-react-native@npm:1.2.4" - dependencies: - "@reown/appkit-common-react-native": "npm:1.2.4" - "@reown/appkit-core-react-native": "npm:1.2.4" - "@reown/appkit-ui-react-native": "npm:1.2.4" - valtio: "npm:1.11.2" - peerDependencies: - "@walletconnect/utils": ">=2.16.1" - checksum: c87cad0009de3bd17a65c9bd2fc7685e02c5e3b9b3a974492bcfeb175c641a6de440a8943c812c1cacecb3265a92fc83a63d3ea67fea718af4c6f51b366f98ce - languageName: node - linkType: hard - -"@reown/appkit-siwe-react-native@workspace:packages/siwe": +"@reown/appkit-siwe-react-native@npm:2.0.0-alpha.1, @reown/appkit-siwe-react-native@workspace:packages/siwe": version: 0.0.0-use.local resolution: "@reown/appkit-siwe-react-native@workspace:packages/siwe" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-core-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-core-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-ui-react-native": "npm:2.0.0-alpha.1" valtio: "npm:1.11.2" peerDependencies: "@walletconnect/utils": ">=2.16.1" languageName: unknown linkType: soft -"@reown/appkit-solana-react-native@npm:2.0.0-alpha.0, @reown/appkit-solana-react-native@workspace:packages/solana": +"@reown/appkit-solana-react-native@npm:2.0.0-alpha.1, @reown/appkit-solana-react-native@workspace:packages/solana": version: 0.0.0-use.local resolution: "@reown/appkit-solana-react-native@workspace:packages/solana" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" bs58: "npm:6.0.0" tweetnacl: "npm:1.0.3" languageName: unknown @@ -6918,11 +6904,11 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-ui-react-native@npm:2.0.0-alpha.0, @reown/appkit-ui-react-native@workspace:packages/ui": +"@reown/appkit-ui-react-native@npm:2.0.0-alpha.1, @reown/appkit-ui-react-native@workspace:packages/ui": version: 0.0.0-use.local resolution: "@reown/appkit-ui-react-native@workspace:packages/ui" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" polished: "npm:4.3.1" qrcode: "npm:1.5.3" peerDependencies: @@ -6963,14 +6949,14 @@ __metadata: languageName: node linkType: hard -"@reown/appkit-wagmi-react-native@npm:2.0.0-alpha.0, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": +"@reown/appkit-wagmi-react-native@npm:2.0.0-alpha.1, @reown/appkit-wagmi-react-native@workspace:packages/wagmi": version: 0.0.0-use.local resolution: "@reown/appkit-wagmi-react-native@workspace:packages/wagmi" dependencies: - "@reown/appkit-common-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0-alpha.0" - "@reown/appkit-siwe-react-native": "npm:1.2.4" + "@reown/appkit-common-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-scaffold-utils-react-native": "npm:2.0.0-alpha.1" + "@reown/appkit-siwe-react-native": "npm:2.0.0-alpha.1" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" "@react-native-community/netinfo": "*" From 13fc85524da3cbb31dbfc8e1e8c00b2cdc0f48c8 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 19 Jun 2025 10:27:42 -0300 Subject: [PATCH 78/91] chore: enabled siwe --- apps/native/App.tsx | 2 + packages/appkit/src/AppKit.ts | 45 +++++++++++- packages/appkit/src/modal/w3m-modal/index.tsx | 70 +++---------------- .../appkit/src/modal/w3m-router/index.tsx | 2 +- .../views/w3m-connecting-siwe-view/index.tsx | 13 ++-- .../views/w3m-connecting-siwe-view/styles.ts | 0 packages/common/src/adapters/EvmAdapter.ts | 17 +++++ .../src/controllers/ConnectionsController.ts | 14 ++++ packages/siwe/src/client.ts | 31 ++++---- .../siwe/src/controller/SIWEController.ts | 2 +- packages/siwe/src/index.ts | 2 - 11 files changed, 111 insertions(+), 87 deletions(-) rename packages/{siwe/src/scaffold => appkit/src}/views/w3m-connecting-siwe-view/index.tsx (91%) rename packages/{siwe/src/scaffold => appkit/src}/views/w3m-connecting-siwe-view/styles.ts (100%) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 0a074c6b..9ac55d2c 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -33,6 +33,7 @@ import { WalletInfoView } from './src/views/WalletInfoView'; import { EventsView } from './src/views/EventsView'; import { getCustomWallets } from './src/utils/misc'; import { storage } from './src/utils/StorageUtil'; +import { siweConfig } from './src/utils/SiweUtils'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -76,6 +77,7 @@ const appKit = createAppKit({ projectId, adapters: [wagmiAdapter, solanaAdapter, bitcoinAdapter], metadata, + siweConfig, networks: [...chains, solana, bitcoin], defaultNetwork: chains[2], clipboardClient, diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index 228afe21..d2d4c42b 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -185,6 +185,10 @@ export class AppKit { TransactionsController.resetTransactions(); ConnectionController.disconnect(); + if (OptionsController.state.isSiweEnabled) { + await SIWEController.signOut(); + } + EventsController.sendEvent({ type: 'track', event: 'DISCONNECT_SUCCESS' @@ -418,6 +422,10 @@ export class AppKit { //eslint-disable-next-line no-console console.log('accountsChanged', accounts, namespace); //TODO: check this + + if (namespace === 'eip155') { + this.handleSiweChange({ isAccountChange: true }); + } }); adapter.on('chainChanged', ({ chainId }) => { @@ -432,6 +440,10 @@ export class AppKit { tokens: this.config.tokens }); } + + if (namespace === 'eip155') { + this.handleSiweChange({ isNetworkChange: true }); + } }); adapter.on('disconnect', () => { @@ -464,7 +476,8 @@ export class AppKit { ThemeController.setThemeVariables(options.themeVariables); //TODO: function to get sdk version based on adapters - // OptionsController.setSdkVersion(options._sdkVersion); + // @ts-ignore + OptionsController.setSdkVersion('appkit-react-native-multichain'); if (options.clipboardClient) { OptionsController.setClipboardClient(options.clipboardClient); @@ -521,6 +534,36 @@ export class AppKit { await this.initRecentWallets(options); //disable coinbase if connector is not set } + + private onSiweNavigation = () => { + if (ModalController.state.open) { + RouterController.push('ConnectingSiwe'); + } else { + ModalController.open({ view: 'ConnectingSiwe' }); + } + }; + + private async handleSiweChange(params: { isNetworkChange?: boolean; isAccountChange?: boolean }) { + const { isNetworkChange, isAccountChange } = params; + const { enabled, signOutOnAccountChange, signOutOnNetworkChange } = + SIWEController.state._client?.options ?? {}; + + if (enabled) { + const session = await SIWEController.getSession(); + if (session && isAccountChange && signOutOnAccountChange) { + // If the address has changed and signOnAccountChange is enabled, sign out + await SIWEController.signOut(); + this.onSiweNavigation(); + } else if (isNetworkChange && signOutOnNetworkChange) { + // If the network has changed and signOnNetworkChange is enabled, sign out + await SIWEController.signOut(); + this.onSiweNavigation(); + } else if (!session) { + // If it's connected but there's no session, show sign view + this.onSiweNavigation(); + } + } + } } export function createAppKit(config: AppKitConfig): AppKit { diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index cf87d694..611be634 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -4,21 +4,17 @@ import { useWindowDimensions, StatusBar } from 'react-native'; import Modal from 'react-native-modal'; import { Card, ThemeProvider } from '@reown/appkit-ui-react-native'; import { - AccountController, ApiController, - ConnectionController, ConnectorController, - CoreHelperUtil, EventsController, ModalController, OptionsController, RouterController, - TransactionsController, type AppKitFrameProvider, WebviewController, - ThemeController + ThemeController, + ConnectionsController } from '@reown/appkit-core-react-native'; -import type { CaipAddress } from '@reown/appkit-common-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; import { AppKitRouter } from '../w3m-router'; @@ -26,11 +22,12 @@ import { Header } from '../../partials/w3m-header'; import { Snackbar } from '../../partials/w3m-snackbar'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; +import { useAppKit } from '../../AppKitContext'; export function AppKit() { - const { open, loading } = useSnapshot(ModalController.state); + const { disconnect } = useAppKit(); + const { open } = useSnapshot(ModalController.state); const { connectors, connectedConnector } = useSnapshot(ConnectorController.state); - const { caipAddress, isConnected } = useSnapshot(AccountController.state); const { frameViewVisible, webviewVisible } = useSnapshot(WebviewController.state); const { themeMode, themeVariables } = useSnapshot(ThemeController.state); const { projectId } = useSnapshot(OptionsController.state); @@ -58,8 +55,11 @@ export function AppKit() { const handleClose = async () => { if (OptionsController.state.isSiweEnabled) { - if (SIWEController.state.status !== 'success' && AccountController.state.isConnected) { - await ConnectionController.disconnect(); + if ( + SIWEController.state.status !== 'success' && + !!ConnectionsController.state.activeAddress + ) { + await disconnect(); } } @@ -71,62 +71,12 @@ export function AppKit() { EventsController.sendEvent({ type: 'track', event: 'BUY_CANCEL' }); } }; - - const onNewAddress = useCallback( - async (address?: CaipAddress) => { - if (!isConnected || loading) { - return; - } - - const newAddress = CoreHelperUtil.getPlainAddress(address); - TransactionsController.resetTransactions(); - - if (OptionsController.state.isSiweEnabled) { - const newNetworkId = CoreHelperUtil.getNetworkId(address); - - const { signOutOnAccountChange, signOutOnNetworkChange } = - SIWEController.state._client?.options ?? {}; - const session = await SIWEController.getSession(); - - if (session && newAddress && signOutOnAccountChange) { - // If the address has changed and signOnAccountChange is enabled, sign out - await SIWEController.signOut(); - onSiweNavigation(); - } else if ( - newNetworkId && - session?.chainId.toString() !== newNetworkId && - signOutOnNetworkChange - ) { - // If the network has changed and signOnNetworkChange is enabled, sign out - await SIWEController.signOut(); - onSiweNavigation(); - } else if (!session) { - // If it's connected but there's no session, show sign view - onSiweNavigation(); - } - } - }, - [isConnected, loading] - ); - - const onSiweNavigation = () => { - if (ModalController.state.open) { - RouterController.push('ConnectingSiwe'); - } else { - ModalController.open({ view: 'ConnectingSiwe' }); - } - }; - useEffect(() => { if (projectId) { prefetch(); } }, [projectId, prefetch]); - useEffect(() => { - onNewAddress(caipAddress); - }, [caipAddress, onNewAddress]); - return ( <> diff --git a/packages/appkit/src/modal/w3m-router/index.tsx b/packages/appkit/src/modal/w3m-router/index.tsx index 761770ef..9294c43a 100644 --- a/packages/appkit/src/modal/w3m-router/index.tsx +++ b/packages/appkit/src/modal/w3m-router/index.tsx @@ -12,7 +12,7 @@ import { ConnectingExternalView } from '../../views/w3m-connecting-external-view import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; import { ConnectingSocialView } from '../../views/w3m-connecting-social-view'; import { CreateView } from '../../views/w3m-create-view'; -import { ConnectingSiweView } from '@reown/appkit-siwe-react-native'; +import { ConnectingSiweView } from '../../views/w3m-connecting-siwe-view'; import { EmailVerifyOtpView } from '../../views/w3m-email-verify-otp-view'; import { EmailVerifyDeviceView } from '../../views/w3m-email-verify-device-view'; import { GetWalletView } from '../../views/w3m-get-wallet-view'; diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx b/packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx similarity index 91% rename from packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx rename to packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx index d9cf0efb..ed73144a 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx @@ -20,13 +20,15 @@ import { } from '@reown/appkit-core-react-native'; import { useState } from 'react'; -import { SIWEController } from '../../../controller/SIWEController'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; import styles from './styles'; +import { useAppKit } from '../../AppKitContext'; export function ConnectingSiweView() { + const { disconnect } = useAppKit(); const { metadata } = useSnapshot(OptionsController.state); const { connectedWalletImageUrl, pressedWallet } = useSnapshot(ConnectionController.state); - const { address, profileImage } = useSnapshot(AccountController.state); + const { activeAddress } = useSnapshot(ConnectionsController.state); const [isSigning, setIsSigning] = useState(false); const [isDisconnecting, setIsDisconnecting] = useState(false); @@ -76,10 +78,9 @@ export function ConnectingSiweView() { }; const onCancel = async () => { - const { isConnected } = AccountController.state; - if (isConnected) { + if (ConnectionsController.state.activeAddress) { setIsDisconnecting(true); - await ConnectionController.disconnect(); + await disconnect(); ModalController.close(); setIsDisconnecting(false); } else { @@ -112,7 +113,7 @@ export function ConnectingSiweView() { leftImage={dappIcon} rightImage={walletIcon} renderRightPlaceholder={() => ( - + )} rightItemStyle={!walletIcon && styles.walletAvatar} /> diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts b/packages/appkit/src/views/w3m-connecting-siwe-view/styles.ts similarity index 100% rename from packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts rename to packages/appkit/src/views/w3m-connecting-siwe-view/styles.ts diff --git a/packages/common/src/adapters/EvmAdapter.ts b/packages/common/src/adapters/EvmAdapter.ts index b9aa2556..7e4aee3a 100644 --- a/packages/common/src/adapters/EvmAdapter.ts +++ b/packages/common/src/adapters/EvmAdapter.ts @@ -2,6 +2,23 @@ import { BlockchainAdapter } from './BlockchainAdapter'; import { NumberUtil } from '../utils/NumberUtil'; export abstract class EVMAdapter extends BlockchainAdapter { + async signMessage(address: string, message: string, chain?: string): Promise { + const provider = this.getProvider(); + + if (!provider) { + throw new Error('EVMAdapter:signMessage - provider is undefined'); + } + + const signature = await provider.request( + { + method: 'personal_sign', + params: [message, address] + }, + `eip155:${chain}` + ); + + return signature as string; + } async estimateGas({ address, to, data, chainNamespace }: any): Promise { const provider = this.getProvider(); diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 68bc2c67..4839985f 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -265,6 +265,20 @@ export const ConnectionsController = { ?.adapter.parseUnits(value, decimals); }, + async signMessage(address: CaipAddress, message: string) { + if (!baseState.activeNamespace) return undefined; + + const adapter = baseState.connections.get(baseState.activeNamespace)?.adapter; + + const evmAddress = address.split(':')[2]; + const chainId = address.split(':')[1]; + if (adapter instanceof EVMAdapter && evmAddress && chainId) { + return adapter.signMessage(evmAddress, message, chainId); + } + + return undefined; + }, + async sendTransaction(args: any) { if (!baseState.activeNamespace) return undefined; diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index b4da23af..91b43f29 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -1,9 +1,4 @@ -import { - AccountController, - ConnectionController, - RouterUtil, - ConnectionsController -} from '@reown/appkit-core-react-native'; +import { RouterUtil, ConnectionsController } from '@reown/appkit-core-react-native'; import { NetworkUtil } from '@reown/appkit-common-react-native'; import type { @@ -86,21 +81,25 @@ export class AppKitSIWEClient { return session; } - async signIn(): Promise { - const { address } = AccountController.state; - const nonce = await this.getNonce(address); - if (!address) { + async signIn(): Promise { + const { activeAddress, activeCaipNetworkId } = ConnectionsController.state; + + if (activeCaipNetworkId && !activeCaipNetworkId.startsWith('eip155')) { + return Promise.resolve(undefined); + } + + const nonce = await this.getNonce(activeAddress); + + if (!activeAddress) { throw new Error('An address is required to create a SIWE message.'); } - const chainId = NetworkUtil.caipNetworkIdToNumber( - ConnectionsController.state.activeNetwork?.caipNetworkId - ); + const chainId = NetworkUtil.caipNetworkIdToNumber(activeCaipNetworkId); if (!chainId) { throw new Error('A chainId is required to create a SIWE message.'); } const messageParams = await this.getMessageParams?.(); const message = this.createMessage({ - address: `eip155:${chainId}:${address}`, + address: `eip155:${chainId}:${activeAddress}`, chainId, nonce, version: '1', @@ -108,8 +107,8 @@ export class AppKitSIWEClient { ...messageParams! }); - const signature = await ConnectionController.signMessage(message); - const isValid = await this.verifyMessage({ message, signature }); + const signature = await ConnectionsController.signMessage(activeAddress, message); + const isValid = signature && (await this.verifyMessage({ message, signature })); if (!isValid) { throw new Error('Error verifying SIWE signature'); } diff --git a/packages/siwe/src/controller/SIWEController.ts b/packages/siwe/src/controller/SIWEController.ts index 95ecad9b..4b4e594b 100644 --- a/packages/siwe/src/controller/SIWEController.ts +++ b/packages/siwe/src/controller/SIWEController.ts @@ -10,7 +10,7 @@ import type { // -- Types --------------------------------------------- // export interface SIWEControllerClient extends SIWEClientMethods { - signIn: () => Promise; + signIn: () => Promise; options: { enabled: boolean; nonceRefetchIntervalMs: number; diff --git a/packages/siwe/src/index.ts b/packages/siwe/src/index.ts index 59dca66b..9e9f1fd6 100644 --- a/packages/siwe/src/index.ts +++ b/packages/siwe/src/index.ts @@ -22,5 +22,3 @@ export type { export function createSIWEConfig(siweConfig: SIWEConfig) { return new AppKitSIWEClient(siweConfig); } - -export * from './scaffold/views/w3m-connecting-siwe-view/index'; From 8f3012b74df6c6e6629ccebb6d0df4f420514da2 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:27:58 -0300 Subject: [PATCH 79/91] chore: moved siwe types to common, enabled 1ca, solved double init of wc connector --- package.json | 4 +- packages/appkit/src/AppKit.ts | 27 +++- .../src/connectors/WalletConnectConnector.ts | 86 +++++++++- packages/common/src/utils/TypeUtil.ts | 106 +++++++++++++ packages/siwe/src/client.ts | 2 +- .../siwe/src/controller/SIWEController.ts | 2 +- packages/siwe/src/index.ts | 16 +- packages/siwe/src/utils/TypeUtils.ts | 86 ---------- yarn.lock | 149 ++++++++++++++---- 9 files changed, 337 insertions(+), 141 deletions(-) delete mode 100644 packages/siwe/src/utils/TypeUtils.ts diff --git a/package.json b/package.json index 34d43fde..cbd619f0 100644 --- a/package.json +++ b/package.json @@ -73,8 +73,8 @@ "tsconfig": "*", "turbo": "2.1.1", "typescript": "5.2.2", - "viem": "2.28.3", - "wagmi": "2.15.1" + "viem": "2.31.3", + "wagmi": "2.15.6" }, "packageManager": "yarn@4.0.2", "resolutions": { diff --git a/packages/appkit/src/AppKit.ts b/packages/appkit/src/AppKit.ts index d2d4c42b..7c1669c6 100644 --- a/packages/appkit/src/AppKit.ts +++ b/packages/appkit/src/AppKit.ts @@ -28,14 +28,15 @@ import type { WalletInfo, Network, ChainNamespace, - ConnectOptions, - Storage + Storage, + AppKitConnectOptions, + AppKitSIWEClient } from '@reown/appkit-common-react-native'; import { WalletConnectConnector } from './connectors/WalletConnectConnector'; import { WcHelpersUtil } from './utils/HelpersUtil'; import { NetworkUtil } from './utils/NetworkUtil'; -import { SIWEController, type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; import type { OpenOptions } from './client'; interface AppKitConfig { @@ -69,6 +70,7 @@ export class AppKit { private namespaces: ProposalNamespaces; private config: AppKitConfig; private extraConnectors: WalletConnector[]; + private walletConnectConnector?: WalletConnector; constructor(config: AppKitConfig) { this.projectId = config.projectId; @@ -106,7 +108,7 @@ export class AppKit { * @param type - The type of connector to use. * @param options - Optional connection options. */ - async connect(type: New_ConnectorType, options?: ConnectOptions): Promise { + async connect(type: New_ConnectorType, options?: AppKitConnectOptions): Promise { try { const { namespaces, defaultChain, universalLink } = options ?? {}; const connector = await this.createConnector(type); @@ -114,7 +116,8 @@ export class AppKit { const approvedNamespaces = await connector.connect({ namespaces: namespaces ?? this.namespaces, defaultChain, - universalLink + universalLink, + siweConfig: this.config?.siweConfig }); const walletInfo = connector.getWalletInfo(); @@ -270,15 +273,23 @@ export class AppKit { } // Default to WalletConnectConnector if no custom connector matches - const walletConnectConnector = new WalletConnectConnector({ + return this.createWalletConnectConnector(); + } + + private async createWalletConnectConnector() { + if (this.walletConnectConnector) { + return this.walletConnectConnector; + } + + this.walletConnectConnector = new WalletConnectConnector({ projectId: this.projectId }); - await walletConnectConnector.init({ + await this.walletConnectConnector.init({ storage: this.config.storage, metadata: this.config.metadata }); - return walletConnectConnector; + return this.walletConnectConnector; } //TODO: reuse logic with connect method diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index ff30bf31..35725281 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -12,6 +12,7 @@ import { type ConnectorInitOptions, type Metadata } from '@reown/appkit-common-react-native'; +import { getDidAddress, getDidChainId, SIWEController } from '@reown/appkit-siwe-react-native'; interface WalletConnectConnectorConfig { projectId: string; @@ -85,6 +86,7 @@ export class WalletConnectConnector extends WalletConnector { } override async connect(opts: ConnectOptions) { + const { siweConfig, namespaces, defaultChain, universalLink } = opts; function onUri(uri: string) { ConnectionController.setWcUri(uri); } @@ -94,13 +96,84 @@ export class WalletConnectConnector extends WalletConnector { // @ts-ignore provider.on('display_uri', onUri); - const session = await (this.provider as IUniversalProvider).connect({ - namespaces: {}, - optionalNamespaces: opts.namespaces - }); + let session; + + // SIWE + const params = await siweConfig?.getMessageParams?.(); + if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { + // @ts-ignore + const result = await provider.authenticate( + { + ...params, + nonce: await siweConfig.getNonce(), + methods: namespaces?.['eip155']?.methods, + chains: params.chains.map(chain => `eip155:${chain}`) + }, + universalLink + ); + + console.log('result SIWE', result); + + // Auths is an array of signed CACAO objects https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-74.md + const signedCacao = result?.auths?.[0]; + if (signedCacao) { + const { p, s } = signedCacao; + const chainId = getDidChainId(p.iss); + const address = getDidAddress(p.iss); + + try { + // Kicks off verifyMessage and populates external states + const message = provider?.client?.formatAuthMessage({ + request: p, + iss: p.iss + })!; + + await SIWEController.verifyMessage({ + message, + signature: s.s, + cacao: signedCacao + }); + + if (address && chainId) { + const session = { + address, + chainId: parseInt(chainId, 10) + }; + + SIWEController.setSession(session); + SIWEController.onSignIn?.(session); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error verifying message', error); + // eslint-disable-next-line no-console + await provider.disconnect().catch(console.error); + // eslint-disable-next-line no-console + await SIWEController.signOut().catch(console.error); + throw error; + } + } + session = result?.session; + } else { + session = await (this.provider as IUniversalProvider).connect({ + namespaces: {}, + optionalNamespaces: namespaces + }); + } + + const metadata = session?.peer?.metadata; + if (metadata) { + this.wallet = { + ...metadata, + name: metadata?.name, + icon: metadata?.icons?.[0] + }; + } else { + this.wallet = undefined; + } - if (opts.defaultChain) { - (this.provider as IUniversalProvider).setDefaultChain(opts.defaultChain); + if (defaultChain) { + (this.provider as IUniversalProvider).setDefaultChain(defaultChain); } this.namespaces = session?.namespaces as Namespaces; @@ -133,6 +206,7 @@ export class WalletConnectConnector extends WalletConnector { } override getWalletInfo(): WalletInfo | undefined { + console.log('getWalletInfo', this.wallet); return this.wallet; } diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 83d9b323..15a3688e 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -34,6 +34,11 @@ export type AppKitNetwork = Network & { deprecatedCaipNetworkId?: CaipNetworkId; // for Solana deprecated id }; +export type AppKitConnectOptions = Pick< + ConnectOptions, + 'namespaces' | 'defaultChain' | 'universalLink' +>; + export interface CaipNetwork { id: CaipNetworkId; name?: string; @@ -222,6 +227,7 @@ export type ConnectOptions = { namespaces?: ProposalNamespaces; defaultChain?: CaipNetworkId; universalLink?: string; + siweConfig?: AppKitSIWEClient; }; export type ConnectorInitOptions = { @@ -334,3 +340,103 @@ export interface Storage { */ removeItem(key: string): Promise; } + +//********** SIWE Types **********// +export interface SIWESession { + address: string; + chainId: number; +} + +interface CacaoHeader { + t: 'caip122'; +} + +export interface SIWECreateMessageArgs { + chainId: number; + domain: string; + nonce: string; + uri: string; + address: string; + version: '1'; + type?: CacaoHeader['t']; + nbf?: string; + exp?: string; + statement?: string; + requestId?: string; + resources?: string[]; + expiry?: number; + iat?: string; +} +export type SIWEMessageArgs = { + chains: string[]; + methods?: string[]; +} & Omit; +// Signed Cacao (CAIP-74) +interface CacaoPayload { + domain: string; + aud: string; + nonce: string; + iss: string; + version?: string; + iat?: string; + nbf?: string; + exp?: string; + statement?: string; + requestId?: string; + resources?: string[]; + type?: string; +} + +interface Cacao { + h: CacaoHeader; + p: CacaoPayload; + s: { + t: 'eip191' | 'eip1271'; + s: string; + m?: string; + }; +} + +export interface SIWEVerifyMessageArgs { + message: string; + signature: string; + cacao?: Cacao; +} + +export interface SIWEClientMethods { + getNonce: (address?: string) => Promise; + createMessage: (args: SIWECreateMessageArgs) => string; + verifyMessage: (args: SIWEVerifyMessageArgs) => Promise; + getSession: () => Promise; + signOut: () => Promise; + getMessageParams?: () => Promise; + onSignIn?: (session?: SIWESession) => void; + onSignOut?: () => void; +} + +export interface SIWEConfig extends SIWEClientMethods { + // Defaults to true + enabled?: boolean; + // In milliseconds, defaults to 5 minutes + nonceRefetchIntervalMs?: number; + // In milliseconds, defaults to 5 minutes + sessionRefetchIntervalMs?: number; + // Defaults to true + signOutOnDisconnect?: boolean; + // Defaults to true + signOutOnAccountChange?: boolean; + // Defaults to true + signOutOnNetworkChange?: boolean; +} + +export interface AppKitSIWEClient extends SIWEClientMethods { + signIn: () => Promise; + options: { + enabled: boolean; + nonceRefetchIntervalMs: number; + sessionRefetchIntervalMs: number; + signOutOnDisconnect: boolean; + signOutOnAccountChange: boolean; + signOutOnNetworkChange: boolean; + }; +} diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index 91b43f29..644f22c3 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -8,7 +8,7 @@ import type { SIWEClientMethods, SIWESession, SIWEMessageArgs -} from './utils/TypeUtils'; +} from '@reown/appkit-common-react-native'; import type { SIWEControllerClient } from './controller/SIWEController'; import { ConstantsUtil } from './utils/ConstantsUtil'; diff --git a/packages/siwe/src/controller/SIWEController.ts b/packages/siwe/src/controller/SIWEController.ts index 4b4e594b..19fa655e 100644 --- a/packages/siwe/src/controller/SIWEController.ts +++ b/packages/siwe/src/controller/SIWEController.ts @@ -6,7 +6,7 @@ import type { SIWESession, SIWECreateMessageArgs, SIWEVerifyMessageArgs -} from '../utils/TypeUtils'; +} from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface SIWEControllerClient extends SIWEClientMethods { diff --git a/packages/siwe/src/index.ts b/packages/siwe/src/index.ts index 9e9f1fd6..d6d031b1 100644 --- a/packages/siwe/src/index.ts +++ b/packages/siwe/src/index.ts @@ -1,23 +1,19 @@ +import type { SIWEConfig } from '@reown/appkit-common-react-native'; export { formatMessage, getDidChainId, getDidAddress } from '@walletconnect/utils'; -import type { - SIWEConfig, - SIWESession, - SIWECreateMessageArgs, - SIWEVerifyMessageArgs, - SIWEClientMethods -} from './utils/TypeUtils'; -import { AppKitSIWEClient } from './client'; export { getAddressFromMessage, getChainIdFromMessage, verifySignature } from './helpers/index'; export { SIWEController, type SIWEControllerClient } from './controller/SIWEController'; +import { AppKitSIWEClient } from './client'; + +export type { AppKitSIWEClient }; + export type { - AppKitSIWEClient, SIWEConfig, SIWESession, SIWECreateMessageArgs, SIWEVerifyMessageArgs, SIWEClientMethods -}; +} from '@reown/appkit-common-react-native'; export function createSIWEConfig(siweConfig: SIWEConfig) { return new AppKitSIWEClient(siweConfig); diff --git a/packages/siwe/src/utils/TypeUtils.ts b/packages/siwe/src/utils/TypeUtils.ts deleted file mode 100644 index 19723e56..00000000 --- a/packages/siwe/src/utils/TypeUtils.ts +++ /dev/null @@ -1,86 +0,0 @@ -export interface SIWESession { - address: string; - chainId: number; -} - -interface CacaoHeader { - t: 'caip122'; -} - -export interface SIWECreateMessageArgs { - chainId: number; - domain: string; - nonce: string; - uri: string; - address: string; - version: '1'; - type?: CacaoHeader['t']; - nbf?: string; - exp?: string; - statement?: string; - requestId?: string; - resources?: string[]; - expiry?: number; - iat?: string; -} -export type SIWEMessageArgs = { - chains: number[]; - methods?: string[]; -} & Omit; -// Signed Cacao (CAIP-74) -interface CacaoPayload { - domain: string; - aud: string; - nonce: string; - iss: string; - version?: string; - iat?: string; - nbf?: string; - exp?: string; - statement?: string; - requestId?: string; - resources?: string[]; - type?: string; -} - -interface Cacao { - h: CacaoHeader; - p: CacaoPayload; - s: { - t: 'eip191' | 'eip1271'; - s: string; - m?: string; - }; -} - -export interface SIWEVerifyMessageArgs { - message: string; - signature: string; - cacao?: Cacao; -} - -export interface SIWEClientMethods { - getNonce: (address?: string) => Promise; - createMessage: (args: SIWECreateMessageArgs) => string; - verifyMessage: (args: SIWEVerifyMessageArgs) => Promise; - getSession: () => Promise; - signOut: () => Promise; - getMessageParams?: () => Promise; - onSignIn?: (session?: SIWESession) => void; - onSignOut?: () => void; -} - -export interface SIWEConfig extends SIWEClientMethods { - // Defaults to true - enabled?: boolean; - // In milliseconds, defaults to 5 minutes - nonceRefetchIntervalMs?: number; - // In milliseconds, defaults to 5 minutes - sessionRefetchIntervalMs?: number; - // Defaults to true - signOutOnDisconnect?: boolean; - // Defaults to true - signOutOnAccountChange?: boolean; - // Defaults to true - signOutOnNetworkChange?: boolean; -} diff --git a/yarn.lock b/yarn.lock index 51a1abaf..dab597d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,7 +45,7 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:^1.10.1": +"@adraffy/ens-normalize@npm:^1.10.1, @adraffy/ens-normalize@npm:^1.11.0": version: 1.11.0 resolution: "@adraffy/ens-normalize@npm:1.11.0" checksum: 5111d0f1a273468cb5661ed3cf46ee58de8f32f84e2ebc2365652e66c1ead82649df94c736804e2b9cfa831d30ef24e1cc3575d970dbda583416d3a98d8870a6 @@ -4211,15 +4211,15 @@ __metadata: languageName: node linkType: hard -"@coinbase/wallet-sdk@npm:4.3.0": - version: 4.3.0 - resolution: "@coinbase/wallet-sdk@npm:4.3.0" +"@coinbase/wallet-sdk@npm:4.3.3": + version: 4.3.3 + resolution: "@coinbase/wallet-sdk@npm:4.3.3" dependencies: "@noble/hashes": "npm:^1.4.0" clsx: "npm:^1.2.1" eventemitter3: "npm:^5.0.1" preact: "npm:^10.24.2" - checksum: 39e38ab6f84e34d8a61b9baf3fb69ad20b497d6844fe3f0cb1496e89bbb990066a6e8d68446f90054394eee840f3a452330ffbb015adabc34400f36a3ef03364 + checksum: 528cbc62f42c151c45c61c4c73e120d6b98d88f5858edbc8cf50f3d96030103b5b0ae53415e2aa80d455a1be660d1f0dc73672aa64636359bd55aa25b0faea60 languageName: node linkType: hard @@ -5674,6 +5674,13 @@ __metadata: languageName: node linkType: hard +"@noble/ciphers@npm:^1.3.0": + version: 1.3.0 + resolution: "@noble/ciphers@npm:1.3.0" + checksum: 3ba6da645ce45e2f35e3b2e5c87ceba86b21dfa62b9466ede9edfb397f8116dae284f06652c0cd81d99445a2262b606632e868103d54ecc99fd946ae1af8cd37 + languageName: node + linkType: hard + "@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": version: 1.1.0 resolution: "@noble/curves@npm:1.1.0" @@ -5728,6 +5735,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.9.2, @noble/curves@npm:^1.9.1, @noble/curves@npm:~1.9.0": + version: 1.9.2 + resolution: "@noble/curves@npm:1.9.2" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: 21d049ae4558beedbf5da0004407b72db84360fa29d64822d82dc9e80251e1ecb46023590cc4b20e70eed697d1b87279b4911dc39f8694c51c874289cfc8e9a7 + languageName: node + linkType: hard + "@noble/curves@npm:^1.4.0, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -5813,7 +5829,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.2.0": +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.2.0, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" checksum: 06a0b52c81a6fa7f04d67762e08b2c476a00285858150caeaaff4037356dd5e119f45b2a530f638b77a5eeca013168ec1b655db41bae3236cb2e9d511484fc77 @@ -7084,6 +7100,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.2.5": + version: 1.2.6 + resolution: "@scure/base@npm:1.2.6" + checksum: 49bd5293371c4e062cb6ba689c8fe3ea3981b7bb9c000400dc4eafa29f56814cdcdd27c04311c2fec34de26bc373c593a1d6ca6d754398a488d587943b7c128a + languageName: node + linkType: hard + "@scure/bip32@npm:1.3.1": version: 1.3.1 resolution: "@scure/bip32@npm:1.3.1" @@ -7117,6 +7140,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.7.0, @scure/bip32@npm:^1.7.0": + version: 1.7.0 + resolution: "@scure/bip32@npm:1.7.0" + dependencies: + "@noble/curves": "npm:~1.9.0" + "@noble/hashes": "npm:~1.8.0" + "@scure/base": "npm:~1.2.5" + checksum: e3d4c1f207df16abcd79babcdb74d36f89bdafc90bf02218a5140cc5cba25821d80d42957c6705f35210cc5769714ea9501d4ae34732cdd1c26c9ff182a219f7 + languageName: node + linkType: hard + "@scure/bip32@npm:^1.5.0": version: 1.5.0 resolution: "@scure/bip32@npm:1.5.0" @@ -7158,6 +7192,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:1.6.0, @scure/bip39@npm:^1.6.0": + version: 1.6.0 + resolution: "@scure/bip39@npm:1.6.0" + dependencies: + "@noble/hashes": "npm:~1.8.0" + "@scure/base": "npm:~1.2.5" + checksum: 73a54b5566a50a3f8348a5cfd74d2092efeefc485efbed83d7a7374ffd9a75defddf446e8e5ea0385e4adb49a94b8ae83c5bad3e16333af400e932f7da3aaff8 + languageName: node + linkType: hard + "@scure/bip39@npm:^1.4.0": version: 1.4.0 resolution: "@scure/bip39@npm:1.4.0" @@ -8820,30 +8864,30 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.8.0": - version: 5.8.0 - resolution: "@wagmi/connectors@npm:5.8.0" +"@wagmi/connectors@npm:5.8.5": + version: 5.8.5 + resolution: "@wagmi/connectors@npm:5.8.5" dependencies: - "@coinbase/wallet-sdk": "npm:4.3.0" + "@coinbase/wallet-sdk": "npm:4.3.3" "@metamask/sdk": "npm:0.32.0" "@safe-global/safe-apps-provider": "npm:0.18.6" "@safe-global/safe-apps-sdk": "npm:9.1.0" - "@walletconnect/ethereum-provider": "npm:2.20.0" + "@walletconnect/ethereum-provider": "npm:2.21.1" cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.17.0 + "@wagmi/core": 2.17.3 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 620f668843e8799dd990d3f1c1645462f04f6ddb96b958e6d449f0a3c7ca05330ba44be2551238fec8b3f6a11f0b1967cb246d31669b6c504535f268b6641178 + checksum: d00dbeefe090a2dd740594d560108998cee4ce02e789e4a3b591a742b3622a7a47f2504e0e1c72315935696fbda26446c496491972dcdde1e1b3f2173ceb2b8d languageName: node linkType: hard -"@wagmi/core@npm:2.17.0": - version: 2.17.0 - resolution: "@wagmi/core@npm:2.17.0" +"@wagmi/core@npm:2.17.3": + version: 2.17.3 + resolution: "@wagmi/core@npm:2.17.3" dependencies: eventemitter3: "npm:5.0.1" mipd: "npm:0.0.7" @@ -8857,7 +8901,7 @@ __metadata: optional: true typescript: optional: true - checksum: cf691f134b3335302f3230bca064b587b5b085b36b6bc6f0a96e32888b7f5220fe4def2b0727fddef0f07fd11c0241ccd352980ae761cd0fa8c9010315621739 + checksum: 128875066323c87242293cfe5b22fe596dd8a55c79efeb2a7b36b6a1acd549e217cdb30215119bd203580b71118cd197274eed83c120dce5846b1894140b79be languageName: node linkType: hard @@ -9397,7 +9441,7 @@ __metadata: languageName: node linkType: hard -"abitype@npm:1.0.8": +"abitype@npm:1.0.8, abitype@npm:^1.0.8": version: 1.0.8 resolution: "abitype@npm:1.0.8" peerDependencies: @@ -9761,8 +9805,8 @@ __metadata: tsconfig: "npm:*" turbo: "npm:2.1.1" typescript: "npm:5.2.2" - viem: "npm:2.28.3" - wagmi: "npm:2.15.1" + viem: "npm:2.31.3" + wagmi: "npm:2.15.6" languageName: unknown linkType: soft @@ -15762,6 +15806,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.7": + version: 1.0.7 + resolution: "isows@npm:1.0.7" + peerDependencies: + ws: "*" + checksum: 43c41fe89c7c07258d0be3825f87e12da8ac9023c5b5ae6741ec00b2b8169675c04331ea73ef8c172d37a6747066f4dc93947b17cd369f92828a3b3e741afbda + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -18767,6 +18820,27 @@ __metadata: languageName: node linkType: hard +"ox@npm:0.8.1": + version: 0.8.1 + resolution: "ox@npm:0.8.1" + dependencies: + "@adraffy/ens-normalize": "npm:^1.11.0" + "@noble/ciphers": "npm:^1.3.0" + "@noble/curves": "npm:^1.9.1" + "@noble/hashes": "npm:^1.8.0" + "@scure/bip32": "npm:^1.7.0" + "@scure/bip39": "npm:^1.6.0" + abitype: "npm:^1.0.8" + eventemitter3: "npm:5.0.1" + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 3d04df384a35c94b21a29d867ee3735acf9a975d46ffb0a26cc438b92f1e4952b2b3cddb74b4213e88d2988e82687db9b85c1018c5d4b24737b1c3d7cb7c809e + languageName: node + linkType: hard + "p-filter@npm:^2.1.0": version: 2.1.0 resolution: "p-filter@npm:2.1.0" @@ -23138,7 +23212,28 @@ __metadata: languageName: node linkType: hard -"viem@npm:2.28.3, viem@npm:>=2.23.11": +"viem@npm:2.31.3": + version: 2.31.3 + resolution: "viem@npm:2.31.3" + dependencies: + "@noble/curves": "npm:1.9.2" + "@noble/hashes": "npm:1.8.0" + "@scure/bip32": "npm:1.7.0" + "@scure/bip39": "npm:1.6.0" + abitype: "npm:1.0.8" + isows: "npm:1.0.7" + ox: "npm:0.8.1" + ws: "npm:8.18.2" + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 40a6493e594047ec3d41fe4758653015d9d860dc469fdc4f4e6062cb167290cc5e13ae84fd448e29bce1d543c981852ea2123ce231d7caa67bd098e95ac5f709 + languageName: node + linkType: hard + +"viem@npm:>=2.23.11": version: 2.28.3 resolution: "viem@npm:2.28.3" dependencies: @@ -23188,12 +23283,12 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:2.15.1": - version: 2.15.1 - resolution: "wagmi@npm:2.15.1" +"wagmi@npm:2.15.6": + version: 2.15.6 + resolution: "wagmi@npm:2.15.6" dependencies: - "@wagmi/connectors": "npm:5.8.0" - "@wagmi/core": "npm:2.17.0" + "@wagmi/connectors": "npm:5.8.5" + "@wagmi/core": "npm:2.17.3" use-sync-external-store: "npm:1.4.0" peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -23203,7 +23298,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 117a66fc132b68f25cc936058f419eb3d153d91424403e1db6622fa56b01370bb51f7b742f3e5f66466b78f26008198752d759ebc2005462604daaa31c88a39c + checksum: ecdbb177b8a18827e5b0bddcaf3af5147f43662eb85db1964d76f657c89211ee3c56e0565747d175143664a676c43b497d55446a1e31997d50f908f82b3ab472 languageName: node linkType: hard From 706153cb689b1ce0b4646fb1cff00fe2f4591e88 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:57:16 -0300 Subject: [PATCH 80/91] chore: solved lint issues + changed build command --- package.json | 4 ++-- packages/appkit/src/connectors/WalletConnectConnector.ts | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index cbd619f0..61027d35 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "clean": "turbo clean && rm -rf node_modules && watchman watch-del-all", "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore", "playwright:test": "cd apps/native && yarn playwright:test", - "changeset:prepublish": "yarn run clean; yarn install; yarn version:update; yarn run lint && yarn run prettier; yarn run build; yarn run test", - "changeset:publish": "yarn run changeset:prepublish; yarn run changeset publish", + "changeset:prepublish": "yarn run clean && yarn install && yarn version:update && yarn run lint && yarn run prettier && yarn run build && yarn run test", + "changeset:publish": "yarn run changeset:prepublish && yarn run changeset publish", "changeset:version": "changeset version; yarn run version:update; yarn install --refresh-lockfile", "version:update": "./scripts/bump-version.sh" }, diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 35725281..0575926f 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -112,8 +112,6 @@ export class WalletConnectConnector extends WalletConnector { universalLink ); - console.log('result SIWE', result); - // Auths is an array of signed CACAO objects https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-74.md const signedCacao = result?.auths?.[0]; if (signedCacao) { @@ -135,13 +133,13 @@ export class WalletConnectConnector extends WalletConnector { }); if (address && chainId) { - const session = { + const siweSession = { address, chainId: parseInt(chainId, 10) }; - SIWEController.setSession(session); - SIWEController.onSignIn?.(session); + SIWEController.setSession(siweSession); + SIWEController.onSignIn?.(siweSession); } } catch (error) { // eslint-disable-next-line no-console @@ -206,7 +204,6 @@ export class WalletConnectConnector extends WalletConnector { } override getWalletInfo(): WalletInfo | undefined { - console.log('getWalletInfo', this.wallet); return this.wallet; } From 42aba195cb054b296b40581445e6974f2454c63b Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 19 Jun 2025 13:03:24 -0300 Subject: [PATCH 81/91] chore: make watchman optional in clean command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61027d35..5ea114bd 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "prettier": "prettier --check .", "test": "turbo run test --parallel", - "clean": "turbo clean && rm -rf node_modules && watchman watch-del-all", + "clean": "turbo clean && rm -rf node_modules && (watchman watch-del-all || true)", "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore", "playwright:test": "cd apps/native && yarn playwright:test", "changeset:prepublish": "yarn run clean && yarn install && yarn version:update && yarn run lint && yarn run prettier && yarn run build && yarn run test", From e6912aff9721304a270cb4e30c0ca0dbba1f4671 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:55:08 -0300 Subject: [PATCH 82/91] chore: send universal link when connecting --- packages/appkit/src/views/w3m-connecting-view/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 88fbdf6a..468d747b 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -55,8 +55,9 @@ export function ConnectingView() { if (data?.wallet?.id === 'phantom-wallet') { connectPromise = connect('phantom'); } else { - //TODO: check linkmode - connectPromise = connect('walletconnect'); + connectPromise = connect('walletconnect', { + universalLink: data?.wallet?.link_mode ?? undefined + }); } ConnectionController.setWcPromise(connectPromise); await connectPromise; From 42756d07ca0dff0ec63a1c321a53a27e1e9b2ab1 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:28:49 -0300 Subject: [PATCH 83/91] chore: use 1CA only if all networks are evm --- .../src/connectors/WalletConnectConnector.ts | 5 ++++- packages/appkit/src/modal/w3m-modal/index.tsx | 1 + .../src/views/w3m-connecting-view/index.tsx | 15 ++++++++------- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 0575926f..c67dc696 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -99,8 +99,11 @@ export class WalletConnectConnector extends WalletConnector { let session; // SIWE + const isEVMOnly = Object.keys(namespaces ?? {}).length === 1 && namespaces?.['eip155']; const params = await siweConfig?.getMessageParams?.(); - if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { + if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0 && isEVMOnly) { + // 1CA is only supported on EVM + // @ts-ignore const result = await provider.authenticate( { diff --git a/packages/appkit/src/modal/w3m-modal/index.tsx b/packages/appkit/src/modal/w3m-modal/index.tsx index 611be634..88aa22de 100644 --- a/packages/appkit/src/modal/w3m-modal/index.tsx +++ b/packages/appkit/src/modal/w3m-modal/index.tsx @@ -57,6 +57,7 @@ export function AppKit() { if (OptionsController.state.isSiweEnabled) { if ( SIWEController.state.status !== 'success' && + ConnectionsController.state.activeNamespace === 'eip155' && !!ConnectionsController.state.activeAddress ) { await disconnect(); diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 468d747b..14c954f1 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -11,7 +11,8 @@ import { type Platform, OptionsController, ApiController, - EventsController + EventsController, + ConnectionsController } from '@reown/appkit-core-react-native'; import { SIWEController } from '@reown/appkit-siwe-react-native'; import { useAppKit } from '../../AppKitContext'; @@ -45,10 +46,9 @@ export function ConnectingView() { const initializeConnection = async (retry = false) => { try { const { wcPairingExpiry } = ConnectionController.state; - // const { data: routeData } = RouterController.state; + const { data: routeData } = RouterController.state; if (retry || CoreHelperUtil.isPairingExpired(wcPairingExpiry)) { ConnectionController.setWcError(false); - // ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); let connectPromise: Promise; // TODO: check phantom wallet id from cloud @@ -56,16 +56,17 @@ export function ConnectingView() { connectPromise = connect('phantom'); } else { connectPromise = connect('walletconnect', { - universalLink: data?.wallet?.link_mode ?? undefined + universalLink: routeData?.wallet?.link_mode ?? undefined }); } ConnectionController.setWcPromise(connectPromise); await connectPromise; - // await ConnectionController.state.wcPromise; // ConnectorController.setConnectedConnector('WALLET_CONNECT'); - AccountController.setIsConnected(true); - if (OptionsController.state.isSiweEnabled) { + if ( + OptionsController.state.isSiweEnabled && + ConnectionsController.state.activeNamespace === 'eip155' + ) { if (SIWEController.state.status === 'success') { ModalController.close(); } else { From e4f41ef5ad6497d04280083ca51fb417bebd6af5 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:33:03 -0300 Subject: [PATCH 84/91] chore: removed unused import --- packages/appkit/src/views/w3m-connecting-view/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/appkit/src/views/w3m-connecting-view/index.tsx b/packages/appkit/src/views/w3m-connecting-view/index.tsx index 14c954f1..ae589b00 100644 --- a/packages/appkit/src/views/w3m-connecting-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-view/index.tsx @@ -1,7 +1,6 @@ import { useSnapshot } from 'valtio'; import { useEffect, useState } from 'react'; import { - AccountController, ConnectionController, ConstantsUtil, CoreHelperUtil, From fec25b8425742dc960074763aa7fa9a31b800804 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 27 Jun 2025 11:45:54 -0300 Subject: [PATCH 85/91] chore: removed unnecesary exports --- packages/appkit/src/index.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 58cae8b3..35d41bd4 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -1,3 +1,4 @@ +/********** Components **********/ export { AccountButton as AccountButton, type AccountButtonProps @@ -12,21 +13,12 @@ export { type NetworkButtonProps as NetworkButtonProps } from './modal/w3m-network-button'; export { AppKit } from './modal/w3m-modal'; -export { AppKitRouter } from './modal/w3m-router'; -export { AppKitScaffold } from './client'; +/********** Types **********/ export type { LibraryOptions, ScaffoldOptions } from './client'; - export type * from '@reown/appkit-core-react-native'; -export { CoreHelperUtil } from '@reown/appkit-core-react-native'; - -export * from './AppKit'; -export { AppKitProvider } from './AppKitContext'; - export type { AppKitNetwork, Storage } from '@reown/appkit-common-react-native'; -export { WalletConnectConnector } from './connectors/WalletConnectConnector'; - /****** Hooks *******/ export { useAppKit } from './hooks/useAppKit'; export { useProvider } from './hooks/useProvider'; @@ -37,3 +29,11 @@ export { useAppKitEvents, useAppKitEventSubscription } from './hooks/useAppKitEv /********** Networks **********/ export { solana, solanaDevnet, solanaTestnet } from '@reown/appkit-common-react-native'; export { bitcoin, bitcoinTestnet } from '@reown/appkit-common-react-native'; + +/********** Main **********/ +export { createAppKit } from './AppKit'; +export { AppKitProvider } from './AppKitContext'; + +// TODO: REMOVE +/********** To be removed **********/ +export { CoreHelperUtil } from '@reown/appkit-core-react-native'; From 94495befffbe9a48d4f5e6219cae389816eaba4f Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 27 Jun 2025 11:48:08 -0300 Subject: [PATCH 86/91] chore: added todo --- packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx b/packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx index ed73144a..f8a9862e 100644 --- a/packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx +++ b/packages/appkit/src/views/w3m-connecting-siwe-view/index.tsx @@ -96,6 +96,7 @@ export function ConnectingSiweView() { }); }; + //TODO: Add profile image in Avatar return ( Date: Fri, 27 Jun 2025 12:03:22 -0300 Subject: [PATCH 87/91] chore: solved minor issues --- packages/appkit/src/connectors/WalletConnectConnector.ts | 2 +- packages/siwe/src/client.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index c67dc696..3c82f9cd 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -110,7 +110,7 @@ export class WalletConnectConnector extends WalletConnector { ...params, nonce: await siweConfig.getNonce(), methods: namespaces?.['eip155']?.methods, - chains: params.chains.map(chain => `eip155:${chain}`) + chains: params.chains.map(chain => (chain.includes(':') ? chain : `eip155:${chain}`)) }, universalLink ); diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index 644f22c3..ec28e44a 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -108,7 +108,10 @@ export class AppKitSIWEClient { }); const signature = await ConnectionsController.signMessage(activeAddress, message); - const isValid = signature && (await this.verifyMessage({ message, signature })); + if (!signature) { + throw new Error('Error signing SIWE message'); + } + const isValid = await this.verifyMessage({ message, signature }); if (!isValid) { throw new Error('Error verifying SIWE signature'); } From fefde633acff66e8ee844d1d4219aeda7b9de380 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:39:08 -0300 Subject: [PATCH 88/91] chore: use caip addresses for siwe --- apps/native/src/utils/SiweUtils.ts | 3 ++- packages/appkit/src/connectors/WalletConnectConnector.ts | 7 ++++--- packages/common/src/utils/TypeUtil.ts | 8 ++++---- .../siwe/src/__tests__/controllers/SIWEController.test.ts | 5 +++-- packages/siwe/src/client.ts | 7 ++++--- packages/siwe/src/controller/SIWEController.ts | 5 +++-- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/native/src/utils/SiweUtils.ts b/apps/native/src/utils/SiweUtils.ts index 0d5a24df..901c4b6c 100644 --- a/apps/native/src/utils/SiweUtils.ts +++ b/apps/native/src/utils/SiweUtils.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import { CaipNetworkId } from '@reown/appkit-common-react-native'; import { generateRandomBytes32 } from '@walletconnect/utils'; import { createSIWEConfig, @@ -25,7 +26,7 @@ export const siweConfig = createSIWEConfig({ return { domain: 'your.bundle.id', //your bundle id or app id uri: 'redirect://', // your redirect uri - chains: chains.map(chain => chain.id), + chains: chains.map(chain => `eip155:${chain.id}`) as CaipNetworkId[], statement: 'Please sign with your account', iat: new Date().toISOString() }; diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index 3c82f9cd..af0463bf 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -10,7 +10,8 @@ import { type CaipNetworkId, type ConnectOptions, type ConnectorInitOptions, - type Metadata + type Metadata, + type CaipAddress } from '@reown/appkit-common-react-native'; import { getDidAddress, getDidChainId, SIWEController } from '@reown/appkit-siwe-react-native'; @@ -110,7 +111,7 @@ export class WalletConnectConnector extends WalletConnector { ...params, nonce: await siweConfig.getNonce(), methods: namespaces?.['eip155']?.methods, - chains: params.chains.map(chain => (chain.includes(':') ? chain : `eip155:${chain}`)) + chains: params.chains }, universalLink ); @@ -137,7 +138,7 @@ export class WalletConnectConnector extends WalletConnector { if (address && chainId) { const siweSession = { - address, + address: `eip155:${chainId}:${address}` as CaipAddress, chainId: parseInt(chainId, 10) }; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 15a3688e..909f0869 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -343,7 +343,7 @@ export interface Storage { //********** SIWE Types **********// export interface SIWESession { - address: string; + address: CaipAddress; chainId: number; } @@ -356,7 +356,7 @@ export interface SIWECreateMessageArgs { domain: string; nonce: string; uri: string; - address: string; + address: CaipAddress; version: '1'; type?: CacaoHeader['t']; nbf?: string; @@ -368,7 +368,7 @@ export interface SIWECreateMessageArgs { iat?: string; } export type SIWEMessageArgs = { - chains: string[]; + chains: CaipNetworkId[]; methods?: string[]; } & Omit; // Signed Cacao (CAIP-74) @@ -404,7 +404,7 @@ export interface SIWEVerifyMessageArgs { } export interface SIWEClientMethods { - getNonce: (address?: string) => Promise; + getNonce: (address?: CaipAddress) => Promise; createMessage: (args: SIWECreateMessageArgs) => string; verifyMessage: (args: SIWEVerifyMessageArgs) => Promise; getSession: () => Promise; diff --git a/packages/siwe/src/__tests__/controllers/SIWEController.test.ts b/packages/siwe/src/__tests__/controllers/SIWEController.test.ts index e5940a8c..3289e07b 100644 --- a/packages/siwe/src/__tests__/controllers/SIWEController.test.ts +++ b/packages/siwe/src/__tests__/controllers/SIWEController.test.ts @@ -1,7 +1,8 @@ +import type { CaipAddress, CaipNetworkId } from '@reown/appkit-common-react-native'; import { SIWEController } from '../../index'; // -- Mocks ------------------------------------------------------------- -const session = { address: '0x', chainId: 1 }; +const session = { address: 'eip155:1:0x' as CaipAddress, chainId: 1 }; const client = { signIn: () => Promise.resolve(session), options: { @@ -19,7 +20,7 @@ const client = { signOut: async () => Promise.resolve(true), getMessageParams: () => Promise.resolve({ - chains: [1], + chains: ['eip155:1'] as CaipNetworkId[], domain: 'mock-domain', uri: 'mock-uri', nonce: 'mock-nonce' diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index ec28e44a..cc1cfb08 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -7,7 +7,8 @@ import type { SIWEConfig, SIWEClientMethods, SIWESession, - SIWEMessageArgs + SIWEMessageArgs, + CaipAddress } from '@reown/appkit-common-react-native'; import type { SIWEControllerClient } from './controller/SIWEController'; @@ -43,7 +44,7 @@ export class AppKitSIWEClient { this.methods = siweConfigMethods; } - async getNonce(address?: string) { + async getNonce(address?: CaipAddress) { const nonce = await this.methods.getNonce(address); if (!nonce) { throw new Error('siweControllerClient:getNonce - nonce is undefined'); @@ -99,7 +100,7 @@ export class AppKitSIWEClient { } const messageParams = await this.getMessageParams?.(); const message = this.createMessage({ - address: `eip155:${chainId}:${activeAddress}`, + address: activeAddress, chainId, nonce, version: '1', diff --git a/packages/siwe/src/controller/SIWEController.ts b/packages/siwe/src/controller/SIWEController.ts index 19fa655e..3c8d3918 100644 --- a/packages/siwe/src/controller/SIWEController.ts +++ b/packages/siwe/src/controller/SIWEController.ts @@ -5,7 +5,8 @@ import type { SIWEClientMethods, SIWESession, SIWECreateMessageArgs, - SIWEVerifyMessageArgs + SIWEVerifyMessageArgs, + CaipAddress } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // @@ -59,7 +60,7 @@ export const SIWEController = { return state._client; }, - async getNonce(address?: string) { + async getNonce(address?: CaipAddress) { const client = this._getClient(); const nonce = await client.getNonce(address); this.setNonce(nonce); From af047726722371341a2b111e7f0a3e6bdaee7a2d Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:57:16 -0300 Subject: [PATCH 89/91] chore: added extra validation on sign message and estimate gas --- .../core/src/controllers/ConnectionsController.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/core/src/controllers/ConnectionsController.ts b/packages/core/src/controllers/ConnectionsController.ts index 4839985f..f13d741b 100644 --- a/packages/core/src/controllers/ConnectionsController.ts +++ b/packages/core/src/controllers/ConnectionsController.ts @@ -268,12 +268,16 @@ export const ConnectionsController = { async signMessage(address: CaipAddress, message: string) { if (!baseState.activeNamespace) return undefined; + const [namespace, chainId, plainAddress] = address.split(':'); + + if (!namespace || namespace !== baseState.activeNamespace || !chainId || !plainAddress) { + return undefined; + } + const adapter = baseState.connections.get(baseState.activeNamespace)?.adapter; - const evmAddress = address.split(':')[2]; - const chainId = address.split(':')[1]; - if (adapter instanceof EVMAdapter && evmAddress && chainId) { - return adapter.signMessage(evmAddress, message, chainId); + if (adapter instanceof EVMAdapter && plainAddress && chainId) { + return adapter.signMessage(plainAddress, message, chainId); } return undefined; @@ -292,7 +296,7 @@ export const ConnectionsController = { }, async estimateGas(args: any) { - if (!baseState.activeNamespace) return undefined; + if (!baseState.activeNamespace || baseState.activeNamespace !== 'eip155') return undefined; const adapter = baseState.connections.get(baseState.activeNamespace)?.adapter; From 767580f68ebb90f9b52cf2dba2b628d2c2689391 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 27 Jun 2025 13:07:06 -0300 Subject: [PATCH 90/91] chore: extra checks --- packages/siwe/src/client.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index cc1cfb08..133d7bf4 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -85,19 +85,22 @@ export class AppKitSIWEClient { async signIn(): Promise { const { activeAddress, activeCaipNetworkId } = ConnectionsController.state; - if (activeCaipNetworkId && !activeCaipNetworkId.startsWith('eip155')) { + if (!activeCaipNetworkId || !activeCaipNetworkId.startsWith('eip155')) { return Promise.resolve(undefined); } - const nonce = await this.getNonce(activeAddress); - if (!activeAddress) { throw new Error('An address is required to create a SIWE message.'); } + + const nonce = await this.getNonce(activeAddress); + const chainId = NetworkUtil.caipNetworkIdToNumber(activeCaipNetworkId); + if (!chainId) { throw new Error('A chainId is required to create a SIWE message.'); } + const messageParams = await this.getMessageParams?.(); const message = this.createMessage({ address: activeAddress, From bc521c86420bb7a534e27e3260d560bc09134080 Mon Sep 17 00:00:00 2001 From: nacho <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:48:05 -0300 Subject: [PATCH 91/91] chore: reverted caip address changes --- .prettierignore | 1 + .../appkit/src/connectors/WalletConnectConnector.ts | 5 ++--- packages/common/src/utils/TypeUtil.ts | 6 +++--- .../__tests__/controllers/SIWEController.test.ts | 4 ++-- packages/siwe/src/client.ts | 13 +++++++------ packages/siwe/src/controller/SIWEController.ts | 5 ++--- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.prettierignore b/.prettierignore index d02f9259..adb56332 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,7 @@ .changeset/ .github/ .maestro/ +.cursor/ .turbo/ .vscode/ .yarn/ diff --git a/packages/appkit/src/connectors/WalletConnectConnector.ts b/packages/appkit/src/connectors/WalletConnectConnector.ts index af0463bf..3733d943 100644 --- a/packages/appkit/src/connectors/WalletConnectConnector.ts +++ b/packages/appkit/src/connectors/WalletConnectConnector.ts @@ -10,8 +10,7 @@ import { type CaipNetworkId, type ConnectOptions, type ConnectorInitOptions, - type Metadata, - type CaipAddress + type Metadata } from '@reown/appkit-common-react-native'; import { getDidAddress, getDidChainId, SIWEController } from '@reown/appkit-siwe-react-native'; @@ -138,7 +137,7 @@ export class WalletConnectConnector extends WalletConnector { if (address && chainId) { const siweSession = { - address: `eip155:${chainId}:${address}` as CaipAddress, + address, chainId: parseInt(chainId, 10) }; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 909f0869..205e4aa4 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -343,7 +343,7 @@ export interface Storage { //********** SIWE Types **********// export interface SIWESession { - address: CaipAddress; + address: string; chainId: number; } @@ -356,7 +356,7 @@ export interface SIWECreateMessageArgs { domain: string; nonce: string; uri: string; - address: CaipAddress; + address: string; version: '1'; type?: CacaoHeader['t']; nbf?: string; @@ -404,7 +404,7 @@ export interface SIWEVerifyMessageArgs { } export interface SIWEClientMethods { - getNonce: (address?: CaipAddress) => Promise; + getNonce: (address?: string) => Promise; createMessage: (args: SIWECreateMessageArgs) => string; verifyMessage: (args: SIWEVerifyMessageArgs) => Promise; getSession: () => Promise; diff --git a/packages/siwe/src/__tests__/controllers/SIWEController.test.ts b/packages/siwe/src/__tests__/controllers/SIWEController.test.ts index 3289e07b..3fab208f 100644 --- a/packages/siwe/src/__tests__/controllers/SIWEController.test.ts +++ b/packages/siwe/src/__tests__/controllers/SIWEController.test.ts @@ -1,8 +1,8 @@ -import type { CaipAddress, CaipNetworkId } from '@reown/appkit-common-react-native'; +import type { CaipNetworkId } from '@reown/appkit-common-react-native'; import { SIWEController } from '../../index'; // -- Mocks ------------------------------------------------------------- -const session = { address: 'eip155:1:0x' as CaipAddress, chainId: 1 }; +const session = { address: '0x', chainId: 1 }; const client = { signIn: () => Promise.resolve(session), options: { diff --git a/packages/siwe/src/client.ts b/packages/siwe/src/client.ts index 133d7bf4..582fcd89 100644 --- a/packages/siwe/src/client.ts +++ b/packages/siwe/src/client.ts @@ -7,8 +7,7 @@ import type { SIWEConfig, SIWEClientMethods, SIWESession, - SIWEMessageArgs, - CaipAddress + SIWEMessageArgs } from '@reown/appkit-common-react-native'; import type { SIWEControllerClient } from './controller/SIWEController'; @@ -44,7 +43,7 @@ export class AppKitSIWEClient { this.methods = siweConfigMethods; } - async getNonce(address?: CaipAddress) { + async getNonce(address?: string) { const nonce = await this.methods.getNonce(address); if (!nonce) { throw new Error('siweControllerClient:getNonce - nonce is undefined'); @@ -89,11 +88,13 @@ export class AppKitSIWEClient { return Promise.resolve(undefined); } - if (!activeAddress) { + const plainAddress = activeAddress?.split(':')[2]; + + if (!plainAddress) { throw new Error('An address is required to create a SIWE message.'); } - const nonce = await this.getNonce(activeAddress); + const nonce = await this.getNonce(plainAddress); const chainId = NetworkUtil.caipNetworkIdToNumber(activeCaipNetworkId); @@ -103,7 +104,7 @@ export class AppKitSIWEClient { const messageParams = await this.getMessageParams?.(); const message = this.createMessage({ - address: activeAddress, + address: plainAddress, chainId, nonce, version: '1', diff --git a/packages/siwe/src/controller/SIWEController.ts b/packages/siwe/src/controller/SIWEController.ts index 3c8d3918..19fa655e 100644 --- a/packages/siwe/src/controller/SIWEController.ts +++ b/packages/siwe/src/controller/SIWEController.ts @@ -5,8 +5,7 @@ import type { SIWEClientMethods, SIWESession, SIWECreateMessageArgs, - SIWEVerifyMessageArgs, - CaipAddress + SIWEVerifyMessageArgs } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // @@ -60,7 +59,7 @@ export const SIWEController = { return state._client; }, - async getNonce(address?: CaipAddress) { + async getNonce(address?: string) { const client = this._getClient(); const nonce = await client.getNonce(address); this.setNonce(nonce);