Compare commits

..
Author SHA1 Message Date
Richard Ramos 690ba69b16 feat_: retry dns discovery on failure 2024-10-03 12:06:57 -04:00
Sean Hagstrom 1691e2fe34 chore: remove run-tests from wallet-send event tests (#21373) 2024-10-03 12:56:45 +01:00
Parvesh Monu b536d20713 Add syncing notification to System Tab (#21314) 2024-10-03 07:11:10 +05:30
Ulises Manuel ca3720be0a fix(communities): token-gated community info text (#21338) 2024-10-02 14:57:45 -06:00
Ulises Manuel 63d0aaca2a feat(chats): Implement a simpler chat screen (#21313)
* Change `defonce ^:const` -> `def ^:const` in `status-im.contexts.chat.messenger.messages.list.view` to be more REPL friendly

* Remove animations from `status-im.contexts.chat.messenger.messages.pin.banner.style`

* Simplify placeholder for messages

* Replace shared-value with a ratom usage since no animations were performed.

* Fix the style for the new designs and remove now unnecessary animations

* Improve chat component implementation

    The previous implementation has two main problems:
        1. It is creating atoms and ratoms in non-form-2-components.
        2. Conditionally uses a hook: `rn/use-mount` in `lazy-chat-screen`.

    The new implementation fixes the previous problems, improves the readability and passes only the props needed to its children.

* Remove action buttons from the chat screen

* Remove now unused code

* Replace reanimated/view -> rn/view where no animations are made
2024-10-02 14:35:23 -06:00
Brian Sztamfater 3a4b58f32f feat(swap): remove feature flag (#21363)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-10-02 12:55:32 -03:00
Lungu Cristian d748ccdef9 Fix WalletConnect sessions disappearing (#21350)
* feat: moved disconnect logic to function

* ref: moved rpc calls to rpc ns

* ref: moved session approval logic to function

* fix: small fixes for sessions

* test: wallet-connect/on-session-delete event test

* test: added event tests for wc sessions

* fix: require sessions events ns

* fix: the wallet was loaded after wc sometimes
2024-10-02 18:16:20 +03:00
Lungu Cristian 97aff27980 Dapps Typed Data request improvements (#21333)
* feat: added type data flattening + tests

* feat: added typed data flattened ui

* ref: moved typed data utils into their own ns

* ref: small changes & formatting

* feat: show typed data view only for typed data requests

* ref: moved typed-data? into sub

* fix: tests and styling
2024-10-02 13:50:38 +03:00
Brian Sztamfater 0c3e836272 feat: disable swap and bridge options on testnet mode (#21343)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-30 13:13:15 -03:00
Mohsen 60fc4f1b9a [#21132] refactor: improve send transaction content in wc flow (#21266) 2024-09-30 15:11:56 +03:00
Siddarth Kumar b97e2df2fb chore: nuke personal INFURA token 2024-09-30 17:09:35 +05:30
Alexander f539bb5ca2 Swap flows (launch from home / launch from account) (#21269)
* Swap flows (launch from home / launch from account)

* Fixes

* Lint fix
2024-09-30 10:44:25 +02:00
58 changed files with 1171 additions and 1031 deletions
@@ -1,47 +0,0 @@
import { useDerivedValue, withTiming, interpolate } from 'react-native-reanimated';
export function navigationHeaderOpacity(distanceFromListTop, isAllLoaded, isCalculationsComplete, startPosition) {
return useDerivedValue(function () {
'worklet';
const isCalculationsCompleteValue = isCalculationsComplete.value;
if (distanceFromListTop.value < startPosition && isAllLoaded.value) {
return isCalculationsCompleteValue ? withTiming(0) : 0;
} else {
return isCalculationsCompleteValue ? withTiming(1) : 1;
}
});
}
export function navigationHeaderPosition(distanceFromListTop, isAllLoaded, topBarHeight, startPosition) {
return useDerivedValue(function () {
'worklet';
return distanceFromListTop.value < startPosition && isAllLoaded.value ? withTiming(topBarHeight) : withTiming(0);
});
}
export function navigationButtonsCompleteOpacity(isCalculationComplete) {
return useDerivedValue(function () {
'worklet';
return isCalculationComplete.value ? withTiming(1) : 0;
});
}
export function interpolateNavigationViewOpacity(props) {
return useDerivedValue(function () {
'worklet';
const {
'all-loaded?': isAllLoaded,
'end-position': endPosition,
'start-position': startPosition,
'distance-from-list-top': distanceFromListTop,
} = props;
if (isAllLoaded.value) {
return interpolate(distanceFromListTop.value, [startPosition, endPosition], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
} else {
return 1;
}
});
}
@@ -1,15 +0,0 @@
import { useDerivedValue } from 'react-native-reanimated';
export function placeholderOpacity(isCalculationsComplete) {
return useDerivedValue(function () {
'worklet';
return isCalculationsComplete.value ? 0 : 1;
});
}
export function placeholderZIndex(isCalculationsComplete) {
return useDerivedValue(function () {
'worklet';
return isCalculationsComplete.value ? 0 : 2;
});
}
@@ -1,4 +1,5 @@
(ns quo.components.buttons.wallet-ctas.style)
(ns quo.components.buttons.wallet-ctas.style
(:require [quo.foundations.colors :as colors]))
(def inner-container
{:flex-direction :row
@@ -10,3 +11,9 @@
:width 77.75
:justify-content :center
:align-items :center})
(defn action-button-text
[theme disabled?]
(cond-> {:margin-top 4
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}
disabled? (assoc :opacity 0.5)))
@@ -3,28 +3,28 @@
[quo.components.buttons.wallet-button.view :as wallet-button]
[quo.components.buttons.wallet-ctas.style :as style]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[utils.i18n :as i18n]))
(defn action-button
[{:keys [icon text on-press theme accessibility-label]}]
[{:keys [icon text on-press theme accessibility-label disabled?]}]
[rn/view
{:style style/button-container
:accessibility-label accessibility-label}
[wallet-button/view
{:icon icon
:on-press on-press}]
{:icon icon
:disabled? disabled?
:on-press on-press}]
[text/text
{:weight :medium
:size :paragraph-2
:style {:margin-top 4
:color (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)}} text]])
:style (style/action-button-text theme disabled?)} text]])
(defn view
[{:keys [buy-action send-action receive-action bridge-action swap-action container-style]}]
[{:keys [buy-action send-action receive-action bridge-action swap-action bridge-disabled?
swap-disabled? container-style]}]
(let [theme (quo.theme/use-theme)]
[rn/view {:style container-style}
[rn/view {:style style/inner-container}
@@ -52,10 +52,12 @@
:text (i18n/label :t/swap)
:on-press swap-action
:theme theme
:disabled? swap-disabled?
:accessibility-label :swap}])
[action-button
{:icon :i/bridge
:text (i18n/label :t/bridge)
:on-press bridge-action
:theme theme
:disabled? bridge-disabled?
:accessibility-label :bridge}]]]))
+1 -1
View File
@@ -10,7 +10,7 @@
(defn enabled? [v] (= "1" v))
(goog-define INFURA_TOKEN "62f3cee52dbb484198e7339837e263f3")
(goog-define INFURA_TOKEN "")
(goog-define POKT_TOKEN "3ef2018191814b7e1009b8d9")
(goog-define STATUS_BUILD_PROXY_USER "")
(goog-define STATUS_BUILD_PROXY_PASSWORD "")
+9
View File
@@ -619,3 +619,12 @@
(def router-error-code-not-enough-liquidity "WPP-038")
(def router-error-code-price-impact-too-high "WPP-039")
(def router-error-code-not-enough-native-balance "WR-002")
(def ^:const wallet-transaction-estimation
{:unknown 0
:less-than-one-minute 1
:less-than-three-minutes 2
:less-than-five-minutes 3
:more-than-five-minutes 4})
(def ^:const wallet-connect-transaction-refresh-interval-ms 10000)
@@ -17,7 +17,7 @@
user])
(defn view
[{:keys [layout-height]}]
[layout-height]
(let [suggestions (rf/sub [:chat/mention-suggestions])
suggestions? (seq suggestions)
theme (quo.theme/use-theme)
@@ -35,7 +35,7 @@
:default-value default-value}])))
(defn view
[props]
[layout-height]
(let [theme (quo.theme/use-theme)
bottom (safe-area/get-bottom)
input-ref (rn/use-ref-atom nil)
@@ -43,7 +43,7 @@
(rf/dispatch [:chat/set-input-ref value])
(reset! input-ref value)))]
[rn/view {:style {:margin-bottom bottom}}
[mentions/view props]
[mentions/view layout-height]
[quo/separator]
[rn/view {:style {:padding-horizontal 20 :padding-top 20}}
[:<>
@@ -3,15 +3,4 @@
;;;; Navigation
(def ^:const top-bar-height 56)
(def ^:const pinned-banner-height 40)
(def ^:const header-container-top-margin 48)
(def ^:const header-container-radius 20)
(def ^:const header-animation-distance 20)
(def ^:const content-animation-start-position-android 130)
(def ^:const content-animation-start-position-ios 124)
;; Note - We should also consider height of bio for banner animation starting position
;; Todo - Should be updated once New-profile implemation is complete
(def ^:const pinned-banner-animation-start-position 148)
(def ^:const default-extrapolation-option
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})
@@ -1,6 +1,5 @@
(ns status-im.contexts.chat.messenger.messages.list.view
(:require
[clojure.string :as string]
[legacy.status-im.ui.screens.chat.group :as chat.group]
[oops.core :as oops]
[quo.core :as quo]
@@ -25,8 +24,8 @@
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.messages :as worklets]))
(defonce ^:const distance-from-last-message 4)
(defonce ^:const loading-indicator-page-loading-height 100)
(def ^:const distance-from-last-message 4)
(def ^:const loading-indicator-page-loading-height 100)
(defn list-key-fn [{:keys [message-id value]}] (or message-id value))
(defn list-ref [ref] (reset! state/messages-list-ref ref))
@@ -60,44 +59,17 @@
on-loaded])
(if platform/low-device? 700 100)))))
(defn- contact-icon
[{:keys [ens-verified added?]} theme]
(when (or ens-verified added?)
[rn/view
{:style {:margin-left 4}}
(if ens-verified
[quo/icon :i/verified
{:no-color true
:size 20
:color (colors/theme-colors
(colors/custom-color :success 50)
(colors/custom-color :success 60)
theme)}]
(when added?
[quo/icon :i/contact
{:no-color true
:size 20
:color (colors/theme-colors colors/primary-50 colors/primary-60 theme)}]))]))
(defn- skeleton-list-props
[content parent-height animated?]
{:content content
:parent-height parent-height
:animated? animated?})
(defn loading-view
[chat-id {:keys [window-height]}]
[{:keys [chat-id window-height]}]
(let [messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
loading-first-page? (= (count messages) 0)
top-spacing (if loading-first-page?
0
(+ messages.constants/top-bar-height (safe-area/get-top)))
parent-height (if loading-first-page?
window-height
loading-indicator-page-loading-height)]
[rn/view {:padding-top top-spacing}
;; Don't use animated loading skeleton https://github.com/status-im/status-mobile/issues/17426
[quo/skeleton-list (skeleton-list-props :messages parent-height false)]]))
[quo/skeleton-list
{:content :messages
:parent-height parent-height
:animated? false}]))
(defn header-height
[{:keys [insets able-to-send-message? images reply edit link-previews? input-content-height]}]
@@ -125,84 +97,6 @@
(+ (:bottom insets)))
(- 70 (:bottom insets))))
(defn list-header
[insets able-to-send-message?]
(let [header-data {:insets insets
:able-to-send-message? able-to-send-message?
:input-content-height (:input-content-height (rf/sub [:chats/current-chat-input]))
:images (rf/sub [:chats/sending-image])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])
:link-previews? (or (rf/sub [:chats/link-previews?])
(rf/sub [:chats/status-link-previews?]))}]
[rn/view {:style {:height (header-height header-data)}}]))
(defn list-footer-avatar
[{:keys [distance-from-list-top display-name online? profile-picture theme group-chat color
emoji chat-type chat-name empty-chat?]}]
(let [scale (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[1 0.4]
messages.constants/default-extrapolation-option)
top (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[-44 -12]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[16 -8]
messages.constants/default-extrapolation-option)
community-channel? (= chat-type constants/community-chat-type)]
[reanimated/view
{:style (style/header-image scale top left theme)}
(cond
community-channel?
[quo/channel-avatar
{:size :size-80
:full-name chat-name
:customization-color color
:emoji (when-not (string/blank? emoji)
(string/trim emoji))}]
group-chat
[quo/group-avatar
{:customization-color color
:size :size-80
:picture profile-picture
:chat-name chat-name}]
:else
[quo/user-avatar
{:full-name display-name
:online? online?
:profile-picture profile-picture
:size :big}])]))
(defn chat-display-name
[{:keys [distance-from-list-top display-name contact theme empty-chat?]}]
(let [top (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[0 -35]
messages.constants/default-extrapolation-option)
left (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[0 40]
messages.constants/default-extrapolation-option)]
[reanimated/view
{:style (style/user-name-container top left)}
[rn/view
{:style style/user-name}
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:flex-shrink 1}
:number-of-lines 1}
display-name]
[contact-icon contact theme]]]))
(defn actions
[chat-id cover-bg-color]
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
@@ -236,101 +130,12 @@
chat-type
muted?)))}]}]))
(defn bio-and-actions
[{:keys [distance-from-list-top bio chat-id customization-color empty-chat? description]}]
(let [has-bio (seq (or bio description))
top (reanimated/interpolate
distance-from-list-top
[0 (if empty-chat? 0 messages.constants/header-container-top-margin)]
[(if has-bio 8 16) (if has-bio -28 -20)]
messages.constants/default-extrapolation-option)]
[reanimated/view
{:style (style/bio-and-actions top)}
(when has-bio
[quo/text (or bio description)])
[actions chat-id customization-color]]))
(defn footer-component
[{:keys [chat distance-from-list-top customization-color]}]
(let [theme (quo.theme/use-theme)
{:keys [chat-id chat-name emoji chat-type
group-chat color description
empty-chat?]} chat
display-name (cond
(= chat-type constants/one-to-one-chat-type)
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
(= chat-type constants/community-chat-type)
(str "# " chat-name)
:else (str emoji chat-name))
{:keys [bio]} (rf/sub [:contacts/contact-by-identity chat-id])
online? (rf/sub [:visibility-status-updates/online? chat-id])
contact (when-not group-chat
(rf/sub [:contacts/contact-by-address chat-id]))
photo-path (if group-chat
(rf/sub [:chats/group-chat-image chat-id])
(rf/sub [:chats/photo-path chat-id]))
top-margin (+ (safe-area/get-top)
messages.constants/top-bar-height
messages.constants/header-container-top-margin
32)
background-color (colors/theme-colors
(colors/resolve-color customization-color theme 20)
(colors/resolve-color customization-color theme 40)
theme)
bottom (reanimated/interpolate
distance-from-list-top
[0 messages.constants/header-container-top-margin]
[32 -4]
messages.constants/default-extrapolation-option)
background-opacity (reanimated/interpolate
distance-from-list-top
[messages.constants/header-container-top-margin
(+ messages.constants/header-animation-distance
messages.constants/header-container-top-margin)]
[1 0]
messages.constants/default-extrapolation-option)]
[:<>
[reanimated/view
{:style (style/background-container background-color background-opacity top-margin)}]
[reanimated/view {:style (style/header-bottom-container bottom top-margin)}
[rn/view {:style (style/header-bottom-shadow theme)}]
[rn/view {:style (style/header-bottom-part theme)}
[list-footer-avatar
{:distance-from-list-top distance-from-list-top
:display-name display-name
:online? online?
:theme theme
:profile-picture photo-path
:group-chat group-chat
:color color
:emoji emoji
:chat-type chat-type
:chat-name chat-name
:empty-chat? empty-chat?}]
[chat-display-name
{:distance-from-list-top distance-from-list-top
:display-name display-name
:theme theme
:contact contact
:group-chat group-chat
:empty-chat? empty-chat?}]
[bio-and-actions
{:distance-from-list-top distance-from-list-top
:bio bio
:chat-id chat-id
:customization-color customization-color
:description description
:empty-chat? empty-chat?}]]]]))
(defn list-footer
[props]
(let [chat-id (get-in props [:chat :chat-id])
loading-messages? (rf/sub [:chats/loading-messages? chat-id])
(defn more-messages-loader
[{:keys [chat-id] :as props}]
(let [loading-messages? (rf/sub [:chats/loading-messages? chat-id])
all-loaded? (rf/sub [:chats/all-loaded? chat-id])]
[:<>
(if (or loading-messages? (not all-loaded?))
[loading-view chat-id props]
[footer-component props])]))
(when (or loading-messages? (not all-loaded?))
[loading-view props])))
(defn list-group-chat-header
[{:keys [chat-id invitation-admin]}]
@@ -359,8 +164,7 @@
(reset! content-height content-height-new)))))
(defn on-layout
[{:keys [event layout-height distance-atom distance-from-list-top
chat-screen-layout-calculations-complete?]}]
[{:keys [event layout-height distance-atom distance-from-list-top on-layout-done?]}]
(let [layout-height-new (oops/oget event "nativeEvent.layout.height")
change (- layout-height-new @layout-height)
new-distance (- @distance-atom change)]
@@ -368,8 +172,7 @@
(reanimated/set-shared-value distance-from-list-top new-distance)
(reset! distance-atom new-distance)
(reset! layout-height layout-height-new))
(when-not (reanimated/get-shared-value chat-screen-layout-calculations-complete?)
(reanimated/set-shared-value chat-screen-layout-calculations-complete? true))))
(reset! on-layout-done? true)))
(defn on-scroll-fn
[distance-atom layout-height-atom]
@@ -379,28 +182,21 @@
(reset! distance-atom new-distance)))
(defn messages-list-content
[{:keys [insets distance-from-list-top content-height layout-height distance-atom
chat-screen-layout-calculations-complete? chat-list-scroll-y]}]
(let [theme (quo.theme/use-theme)
{:keys [chat-type chat-id] :as chat} (rf/sub [:chats/current-chat-chat-view])
one-to-one-chat? (= chat-type constants/one-to-one-chat-type)
community-channel? (= constants/community-chat-type chat-type)
{contact-customization-color
:customization-color} (when one-to-one-chat?
(rf/sub [:contacts/contact-by-identity chat-id]))
customization-color (cond community-channel?
(or (:color chat)
(rf/sub [:communities/community-color
(:community-id chat)]))
one-to-one-chat? contact-customization-color
:else (or (:color chat) :turquoise))
{:keys [keyboard-shown]} (hooks/use-keyboard)
{window-height :height} (rn/get-window)
context (rf/sub [:chats/current-chat-message-list-view-context])
able-to-send-message? (:able-to-send-message? context)
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
margin-bottom? (and community-channel? (not able-to-send-message?))
recording? (rf/sub [:chats/recording?])]
[{:keys [insets distance-from-list-top layout-height chat-list-scroll-y on-layout-done?]}]
(let [content-height (rn/use-ref-atom 0)
distance-atom (rn/use-ref-atom 0)
theme (quo.theme/use-theme)
{:keys [keyboard-shown]} (hooks/use-keyboard)
{:keys [chat-type chat-id]
:as chat} (rf/sub [:chats/current-chat-chat-view])
community-channel? (= constants/community-chat-type chat-type)
{window-height :height} (rn/get-window)
context (rf/sub [:chats/current-chat-message-list-view-context])
able-to-send-message? (:able-to-send-message? context)
messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
margin-bottom? (and community-channel? (not able-to-send-message?))
recording? (rf/sub [:chats/recording?])
top-margin (+ (safe-area/get-top) messages.constants/top-bar-height)]
[rn/view {:style (style/permission-context-sheet margin-bottom?)}
[rn/view {:style {:flex-shrink 1}} ;; Keeps flat list on top
[reanimated/flat-list
@@ -409,12 +205,9 @@
:bounces false
:header (when (= (:chat-type chat) constants/private-group-chat-type)
[list-group-chat-header chat])
:footer [list-footer
{:theme theme
:chat chat
:window-height window-height
:distance-from-list-top distance-from-list-top
:customization-color customization-color}]
:footer [more-messages-loader
{:chat-id chat-id
:window-height window-height}]
:data messages
:render-data {:theme theme
:context context
@@ -444,15 +237,15 @@
:style {:background-color (colors/theme-colors colors/white
colors/neutral-95
theme)}
:content-container-style {:padding-top distance-from-last-message}
:content-container-style {:padding-top distance-from-last-message
:padding-bottom top-margin}
:inverted true
:on-layout #(on-layout
{:event %
:layout-height layout-height
:distance-atom distance-atom
{:event %
:layout-height layout-height
:distance-atom distance-atom
:distance-from-list-top distance-from-list-top
:chat-screen-layout-calculations-complete?
chat-screen-layout-calculations-complete?})
:on-layout-done? on-layout-done?})
:scroll-enabled (not recording?)
:content-inset-adjustment-behavior :never
:scroll-indicator-insets {:right 1}}]]]))
@@ -1,56 +1,43 @@
(ns status-im.contexts.chat.messenger.messages.navigation.style
(:require
[quo.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(:require [quo.foundations.colors :as colors]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
(defn navigation-view
[navigation-view-height pinned-banner-height]
[navigation-view-height]
{:top 0
:left 0
:right 0
:position :absolute
:pointer-events :box-none
:height (+ navigation-view-height pinned-banner-height)
:height (+ navigation-view-height messages.constants/pinned-banner-height)
:z-index 1})
(defn animated-background-view
[background-opacity navigation-view-height]
(reanimated/apply-animations-to-style
{:opacity background-opacity}
{:height navigation-view-height
:top 0
:left 0
:right 0
:overflow :hidden
:position :absolute}))
(defn background
[navigation-view-height]
{:height navigation-view-height
:top 0
:left 0
:right 0
:overflow :hidden
:position :absolute})
(defn header-container
[top-insets top-bar-height]
[top-insets]
{:margin-top top-insets
:flex-direction :row
:padding-horizontal 20
:overflow :hidden
:height top-bar-height
:height messages.constants/top-bar-height
:align-items :center})
(defn button-animation-container
[opacity-value]
(reanimated/apply-animations-to-style
{:opacity opacity-value}
{}))
;;;; Content
(defn header-content-container
[header-opacity header-position]
(reanimated/apply-animations-to-style
{:transform [{:translate-y header-position}]
:opacity header-opacity}
{:flex-direction :row
:align-items :center
:flex 1
:margin-horizontal 12
:height 40}))
(def header-content-container
{:flex-direction :row
:align-items :center
:flex 1
:margin-horizontal 12
:height 40})
(def header-text-container
{:margin-left 8})
@@ -7,7 +7,6 @@
[re-frame.db]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.common.home.actions.view :as actions]
[status-im.constants :as constants]
@@ -15,44 +14,26 @@
[status-im.contexts.chat.messenger.messages.navigation.style :as style]
[status-im.contexts.chat.messenger.messages.pin.banner.view :as pin.banner]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.messages :as messages.worklets]
[utils.worklets.chat.messenger.navigation :as worklets]))
[utils.re-frame :as rf]))
(defn header-content-container
[{:keys [chat distance-from-list-top all-loaded? chat-screen-layout-calculations-complete?]}]
(let [theme (quo.theme/use-theme)
{:keys [chat-id group-chat chat-type chat-name
emoji color]} chat
display-name (cond
(= chat-type constants/one-to-one-chat-type)
(first (rf/sub
[:contacts/contact-two-names-by-identity
chat-id]))
(= chat-type constants/community-chat-type)
(str "# " chat-name)
:else (str emoji chat-name))
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
photo-path (if group-chat
(rf/sub [:chats/group-chat-image chat-id])
(rf/sub [:chats/photo-path chat-id]))
header-opacity (worklets/navigation-header-opacity
distance-from-list-top
all-loaded?
chat-screen-layout-calculations-complete?
(if platform/ios?
messages.constants/content-animation-start-position-ios
messages.constants/content-animation-start-position-android))
header-position (worklets/navigation-header-position
distance-from-list-top
all-loaded?
messages.constants/top-bar-height
(if platform/ios?
messages.constants/content-animation-start-position-ios
messages.constants/content-animation-start-position-android))
community-channel? (= chat-type constants/community-chat-type)]
[reanimated/view
{:style (style/header-content-container header-opacity header-position)}
[{:keys [chat-id group-chat chat-type chat-name emoji color] :as _chat}]
(let [theme (quo.theme/use-theme)
display-name (cond
(= chat-type constants/one-to-one-chat-type)
(first (rf/sub [:contacts/contact-two-names-by-identity chat-id]))
(= chat-type constants/community-chat-type)
(str "# " chat-name)
:else
(str emoji chat-name))
online? (when-not group-chat (rf/sub [:visibility-status-updates/online? chat-id]))
photo-path (if group-chat
(rf/sub [:chats/group-chat-image chat-id])
(rf/sub [:chats/photo-path chat-id]))
community-channel? (= chat-type constants/community-chat-type)]
[rn/view {:style style/header-content-container}
(cond
community-channel?
[quo/channel-avatar
@@ -90,30 +71,11 @@
(i18n/label
(if online? :t/online :t/offline))])]]))
(defn animated-background-and-pinned-banner
[{:keys [chat-id navigation-view-height distance-from-list-top all-loaded?]}]
(let [theme (quo.theme/use-theme)
animation-distance messages.constants/header-animation-distance
props {:distance-from-list-top distance-from-list-top
:all-loaded? all-loaded?}
background-opacity (worklets/interpolate-navigation-view-opacity
(assoc props
:start-position
messages.constants/header-container-top-margin
:end-position
(+ animation-distance
messages.constants/header-container-top-margin)))
banner-opacity (worklets/interpolate-navigation-view-opacity
(assoc props
:start-position
(+ navigation-view-height
messages.constants/pinned-banner-animation-start-position)
:end-position
(+ animation-distance
navigation-view-height
messages.constants/pinned-banner-animation-start-position)))]
(defn header-background
[{:keys [chat-id navigation-view-height]}]
(let [theme (quo.theme/use-theme)]
[:<>
[reanimated/view {:style (style/animated-background-view background-opacity navigation-view-height)}
[rn/view {:style (style/background navigation-view-height)}
[quo/blur
{:style {:flex 1}
:blur-amount 20
@@ -121,58 +83,37 @@
:overlay-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur theme)
:blur-radius (if platform/ios? 20 10)}]]
[pin.banner/banner
{:chat-id chat-id
:banner-opacity banner-opacity
:top-offset navigation-view-height}]]))
{:chat-id chat-id
:top-offset navigation-view-height}]]))
(defn view
[{:keys [distance-from-list-top chat-screen-layout-calculations-complete?]}]
[]
(let [{:keys [chat-id chat-type]
:as chat} (rf/sub [:chats/current-chat-chat-view])
all-loaded? (reanimated/use-shared-value false)
all-loaded-sub (rf/sub [:chats/all-loaded? chat-id])
top-insets (safe-area/get-top)
top-bar-height messages.constants/top-bar-height
navigation-view-height (+ top-bar-height top-insets)
navigation-buttons-opacity (worklets/navigation-buttons-complete-opacity
chat-screen-layout-calculations-complete?)
reached-threshold? (messages.worklets/use-messages-scrolled-to-threshold
distance-from-list-top
top-bar-height)
button-background (if reached-threshold? :photo :blur)]
(rn/use-effect (fn [] (reanimated/set-shared-value all-loaded? all-loaded-sub))
[all-loaded-sub])
[rn/view
{:style (style/navigation-view navigation-view-height messages.constants/pinned-banner-height)}
[animated-background-and-pinned-banner
:as chat} (rf/sub [:chats/current-chat-chat-view])
top-insets (safe-area/get-top)
navigation-view-height (+ top-insets messages.constants/top-bar-height)]
[rn/view {:style (style/navigation-view navigation-view-height)}
[header-background
{:chat-id chat-id
:navigation-view-height navigation-view-height
:distance-from-list-top distance-from-list-top
:all-loaded? all-loaded?}]
[rn/view {:style (style/header-container top-insets top-bar-height)}
[reanimated/view {:style (style/button-animation-container navigation-buttons-opacity)}
[quo/button
{:icon-only? true
:type :grey
:background button-background
:size 32
:accessibility-label :back-button
:on-press #(rf/dispatch [:navigate-back])}
(if (= chat-type constants/community-chat-type) :i/arrow-left :i/close)]]
[header-content-container
{:chat chat
:distance-from-list-top distance-from-list-top
:all-loaded? all-loaded?
:chat-screen-layout-calculations-complete? chat-screen-layout-calculations-complete?}]
[reanimated/view {:style (style/button-animation-container navigation-buttons-opacity)}
[quo/button
{:icon-only? true
:type :grey
:background button-background
:size 32
:accessibility-label :options-button
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [actions/chat-actions chat true])}]))}
:i/options]]]]))
:navigation-view-height navigation-view-height}]
[rn/view {:style (style/header-container top-insets)}
[quo/button
{:icon-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :back-button
:on-press #(rf/dispatch [:navigate-back])}
(if (= chat-type constants/community-chat-type) :i/arrow-left :i/close)]
[header-content-container chat]
[quo/button
{:icon-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :options-button
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [actions/chat-actions chat true])}]))}
:i/options]]]))
@@ -1,6 +1,5 @@
(ns status-im.contexts.chat.messenger.messages.pin.banner.style
(:require
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
(def container
@@ -10,8 +9,6 @@
:right 0
:height messages.constants/pinned-banner-height})
(defn container-animated-style
[top-offset banner-opacity]
(reanimated/apply-animations-to-style
{:opacity banner-opacity}
(assoc container :top top-offset)))
(defn container-with-top-offset
[top-offset]
(assoc container :top top-offset))
@@ -2,30 +2,26 @@
(:require
[quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.messages.pin.banner.style :as style]
[utils.re-frame :as rf]))
(defn f-banner
[{:keys [chat-id banner-opacity top-offset]} latest-pin-text pins-count]
(let [theme (quo.theme/use-theme)]
[reanimated/view {:style (style/container-animated-style top-offset banner-opacity)}
[quo/blur
{:style style/container
:blur-radius (if platform/ios? 20 10)
:blur-type theme
:blur-amount 20}]
[quo/banner
{:latest-pin-text latest-pin-text
:pins-count pins-count
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}]]))
(defn banner
[{:keys [chat-id] :as props}]
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
[{:keys [chat-id top-offset]}]
(let [theme (quo.theme/use-theme)
latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
pins-count (rf/sub [:chats/pin-messages-count chat-id])]
(when (> pins-count 0)
[:f> f-banner props latest-pin-text pins-count])))
[rn/view {:style (style/container-with-top-offset top-offset)}
[quo/blur
{:style style/container
:blur-radius (if platform/ios? 20 10)
:blur-type theme
:blur-amount 20}]
[quo/banner
{:latest-pin-text latest-pin-text
:pins-count pins-count
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}]])))
@@ -7,7 +7,7 @@
[utils.worklets.chat.messenger.composer :as worklets]))
(defn button
[{:keys [chat-list-scroll-y]}]
[chat-list-scroll-y]
(let [{window-height :height} (rn/get-window)
scroll-down-button-opacity (worklets/scroll-down-button-opacity
chat-list-scroll-y
@@ -19,57 +19,50 @@
[utils.re-frame :as rf]))
(defn- footer
[props]
[layout-height]
(let [current-chat-id (rf/sub [:chats/current-chat-id])
able-to-send-message? (rf/sub [:chats/able-to-send-message?])]
(when-not (string/blank? current-chat-id)
(if able-to-send-message?
[composer/view props]
[composer/view layout-height]
[contact-requests.bottom-drawer/view {:contact-id current-chat-id}]))))
(defn- chat-screen
[{:keys [insets] :as props}]
[on-layout-done?]
(let [theme (quo.theme/use-theme)
layout-height (rn/use-ref-atom 0)
distance-from-list-top (reanimated/use-shared-value 0)
chat-list-scroll-y (reanimated/use-shared-value 0)
alert-banners-top-margin (rf/sub [:alert-banners/top-margin])
chat-exist? (rf/sub [:chats/current-chat-exist?])]
(when chat-exist?
[rn/keyboard-avoiding-view
{:style (style/keyboard-avoiding-container theme)
:keyboard-vertical-offset (- (if platform/ios? alert-banners-top-margin 0) (:bottom insets))}
[:<>
[list.view/messages-list-content props]
[scroll-to-bottom/button props]]
[messages.navigation/view props]
[footer props]])))
(defn lazy-chat-screen
[chat-screen-layout-calculations-complete? *screen-loaded?*]
(let [screen-loaded? (rf/sub [:shell/chat-screen-loaded?])
distance-from-list-top (reanimated/use-shared-value 0)
chat-list-scroll-y (reanimated/use-shared-value 0)
props {:insets (safe-area/get-insets)
:content-height (atom 0)
:layout-height (atom 0)
:distance-atom (atom 0)
:distance-from-list-top distance-from-list-top
:chat-list-scroll-y chat-list-scroll-y
:chat-screen-layout-calculations-complete?
chat-screen-layout-calculations-complete?}]
(when *screen-loaded?*
(rn/use-mount #(reset! *screen-loaded?* true)))
(when-not (if *screen-loaded?* @*screen-loaded?* screen-loaded?)
(reanimated/set-shared-value chat-screen-layout-calculations-complete? false)
(reanimated/set-shared-value distance-from-list-top 0)
(reanimated/set-shared-value chat-list-scroll-y 0))
(when (if *screen-loaded?* @*screen-loaded?* screen-loaded?)
[chat-screen props])))
insets (safe-area/get-insets)]
[rn/keyboard-avoiding-view
{:style (style/keyboard-avoiding-container theme)
:keyboard-vertical-offset (- (if platform/ios? alert-banners-top-margin 0)
(:bottom insets))}
[:<>
[list.view/messages-list-content
{:insets insets
:distance-from-list-top distance-from-list-top
:chat-list-scroll-y chat-list-scroll-y
:layout-height layout-height
:on-layout-done? on-layout-done?}]
[scroll-to-bottom/button chat-list-scroll-y]]
[messages.navigation/view]
[footer layout-height]]))
(defn chat
[]
(let [chat-screen-layout-calculations-complete? (reanimated/use-shared-value false)
jump-to-enabled? (ff/enabled? ::ff/shell.jump-to)
*screen-loaded?* (when-not jump-to-enabled?
(reagent/atom false))]
[:<>
[lazy-chat-screen chat-screen-layout-calculations-complete? *screen-loaded?*]
[placeholder.view/view chat-screen-layout-calculations-complete?]]))
(let [on-layout-done? (reagent/atom false)
first-render-done? (reagent/atom false)]
(fn []
(let [chat-exists? (rf/sub [:chats/current-chat-exist?])
jump-to-enabled? (ff/enabled? ::ff/shell.jump-to)
screen-loaded-for-jump-to? (rf/sub [:shell/chat-screen-loaded?])
screen-loaded? (if jump-to-enabled?
screen-loaded-for-jump-to?
@first-render-done?)]
(rn/use-mount #(reset! first-render-done? true))
[:<>
(when (and chat-exists? screen-loaded?)
[chat-screen on-layout-done?])
[placeholder.view/view on-layout-done?]]))))
@@ -1,16 +1,15 @@
(ns status-im.contexts.chat.messenger.placeholder.style
(:require [quo.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
[react-native.safe-area :as safe-area]))
(defn container
[top opacity z-index theme]
(reanimated/apply-animations-to-style
{:opacity opacity
:z-index z-index}
{:position :absolute
:padding-top top
:top 0
:left 0
:right 0
:bottom 0
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}))
[theme on-layout-done?]
{:position :absolute
:padding-top (safe-area/get-top)
:top 0
:left 0
:right 0
:bottom 0
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:opacity (if on-layout-done? 0 1)
:z-index (if on-layout-done? 0 2)})
@@ -3,11 +3,8 @@
[quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.contexts.chat.messenger.placeholder.style :as style]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.placeholder :as worklets]))
[utils.re-frame :as rf]))
(defn- loading-skeleton
[]
@@ -17,13 +14,10 @@
:animated? false}])
(defn view
[chat-screen-layout-calculations-complete?]
[on-layout-done?]
(let [theme (quo.theme/use-theme)
top (safe-area/get-top)
chat-exist? (rf/sub [:chats/current-chat-exist?])
opacity (worklets/placeholder-opacity chat-screen-layout-calculations-complete?)
z-index (worklets/placeholder-z-index chat-screen-layout-calculations-complete?)]
[reanimated/view {:style (style/container top opacity z-index theme)}
chat-exist? (rf/sub [:chats/current-chat-exist?])]
[rn/view {:style (style/container theme @on-layout-done?)}
(when-not chat-exist?
[quo/page-nav
{:icon-name :i/arrow-left
@@ -119,38 +119,29 @@
:icon-left :i/communities}
(i18n/label :t/request-to-join)])
(defn- info-button-handler
(defn token-gated-communities-info
[]
(rf/dispatch
[:show-bottom-sheet
{:content
(fn []
[quo/documentation-drawers
{:title (i18n/label :t/token-gated-communities)
:show-button? true
:button-label (i18n/label :t/read-more)
:button-icon :info}
[quo/text {:size :paragraph-2}
(i18n/label :t/token-gated-communities-info)]])}]))
[quo/documentation-drawers {:title (i18n/label :t/token-gated-communities)}
[quo/text {:size :paragraph-2}
(i18n/label :t/token-gated-communities-info)]])
(defn- token-requirements
[{:keys [id color role-permissions?]}]
(let [{:keys [can-request-access?
no-member-permission?
tokens
networks-not-supported?
highest-permission-role]} (rf/sub [:community/token-gated-overview id])
highest-role-text
(i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))
on-press (rn/use-callback (fn []
(if config/community-accounts-selection-enabled?
(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
(rf/dispatch [:open-modal :community-requests-to-join
{:id
id}])))
[id])]
(let [{:keys [can-request-access? no-member-permission? networks-not-supported?
highest-permission-role
tokens]} (rf/sub [:community/token-gated-overview id])
highest-role-text (i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))
on-press (rn/use-callback
(fn []
(if config/community-accounts-selection-enabled?
(rf/dispatch [:open-modal :community-account-selection-sheet
{:community-id id}])
(rf/dispatch [:open-modal :community-requests-to-join
{:id id}])))
[id])
on-press-info #(rf/dispatch
[:show-bottom-sheet {:content token-gated-communities-info}])]
(cond
networks-not-supported?
[network-not-supported]
@@ -160,13 +151,12 @@
:else
[quo/community-token-gating
{:role highest-role-text
:tokens tokens
{:role highest-role-text
:tokens tokens
:community-color color
:satisfied? can-request-access?
:on-press on-press
:on-press-info
info-button-handler}])))
:satisfied? can-request-access?
:on-press on-press
:on-press-info on-press-info}])))
(defn- join-community
[{:keys [id joined permissions] :as community}]
@@ -102,8 +102,6 @@
[:dispatch-later [{:ms 1500 :dispatch [:profile.login/non-critical-initialization]}]]
[:dispatch [:network/check-expensive-connection]]
[:profile.settings/get-profile-picture key-uid]
(when (ff/enabled? ::ff/wallet.wallet-connect)
[:dispatch [:wallet-connect/init]])
(when notifications-enabled?
[:effects/push-notifications-enable])]})))
@@ -28,7 +28,8 @@
;; TODO: Replace with correct enum values once status-go implements them.
(def ^:const tx 66612)
(def ^:const system 66614)
(def ^:const system
#{new-installation-received new-installation-created})
(def ^:const membership
"Membership is like a logical group of notifications with different types, i.e.
+20 -18
View File
@@ -26,7 +26,8 @@
(let [selected-tab (or (rf/sub [:wallet/account-tab]) first-tab-id)
{:keys [name color formatted-balance
watch-only?]} (rf/sub [:wallet/current-viewing-account])
customization-color (rf/sub [:profile/customization-color])]
customization-color (rf/sub [:profile/customization-color])
testnet-mode? (rf/sub [:profile/test-networks-enabled?])]
(hot-reload/use-safe-unmount (fn []
(rf/dispatch [:wallet/close-account-page])
(rf/dispatch [:wallet/clean-current-viewing-account])))
@@ -46,23 +47,24 @@
[quo/wallet-graph {:time-frame :empty}])
(when (not watch-only?)
[quo/wallet-ctas
{:container-style style/cta-buttons
:send-action (fn []
(rf/dispatch [:wallet/clean-send-data])
(rf/dispatch [:wallet/wizard-navigate-forward
{:start-flow? true
:flow-id :wallet-send-flow}]))
:receive-action #(rf/dispatch [:open-modal :screen/wallet.share-address
{:status :receive}])
:buy-action #(rf/dispatch [:show-bottom-sheet
{:content buy-token/view}])
:bridge-action (fn []
(rf/dispatch [:wallet/clean-send-data])
(rf/dispatch [:wallet/start-bridge]))
:swap-action (when (ff/enabled? ::ff/wallet.swap)
(fn []
(rf/dispatch [:wallet.tokens/get-token-list])
(rf/dispatch [:open-modal :screen/wallet.swap-select-asset-to-pay])))}])
{:container-style style/cta-buttons
:send-action (fn []
(rf/dispatch [:wallet/clean-send-data])
(rf/dispatch [:wallet/wizard-navigate-forward
{:start-flow? true
:flow-id :wallet-send-flow}]))
:receive-action #(rf/dispatch [:open-modal :screen/wallet.share-address
{:status :receive}])
:buy-action #(rf/dispatch [:show-bottom-sheet
{:content buy-token/view}])
:bridge-action (fn []
(rf/dispatch [:wallet/clean-send-data])
(rf/dispatch [:wallet/start-bridge]))
:swap-action (fn []
(rf/dispatch [:wallet.tokens/get-token-list])
(rf/dispatch [:open-modal :screen/wallet.swap-select-asset-to-pay]))
:bridge-disabled? testnet-mode?
:swap-disabled? testnet-mode?}])
[quo/tabs
{:style style/tabs
:size 32
@@ -1,6 +1,5 @@
(ns status-im.contexts.wallet.common.token-value.view
(:require [quo.core :as quo]
[status-im.common.not-implemented :as not-implemented]
[status-im.contexts.wallet.send.utils :as send-utils]
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
[status-im.feature-flags :as ff]
@@ -18,15 +17,15 @@
:right-icon :i/external})
(defn- action-send
[send-params entry-point]
[{:keys [disabled?] :as params} entry-point]
{:icon :i/send
:accessibility-label :send
:label (i18n/label :t/send)
:disabled? (:disabled? send-params)
:disabled? disabled?
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:wallet/clean-send-data])
(rf/dispatch [:wallet/set-token-to-send send-params entry-point]))})
(rf/dispatch [:wallet/set-token-to-send params entry-point]))})
(defn- action-receive
[selected-account?]
@@ -38,21 +37,26 @@
#(rf/dispatch [:open-modal :screen/share-shell {:initial-tab :wallet}]))})
(defn- action-bridge
[bridge-params]
[{:keys [bridge-disabled? testnet-mode?] :as params}]
{:icon :i/bridge
:accessibility-label :bridge
:label (i18n/label :t/bridge)
:disabled? (:bridge-disabled? bridge-params)
:disabled? (or testnet-mode? bridge-disabled?)
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:wallet/bridge-select-token bridge-params]))})
(rf/dispatch [:wallet/bridge-select-token params]))})
(defn- action-swap
[]
[{:keys [token token-symbol testnet-mode?]}]
{:icon :i/swap
:accessibility-label :swap
:label (i18n/label :t/swap)
:on-press #(not-implemented/alert)})
:disabled? testnet-mode?
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch [:wallet.swap/start
{:asset-to-pay (or token {:symbol token-symbol})
:open-new-screen? true}]))})
(defn- action-manage-tokens
[watch-only?]
@@ -71,47 +75,38 @@
(defn token-value-drawer
[token watch-only? entry-point]
(let [token-symbol (:token token)
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
{:query token-symbol}]))
fiat-unformatted-value (get-in token [:values :fiat-unformatted-value])
has-balance? (money/greater-than fiat-unformatted-value (money/bignumber "0"))
selected-account? (rf/sub [:wallet/current-viewing-account-address])
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
send-params (if selected-account?
{:token token-data
:stack-id :screen/wallet.accounts
:disabled? (not has-balance?)
:start-flow? true
:owners token-owners}
{:token-symbol token-symbol
:stack-id :wallet-stack
:start-flow? true
:owners token-owners})
bridge-params (if selected-account?
{:token token-data
:bridge-disabled? (or (not has-balance?)
(send-utils/bridge-disabled? token-symbol))
:stack-id :screen/wallet.accounts
:start-flow? true
:owners token-owners}
{:token-symbol token-symbol
:bridge-disabled? (send-utils/bridge-disabled? token-symbol)
:stack-id :wallet-stack
:start-flow? true
:owners token-owners})]
(let [token-symbol (:token token)
token-data (first (rf/sub [:wallet/current-viewing-account-tokens-filtered
{:query token-symbol}]))
selected-account (rf/sub [:wallet/current-viewing-account-address])
token-owners (rf/sub [:wallet/operable-addresses-with-token-symbol token-symbol])
testnet-mode? (rf/sub [:profile/test-networks-enabled?])
params (cond-> {:start-flow? true
:owners token-owners
:testnet-mode? testnet-mode?}
selected-account
(assoc :token token-data
:stack-id :screen/wallet.accounts
:has-balance? (-> (get-in token [:values :fiat-unformatted-value])
(money/greater-than (money/bignumber "0"))))
(not selected-account)
(assoc :token-symbol token-symbol
:stack-id :wallet-stack))]
[quo/action-drawer
[(cond->> [(when (ff/enabled? ::ff/wallet.assets-modal-manage-tokens)
(action-manage-tokens watch-only?))
(when (ff/enabled? ::ff/wallet.assets-modal-hide)
(action-hide))]
(not watch-only?) (concat [(action-buy)
(when (seq token-owners)
(action-send send-params entry-point))
(action-receive selected-account?)
(when (ff/enabled? ::ff/wallet.swap) (action-swap))
(when (seq (seq token-owners))
(action-bridge bridge-params))]))]]))
(not watch-only?)
(concat [(action-buy)
(when (seq token-owners)
(action-send params entry-point))
(action-receive selected-account)
(action-swap params)
(when (seq token-owners)
(action-bridge (assoc params
:bridge-disabled?
(send-utils/bridge-disabled? token-symbol))))]))]]))
(defn view
[item _ _ {:keys [watch-only? entry-point]}]
@@ -493,3 +493,13 @@
:available-balance (calculate-total-token-balance token)
:total-balance (calculate-total-token-balance token chain-ids)))
tokens))
(defn estimated-time-format
"Formats the estimated time for a transaction"
[estimated-time]
(condp = estimated-time
(:unknown constants/wallet-transaction-estimation) ">5"
(:less-than-one-minute constants/wallet-transaction-estimation) "<1"
(:less-than-three-minutes constants/wallet-transaction-estimation) "1-3"
(:less-than-five-minutes constants/wallet-transaction-estimation) "3-5"
">5"))
+4 -3
View File
@@ -345,8 +345,7 @@
:Prod
data-store/rpc->network)
data)}]
{:fx [(when (ff/enabled? ::ff/wallet.swap)
[:dispatch [:wallet.tokens/get-token-list]])]
{:fx [[:dispatch [:wallet.tokens/get-token-list]]]
:db (assoc-in db [:wallet :networks] network-data)})))
(rf/reg-event-fx
@@ -478,7 +477,9 @@
[:dispatch [:wallet/get-ethereum-chains]]
[:dispatch [:wallet/get-accounts]]
[:dispatch [:wallet/get-keypairs]]
[:dispatch [:wallet/get-saved-addresses]]]}))
[:dispatch [:wallet/get-saved-addresses]]
(when (ff/enabled? ::ff/wallet.wallet-connect)
[:dispatch-later [{:ms 500 :dispatch [:wallet-connect/init]}]])]}))
(rf/reg-event-fx :wallet/share-account
(fn [_ [{:keys [content title]}]]
@@ -1,6 +1,6 @@
(ns status-im.contexts.wallet.send.events-test
(:require
[cljs.test :refer-macros [is testing run-tests]]
[cljs.test :refer-macros [is testing]]
matcher-combinators.test
[re-frame.db :as rf-db]
[status-im.contexts.wallet.common.utils.networks :as network-utils]
@@ -710,5 +710,3 @@
:stack-id stack-id
:start-flow? start-flow?
:netork-details network-details}])))))))
(run-tests)
+29 -14
View File
@@ -14,22 +14,36 @@
[utils.number :as number]))
(rf/reg-event-fx :wallet.swap/start
(fn [{:keys [db]} [{:keys [asset-to-pay asset-to-receive network]}]]
(let [asset-to-receive' (or asset-to-receive
(swap-utils/select-asset-to-receive
(:wallet db)
(:profile/profile db)
asset-to-pay))
network' (or network
(swap-utils/select-network asset-to-pay))]
(fn [{:keys [db]} [{:keys [network open-new-screen?] :as data}]]
(let [{:keys [wallet]} db
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
account (swap-utils/wallet-account wallet)
asset-to-pay (if (get-in data [:asset-to-pay :networks])
(:asset-to-pay data)
(swap-utils/select-asset-to-pay-by-symbol
{:wallet wallet
:account account
:test-networks-enabled? test-networks-enabled?
:token-symbol (get-in data [:asset-to-pay :symbol])}))
asset-to-receive (or (:asset-to-receive data)
(swap-utils/select-default-asset-to-receive
{:wallet wallet
:account account
:test-networks-enabled? test-networks-enabled?
:asset-to-pay asset-to-pay}))
network' (or network
(swap-utils/select-network asset-to-pay))]
{:db (-> db
(assoc-in [:wallet :ui :swap :asset-to-pay] asset-to-pay)
(assoc-in [:wallet :ui :swap :asset-to-receive] asset-to-receive')
(assoc-in [:wallet :ui :swap :asset-to-receive] asset-to-receive)
(assoc-in [:wallet :ui :swap :network] network'))
:fx (if network'
[[:dispatch
[:navigate-to-within-stack
[:screen/wallet.setup-swap :screen/wallet.swap-select-asset-to-pay]]]
[[:dispatch [:wallet/switch-current-viewing-account (:address account)]]
[:dispatch
(if open-new-screen?
[:navigate-to :screen/wallet.setup-swap]
[:navigate-to-within-stack
[:screen/wallet.setup-swap :screen/wallet.swap-select-asset-to-pay]])]
[:dispatch [:wallet.swap/set-default-slippage]]]
[[:dispatch
[:show-bottom-sheet
@@ -41,8 +55,9 @@
(rf/dispatch
[:wallet.swap/start
{:asset-to-pay asset-to-pay
:asset-to-receive asset-to-receive'
:network network}]))}])}]]])})))
:asset-to-receive asset-to-receive
:network network
:open-new-screen? open-new-screen?}]))}])}]]])})))
(rf/reg-event-fx :wallet.swap/select-asset-to-pay
(fn [{:keys [db]} [{:keys [token]}]]
@@ -21,7 +21,9 @@
(defn- assets-view
[search-text on-change-text]
(let [on-token-press (fn [token]
(rf/dispatch [:wallet.swap/start {:asset-to-pay token}]))]
(rf/dispatch [:wallet.swap/start
{:asset-to-pay token
:open-new-screen? false}]))]
[:<>
[search-input search-text on-change-text]
[asset-list/view
+50 -11
View File
@@ -30,22 +30,61 @@
:else
(i18n/label :t/something-went-wrong-please-try-again-later)))
(defn select-asset-to-receive
[wallet profile asset-to-pay]
(let [wallet-address (get-in wallet [:current-viewing-account-address])
account (-> wallet
:accounts
vals
(utils/get-account-by-address wallet-address))
test-networks-enabled? (get profile :test-networks-enabled?)
networks (-> (get-in wallet
[:wallet :networks (if test-networks-enabled? :test :prod)])
(network-utils/sorted-networks-with-details))]
(defn- first-operable-account
[accounts]
(->> accounts
vals
(remove :watch-only?)
(filter :operable?)
(sort-by :position)
first))
(defn wallet-account
"Picks the account that's gonna be used for the swap operation.
It's gonna be either the preselected account defined by
`[:wallet :current-viewing-account-address]` in `db`
or the first operable account."
[wallet]
(if-let [wallet-address (get wallet :current-viewing-account-address)]
(-> wallet
:accounts
vals
(utils/get-account-by-address wallet-address))
(-> wallet :accounts first-operable-account)))
(defn select-asset-to-pay-by-symbol
"Selects an asset to pay by token symbol.
It's used for cases when only token symbol is available and the information
about token needs to be extracted from the database.
That happens when token is being selected on the home screen and
it basically indicates that no account pre-selection was made."
[{:keys [wallet account test-networks-enabled? token-symbol]}]
(let [networks (-> (get-in wallet [:networks (if test-networks-enabled? :test :prod)])
(network-utils/sorted-networks-with-details))
token (->> account
:tokens
(filter #(= token-symbol (:symbol %)))
first)]
(assoc token :networks (network-utils/network-list token networks))))
(defn select-default-asset-to-receive
"Selects an asset to receive if it was not provided explicitly.
The principle of how the asset is being selected is simple: we get the
whole list, remove the currently selected `asset-to-pay` from it, and choose
the first one of what's left."
[{:keys [wallet account test-networks-enabled? asset-to-pay]}]
(let [networks (-> (get-in wallet [:networks (if test-networks-enabled? :test :prod)])
(network-utils/sorted-networks-with-details))]
(->> (utils/tokens-with-balance (:tokens account) networks nil)
(remove #(= (:symbol %) (:symbol asset-to-pay)))
first)))
(defn select-network
"Chooses the network.
Usually user needs to do the selection first and if the selection was done
then the list of networks for the defined token will always contain
one entry. Otherwise `nil` will be returned from here which will serve
as an indicator that the network selector needs to be displayed."
[{:keys [networks]}]
(when (= (count networks) 1)
(first networks)))
@@ -21,8 +21,8 @@
{:fx [[:effects.wallet-connect/init
{:on-success #(rf/dispatch [:wallet-connect/on-init-success %])
:on-fail #(rf/dispatch [:wallet-connect/on-init-fail %])}]]})
;; NOTE: when offline, fetching persistent sessions only
{:fx [[:dispatch [:wallet-connect/fetch-persisted-sessions]]]}))))
;; NOTE: when offline, fetching persistent sessions without initializing WC
{:fx [[:dispatch [:wallet-connect/get-sessions]]]}))))
(rf/reg-event-fx
:wallet-connect/on-init-success
@@ -30,7 +30,7 @@
(log/info "WalletConnect SDK initialisation successful")
{:db (assoc db :wallet-connect/web3-wallet web3-wallet)
:fx [[:dispatch [:wallet-connect/register-event-listeners]]
[:dispatch [:wallet-connect/fetch-persisted-sessions]]]}))
[:dispatch [:wallet-connect/get-sessions]]]}))
(rf/reg-event-fx
:wallet-connect/reload-on-network-change
@@ -5,8 +5,10 @@
[react-native.wallet-connect :as wallet-connect]
[status-im.config :as config]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.sessions :as sessions]
[status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
[utils.i18n :as i18n]
[utils.security.core :as security]))
@@ -41,35 +43,19 @@
(rf/reg-fx
:effects.wallet-connect/disconnect
(fn [{:keys [web3-wallet topic reason on-success on-fail]}]
(-> (wallet-connect/disconnect-session {:web3-wallet web3-wallet
:topic topic
:reason reason})
(fn [{:keys [web3-wallet topic on-success on-fail]}]
(-> (sessions/disconnect web3-wallet topic)
(promesa/then on-success)
(promesa/catch on-fail))))
(rf/reg-fx
:effects.wallet-connect/approve-session
(fn [{:keys [web3-wallet proposal networks accounts on-success on-fail]}]
(let [{:keys [params id]} proposal
approved-namespaces (->> {:eip155
{:chains networks
:accounts accounts
:methods constants/wallet-connect-supported-methods
:events constants/wallet-connect-supported-events}}
(wallet-connect/build-approved-namespaces
params))]
(-> (wallet-connect/approve-session
{:web3-wallet web3-wallet
:id id
:approved-namespaces approved-namespaces})
(promesa/then on-success)
(promesa/catch on-fail)))))
(rf/reg-fx
:effects.wallet-connect/fetch-active-sessions
(fn [{:keys [web3-wallet on-success on-fail]}]
(-> (wallet-connect/get-active-sessions web3-wallet)
(fn [{:keys [web3-wallet proposal-request session-networks address on-success on-fail]}]
(-> (sessions/approve
{:web3-wallet web3-wallet
:proposal-request proposal-request
:address address
:session-networks session-networks})
(promesa/then on-success)
(promesa/catch on-fail))))
@@ -123,11 +109,11 @@
(rf/reg-fx
:effects.wallet-connect/sign-typed-data
(fn [{:keys [password address data version chain-id on-success on-error]}]
(-> (signing/eth-sign-typed-data (security/safe-unmask-data password)
address
data
chain-id
version)
(-> (typed-data/sign (security/safe-unmask-data password)
address
data
chain-id
version)
(promesa/then on-success)
(promesa/catch on-error))))
@@ -155,3 +141,10 @@
:reason reason})
(promesa/then on-success)
(promesa/catch on-error)))))
(rf/reg-fx
:effects.wallet-connect/get-sessions
(fn [{:keys [web3-wallet addresses online? on-success on-error]}]
(-> (sessions/get-sessions web3-wallet addresses online?)
(promesa/then on-success)
(promesa/catch on-error))))
@@ -109,7 +109,6 @@
(fn [{:keys [db]} [address]]
{:db (assoc-in db [:wallet-connect/current-proposal :address] address)}))
(rf/reg-event-fx
:wallet-connect/approve-session
(fn [{:keys [db]}]
@@ -119,33 +118,51 @@
(map networks/chain-id->eip155)
vec)
current-address (get-in db [:wallet-connect/current-proposal :address])
accounts (-> (partial networks/format-eip155-address current-address)
(map session-networks))
network-status (:network/status db)
expiry (get-in current-proposal [:params :expiryTimestamp])]
expired? (-> current-proposal
(get-in [:params :expiryTimestamp])
uri/timestamp-expired?)]
(if (= network-status :online)
{:db (assoc-in db [:wallet-connect/current-proposal :response-sent?] true)
:fx [(if (uri/timestamp-expired? expiry)
:fx [(if expired?
[:dispatch
[:toasts/upsert
{:id :wallet-connect-proposal-expired
:type :negative
:text (i18n/label :t/wallet-connect-proposal-expired)}]]
[:effects.wallet-connect/approve-session
{:web3-wallet web3-wallet
:proposal current-proposal
:networks session-networks
:accounts accounts
:on-success (fn [approved-session]
(log/info "Wallet Connect session approved")
(rf/dispatch [:wallet-connect/reset-current-session-proposal])
(rf/dispatch [:wallet-connect/persist-session
approved-session]))
:on-fail (fn [error]
(log/error "Wallet Connect session approval failed"
{:error error
:event :wallet-connect/approve-session})
(rf/dispatch
[:wallet-connect/reset-current-session-proposal]))}])
{:web3-wallet web3-wallet
:proposal-request current-proposal
:session-networks session-networks
:address current-address
:on-success #(rf/dispatch [:wallet-connect/approve-session-success %])
:on-fail #(rf/dispatch [:wallet-connect/approve-session-error %])}])
[:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]}
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
(rf/reg-event-fx :wallet-connect/approve-session-success
(fn [_ [session]]
(log/info "Wallet Connect session approved")
{:fx [[:dispatch [:wallet-connect/on-new-session session]]
[:dispatch [:wallet-connect/reset-current-session-proposal]]
[:dispatch [:wallet-connect/redirect-to-dapp (data-store/get-dapp-redirect-url session)]]]}))
(rf/reg-event-fx :wallet-connect/approve-session-error
(fn [_ [error]]
(log/error "Wallet Connect session approval failed"
{:error error
:event :wallet-connect/approve-session})
{:fx [[:dispatch [:wallet-connect/reset-current-session-proposal]]]}))
(rf/reg-event-fx
:wallet-connect/reject-session-proposal
(fn [{:keys [db]} [proposal]]
(let [web3-wallet (get db :wallet-connect/web3-wallet)
{:keys [request response-sent?]} (:wallet-connect/current-proposal db)]
{:fx [(when-not response-sent?
[:effects.wallet-connect/reject-session-proposal
{:web3-wallet web3-wallet
:proposal (or proposal request)
:on-success #(log/info "Wallet Connect session proposal rejected")
:on-error #(log/error "Wallet Connect unable to reject session proposal")}])
[:dispatch [:wallet-connect/reset-current-session-proposal]]]})))
@@ -2,15 +2,15 @@
(:require [cljs-bean.core :as bean]
[clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as rf]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
(rf/reg-event-fx
@@ -37,7 +37,9 @@
(assoc-in [:wallet-connect/current-request :response-sent?] false))
:fx [(condp = method
constants/wallet-connect-eth-send-transaction-method
[:dispatch [:wallet-connect/process-eth-send-transaction]]
[:dispatch
[:wallet-connect/process-eth-send-transaction
{:on-success (fn [] (rf/dispatch [:wallet-connect/show-request-modal]))}]]
constants/wallet-connect-eth-sign-method
[:dispatch [:wallet-connect/process-eth-sign]]
@@ -94,22 +96,25 @@
:raw-data prepared-tx
:transaction tx
:chain-id chain-id
:display-data display-data)
:fx [[:dispatch [:wallet-connect/show-request-modal]]]})))
:display-data display-data)})))
(rf/reg-event-fx
:wallet-connect/process-eth-send-transaction
(fn [{:keys [db]}]
(fn [{:keys [db]} [{:keys [on-success]}]]
(let [event (data-store/get-db-current-request-event db)
tx (-> event data-store/get-request-params first)
chain-id (-> event
(get-in [:params :chainId])
networks/eip155->chain-id)]
{:fx [[:effects.wallet-connect/prepare-transaction
{:tx tx
:chain-id chain-id
:on-success #(rf/dispatch [:wallet-connect/prepare-transaction-success % chain-id])
:on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]})))
(when tx
{:fx [[:effects.wallet-connect/prepare-transaction
{:tx tx
:chain-id chain-id
:on-success (fn [data]
(rf/dispatch [:wallet-connect/prepare-transaction-success data chain-id])
(when on-success
(rf/call-continuation on-success)))
:on-error #(rf/dispatch [:wallet-connect/on-processing-error %])}]]}))))
(rf/reg-event-fx
:wallet-connect/process-eth-sign-transaction
@@ -130,16 +135,13 @@
(fn [{:keys [db]}]
(try
(let [[address raw-data] (data-store/get-db-current-request-params db)
parsed-raw-data (transforms/js-parse raw-data)
session-chain-id (-> (data-store/get-db-current-request-event db)
(get-in [:params :chainId])
networks/eip155->chain-id)
data-chain-id (-> parsed-raw-data
transforms/js->clj
signing/typed-data-chain-id)
parsed-data (-> parsed-raw-data
(transforms/js-dissoc :types :primaryType)
(transforms/js-stringify 2))]
typed-data (-> raw-data
transforms/js-parse
transforms/js->clj)
data-chain-id (typed-data/get-chain-id typed-data)]
(if (and data-chain-id
(not= session-chain-id data-chain-id))
{:fx [[:dispatch
@@ -150,7 +152,7 @@
[:wallet-connect/current-request]
assoc
:address (string/lower-case address)
:display-data (or parsed-data raw-data)
:display-data (typed-data/flatten-typed-data typed-data)
:raw-data raw-data)
:fx [[:dispatch [:wallet-connect/show-request-modal]]]}))
(catch js/Error err
@@ -165,19 +165,6 @@
{:fx [[:dispatch [:wallet-connect/send-response {:result result}]]
[:dispatch [:wallet-connect/dismiss-request-modal]]]}))
(rf/reg-event-fx
:wallet-connect/reject-session-proposal
(fn [{:keys [db]} [proposal]]
(let [web3-wallet (get db :wallet-connect/web3-wallet)
{:keys [request response-sent?]} (:wallet-connect/current-proposal db)]
{:fx [(when-not response-sent?
[:effects.wallet-connect/reject-session-proposal
{:web3-wallet web3-wallet
:proposal (or proposal request)
:on-success #(log/info "Wallet Connect session proposal rejected")
:on-error #(log/error "Wallet Connect unable to reject session proposal")}])
[:dispatch [:wallet-connect/reset-current-session-proposal]]]})))
;; NOTE: Currently we only reject a session if the user dismissed a modal
;; without accepting the session first.
;; But this needs to be solidified to ensure other cases:
@@ -1,20 +1,20 @@
(ns status-im.contexts.wallet.wallet-connect.events.sessions
(:require [re-frame.core :as rf]
[react-native.wallet-connect :as wallet-connect]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.sessions :as sessions]
[taoensso.timbre :as log]
[utils.transforms :as types]))
[utils.i18n :as i18n]))
(rf/reg-event-fx
:wallet-connect/on-session-delete
(fn [{:keys [db]} [{:keys [topic] :as event}]]
(when (networks/event-should-be-handled? db event)
(log/info "Received Wallet Connect session delete from the SDK: " event)
{:fx [[:dispatch [:wallet-connect/disconnect-persisted-session topic]]]})))
{:fx [[:json-rpc/call
[{:method "wallet_disconnectWalletConnectSession"
:params [topic]
:on-success [:wallet-connect/delete-session topic]
:on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})))
(rf/reg-event-fx
:wallet-connect/disconnect-dapp
@@ -26,116 +26,64 @@
{:fx [[:effects.wallet-connect/disconnect
{:web3-wallet web3-wallet
:topic topic
:reason (wallet-connect/get-sdk-error
constants/wallet-connect-user-disconnected-reason-key)
:on-fail on-fail
:on-success (fn []
(rf/dispatch [:wallet-connect/disconnect-persisted-session topic])
(log/info "Successfully disconnected dApp session" topic)
(rf/dispatch [:wallet-connect/delete-session topic])
(when on-success
(on-success)))}]]}
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
;; We first load sessions from database, then we initiate a call to Wallet Connect SDK and
;; then replace the list we have stored in the database with the one that came from the SDK.
;; In addition to that, we also update the backend state by marking sessions that are not
;; active anymore by calling `:wallet-connect/disconnect-session`.
(rf/reg-event-fx
:wallet-connect/fetch-active-sessions-success
(fn [{:keys [db now]} [sessions]]
(let [persisted-sessions (:wallet-connect/sessions db)
account-addresses (->> (get-in db [:wallet :accounts])
vals
sessions/filter-operable-accounts
(map :address))
sessions (->> (js->clj sessions :keywordize-keys true)
vals
(map sessions/sdk-session->db-session)
(sessions/filter-sessions-for-account-addresses
account-addresses))
session-topics (set (map :topic sessions))
expired-sessions (filter
(fn [{:keys [expiry topic]}]
(or (< expiry (/ now 1000))
(not (contains? session-topics topic))))
persisted-sessions)]
(when (seq expired-sessions)
(log/info "Updating WalletConnect persisted sessions due to expired/inactive sessions"
{:expired expired-sessions}))
{:fx (mapv (fn [{:keys [topic]}]
[:dispatch [:wallet-connect/disconnect-persisted-session topic]])
expired-sessions)
:db (assoc db :wallet-connect/sessions sessions)})))
(rf/reg-event-fx
:wallet-connect/fetch-active-sessions
:wallet-connect/get-sessions
(fn [{:keys [db]}]
(let [web3-wallet (get db :wallet-connect/web3-wallet)]
{:fx [[:effects.wallet-connect/fetch-active-sessions
{:web3-wallet web3-wallet
:on-fail #(log/error "Failed to get active sessions" {:error %})
:on-success #(rf/dispatch [:wallet-connect/fetch-active-sessions-success %])}]]})))
(let [addresses (->> (get-in db [:wallet :accounts])
vals
sessions/filter-operable-accounts
(map :address))]
(if (not (seq addresses))
;; NOTE: Re-trying to get active sessions if accounts weren't loaded yet during
;; initialization
((log/info "Re-trying to fetch active WalletConnect sessions")
{:fx [[:dispatch-later [{:ms 500 :dispatch [:wallet-connect/get-sessions]}]]]})
{:fx [[:effects.wallet-connect/get-sessions
{:online? (-> db :network/status (= :online))
:web3-wallet (get db :wallet-connect/web3-wallet)
:addresses addresses
:on-success #(rf/dispatch [:wallet-connect/get-sessions-success %])
:on-error #(rf/dispatch [:wallet-connect/get-sessions-error %])}]]}))))
(rf/reg-event-fx
:wallet-connect/fetch-persisted-sessions-success
:wallet-connect/get-sessions-success
(fn [{:keys [db]} [sessions]]
(let [network-status (:network/status db)
sessions' (mapv (fn [{:keys [sessionJson] :as session}]
(assoc session
:accounts
(-> sessionJson
types/json->clj
:namespaces
:eip155
:accounts)))
sessions)]
{:fx [(when (= network-status :online)
[:dispatch [:wallet-connect/fetch-active-sessions]])]
:db (assoc db :wallet-connect/sessions sessions')})))
(log/info "WalletConnect sessions loaded successfully")
{:db (assoc db :wallet-connect/sessions sessions)}))
(rf/reg-event-fx
:wallet-connect/fetch-persisted-sessions-fail
:wallet-connect/get-sessions-error
(fn [_ [error]]
(log/info "Wallet Connect fetch persisted sessions failed" error)
{:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]]}))
(log/error "WalletConnect sessions failed to load" error)
{:fx [[:dispatch
[:toasts/upsert
{:type :negative
:text (i18n/label :t/wallet-connect-connections-error)}]]]}))
(rf/reg-event-fx
:wallet-connect/fetch-persisted-sessions
(fn [{:keys [now]} _]
(let [current-timestamp (quot now 1000)]
{:fx [[:json-rpc/call
[{:method "wallet_getWalletConnectActiveSessions"
;; NOTE: This is the activeSince timestamp to avoid expired sessions
:params [current-timestamp]
:on-success [:wallet-connect/fetch-persisted-sessions-success]
:on-error [:wallet-connect/fetch-persisted-sessions-fail]}]]]})))
:wallet-connect/on-new-session
(fn [{:keys [db]} [new-session]]
{:db (update db
:wallet-connect/sessions
(fn [sessions]
(->> new-session
sessions/sdk-session->db-session
(conj sessions))))}))
(rf/reg-event-fx
:wallet-connect/persist-session
(fn [_ [session-info]]
(let [redirect-url (-> session-info
(js->clj :keywordize-keys true)
(data-store/get-dapp-redirect-url))]
{:fx [[:json-rpc/call
[{:method "wallet_addWalletConnectSession"
:params [(js/JSON.stringify session-info)]
:on-success (fn []
(log/info "Wallet Connect session persisted")
(rf/dispatch [:wallet-connect/fetch-persisted-sessions])
(rf/dispatch [:wallet-connect/redirect-to-dapp redirect-url]))
:on-error #(log/info "Wallet Connect session persistence failed" %)}]]]})))
(rf/reg-event-fx
:wallet-connect/disconnect-persisted-session
:wallet-connect/delete-session
(fn [{:keys [db]} [topic]]
(log/info "Removing session from persistance and state" topic)
{:db (update db
:wallet-connect/sessions
(fn [sessions]
(->> sessions
(remove #(= (:topic %) topic))
(into []))))
:fx [[:json-rpc/call
[{:method "wallet_disconnectWalletConnectSession"
:params [topic]
:on-success #(log/info "Wallet Connect session disconnected")
:on-error #(log/info "Wallet Connect session persistence failed" %)}]]]}))
(into []))))}))
@@ -0,0 +1,122 @@
(ns status-im.contexts.wallet.wallet-connect.events.sessions-test
(:require
[cljs.test :refer-macros [is are testing]]
matcher-combinators.test
[re-frame.db :as rf-db]
status-im.contexts.wallet.wallet-connect.events.session-responses
status-im.contexts.wallet.wallet-connect.events.sessions
[test-helpers.unit :as h]))
(defn- find-fx
[fx name]
(some #(when (= name (first %)) %) fx))
(defn- get-fx-arg
"Finds the arg value for an effect, if present in the fx vector"
[fx fx-name arg-fn]
(-> fx
(find-fx fx-name)
second
arg-fn))
(h/deftest-event :wallet-connect/on-session-delete
[event-id dispatch]
(testing "successfully deletes the session"
(reset! rf-db/app-db {:profile/profile {:test-networks-enabled? false}
:wallet-connect/sessions [{:topic "topic"
:chains ["eip155:1"]}]})
(let [fx (:fx (dispatch [event-id
{:topic "topic"
:chains ["eip155:1"]}]))
get-rpc-fx-arg (partial get-fx-arg fx :json-rpc/call)]
(are [expected result] (match? expected result)
:json-rpc/call (-> fx (find-fx :json-rpc/call) first)
"wallet_disconnectWalletConnectSession" (get-rpc-fx-arg (comp :method first))
["topic"] (get-rpc-fx-arg (comp :params first))
[:wallet-connect/delete-session "topic"] (get-rpc-fx-arg (comp :on-success first)))))
(testing "ignore the deletion if session topic not found"
(let [topic-1 "topic-1"
topic-2 "topic-2"]
(reset! rf-db/app-db {:profile/profile {:test-networks-enabled? false}
:wallet-connect/sessions [{:topic topic-1
:chains ["eip155:1"]}]})
(is (match? nil
(dispatch [event-id
{:topic topic-2
:chains ["eip155:1"]}])))))
(testing "ignore the deletion if the event is for the wrong network mode (testnet/mainnet)"
(let [topic "topic"]
(reset! rf-db/app-db {:profile/profile {:test-networks-enabled? true}
:wallet-connect/sessions [{:topic topic
:chains ["eip155:1"]}]})
(is (match? nil
(dispatch [event-id
{:topic topic
:chains ["eip155:11155111"]}]))))))
(h/deftest-event :wallet-connect/disconnect-dapp
[event-id dispatch]
(testing "disconnecting from dApp when online"
(reset! rf-db/app-db {:network/status :online
:wallet-connect/web3-wallet "mock"})
(let [fx (:fx (dispatch [event-id {:topic "topic"}]))]
(are [expected result] (match? expected result)
:effects.wallet-connect/disconnect (ffirst fx)
"topic" (-> fx first second :topic)
"mock" (-> fx first second :web3-wallet))))
(testing "showing no-internet toast when offline"
(reset! rf-db/app-db {:network/status :offline})
(is (match? :wallet-connect/no-internet-toast
(-> (dispatch [event-id {:topic "topic"}])
:fx
first
second
first)))))
(h/deftest-event :wallet-connect/get-sessions
[event-id dispatch]
(testing "the fx includes only for the available accounts"
(reset! rf-db/app-db {:wallet {:accounts {"available" {:address "0x123"
:operable? true}
"watch-only" {:address "0x456"
:operable? true
:watch-only? true}
"non-operable" {:address "0x789"
:operable? false}}}
:network/status :online
:wallet-connect/web3-wallet "mock"})
(let [fx (:fx (dispatch [event-id]))
get-sessions-fx-arg (partial get-fx-arg fx :effects.wallet-connect/get-sessions)]
(are [expected result] (match? expected result)
:effects.wallet-connect/get-sessions (-> fx (find-fx :effects.wallet-connect/get-sessions) first)
true (get-sessions-fx-arg :online?)
'("0x123") (get-sessions-fx-arg :addresses)
"mock" (get-sessions-fx-arg :web3-wallet)))))
(h/deftest-event :wallet-connect/get-sessions-success
[event-id dispatch]
(testing "sessions are stored in the db"
(let [sessions '({:topic "123"} {:topic "456"})]
(is (match? {:db {:wallet-connect/sessions sessions}}
(dispatch [event-id sessions]))))))
(h/deftest-event :wallet-connect/on-new-session
[event-id dispatch]
(testing "new session is added to db"
(let [sessions '({:topic "123"} {:topic "456"})
new-session {:topic "789"}]
(reset! rf-db/app-db {:wallet-connect/sessions sessions})
(is (match? {:db {:wallet-connect/sessions (conj sessions new-session)}}
(dispatch [event-id new-session]))))))
(h/deftest-event :wallet-connect/delete-session
[event-id dispatch]
(testing "session is deleted from db"
(let [sessions '({:topic "123"} {:topic "456"})
expected '({:topic "123"})]
(reset! rf-db/app-db {:wallet-connect/sessions sessions})
(is (match? {:db {:wallet-connect/sessions expected}}
(dispatch [event-id "456"]))))))
@@ -4,6 +4,7 @@
[quo.theme]
[react-native.core :as rn]
[status-im.common.standard-authentication.core :as standard-authentication]
[status-im.contexts.wallet.sheets.buy-token.view :as buy-token]
[status-im.contexts.wallet.wallet-connect.modals.common.footer.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -14,17 +15,25 @@
(rf/dispatch [:wallet-connect/respond-current-session password]))
(defn view
[{:keys [warning-label slide-button-text error-text]} & children]
[{:keys [warning-label slide-button-text error-state]} & children]
(let [{:keys [customization-color]} (rf/sub [:wallet-connect/current-request-account-details])
offline? (rf/sub [:network/offline?])
theme (quo.theme/use-theme)]
[:<>
(when (or offline? error-text)
(when (or offline? error-state)
[quo/alert-banner
{:action? false
:text (if offline?
(i18n/label :t/wallet-connect-no-internet-warning)
error-text)}])
{:action? (when error-state true)
:text (if offline?
(i18n/label :t/wallet-connect-no-internet-warning)
(i18n/label (condp = error-state
:not-enough-assets-to-pay-gas-fees
:t/not-enough-assets-to-pay-gas-fees
:not-enough-assets
:t/not-enough-assets-for-transaction)))
:button-text (i18n/label :t/buy-eth)
:on-button-press #(rf/dispatch [:show-bottom-sheet
{:content buy-token/view}])}])
[rn/view {:style style/content-container}
(into [rn/view
{:style style/data-items-container}]
@@ -33,7 +42,7 @@
[standard-authentication/slide-button
{:size :size-48
:track-text slide-button-text
:disabled? (or offline? (seq error-text))
:disabled? (or offline? error-state)
:customization-color customization-color
:on-auth-success on-auth-success
:auth-button-label (i18n/label :t/confirm)}]]
@@ -1,5 +1,6 @@
(ns status-im.contexts.wallet.wallet-connect.modals.common.style
(:require [status-im.constants :as constants]))
(:require [quo.foundations.colors :as colors]
[status-im.constants :as constants]))
(defn container
[bottom]
@@ -21,3 +22,23 @@
(def data-item
{:flex 1
:background-color :transparent})
(def typed-data-item
(assoc data-item :margin-bottom 4))
(def data-border-container
{:padding-horizontal 12
:padding-top 8
:margin-top 10.5
:margin-bottom 0
:border-width 1
:border-color colors/neutral-10
:border-radius 16})
(def data-item-container
{:padding 10
:margin-top 10.5
:margin-bottom 0
:border-width 1
:border-color colors/neutral-10
:border-radius 16})
@@ -2,7 +2,9 @@
(:require [quo.core :as quo]
[quo.theme]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.safe-area :as safe-area]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block]
[status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as
fees-data-item]
@@ -10,19 +12,71 @@
[status-im.contexts.wallet.wallet-connect.modals.common.header.view :as header]
[status-im.contexts.wallet.wallet-connect.modals.common.page-nav.view :as page-nav]
[status-im.contexts.wallet.wallet-connect.modals.common.style :as style]
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transaction-utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- refetch-transaction
[]
(rf/dispatch [:wallet-connect/process-eth-send-transaction]))
(def tabs-data
[{:id :tab/data :label (i18n/label :t/data)}
{:id :tab/hex :label (i18n/label :t/hex)}])
(defn- render-item
[props]
(let [[label value] props]
[quo/data-item
{:card? false
:container-style style/data-item
:title (str (name label) ":")
:subtitle value}]))
(defn- tab-view
[selected-tab]
(let [{:keys [transaction]} (rf/sub [:wallet-connect/current-request])
transaction-items (rn/use-memo #(-> transaction
(transaction-utils/transaction-hex-values->number)
(transaction-utils/transactions->display-array))
[transaction])]
[:<>
(case selected-tab
:tab/data [gesture/flat-list
{:data transaction-items
:content-container-style style/data-item-container
:render-fn render-item
:shows-vertical-scroll-indicator false}]
:tab/hex [data-block/view])]))
(defn view
[]
(let [bottom (safe-area/get-bottom)
(let [bottom (safe-area/get-bottom)
{:keys [customization-color]
:as account} (rf/sub [:wallet-connect/current-request-account-details])
dapp (rf/sub [:wallet-connect/current-request-dapp])
network (rf/sub [:wallet-connect/current-request-network])
:as account} (rf/sub [:wallet-connect/current-request-account-details])
dapp (rf/sub [:wallet-connect/current-request-dapp])
network (rf/sub [:wallet-connect/current-request-network])
{:keys [max-fees-fiat-formatted
error-state]} (rf/sub [:wallet-connect/current-request-transaction-information])]
(rn/use-unmount #(rf/dispatch [:wallet-connect/on-request-modal-dismissed]))
error-state estimated-time]} (rf/sub
[:wallet-connect/current-request-transaction-information])
[selected-tab set-selected-tab] (rn/use-state (:id (first tabs-data)))
on-change-tab #(set-selected-tab %)
refetch-interval-ref (rn/use-ref nil)
clear-interval (rn/use-callback (fn []
(when (.-current refetch-interval-ref)
(js/clearInterval
(.-current refetch-interval-ref))))
[refetch-interval-ref])]
(rn/use-mount
(fn []
(clear-interval)
(set! (.-current refetch-interval-ref)
(js/setInterval refetch-transaction constants/wallet-connect-transaction-refresh-interval-ms))))
(rn/use-unmount (fn []
(clear-interval)
(rf/dispatch [:wallet-connect/on-request-modal-dismissed])))
[rn/view {:style (style/container bottom)}
[quo/gradient-cover {:customization-color customization-color}]
[page-nav/view
@@ -33,17 +87,17 @@
{:label (i18n/label :t/wallet-connect-send-transaction-header)
:dapp dapp
:account account}]
[data-block/view]]
[quo/segmented-control
{:size 32
:blur? false
:default-active :tab/data
:data tabs-data
:on-change on-change-tab}]
[tab-view selected-tab]]
[footer/view
{:warning-label (i18n/label :t/wallet-connect-sign-warning)
:slide-button-text (i18n/label :t/slide-to-send)
:error-text (when error-state
(i18n/label (condp = error-state
:not-enough-assets-to-pay-gas-fees
:t/not-enough-assets-to-pay-gas-fees
:not-enough-assets
:t/not-enough-assets)))}
:error-state error-state}
[quo/data-item
{:status :default
:card? false
@@ -54,5 +108,12 @@
:subtitle (:full-name network)}]
[fees-data-item/view
{:fees max-fees-fiat-formatted
:fees-error error-state}]]]]))
:fees-error error-state}]
[quo/data-item
{:card? false
:container-style style/data-item
:title (i18n/label :t/est-time)
:subtitle (if estimated-time
(i18n/label :t/time-in-mins {:minutes estimated-time})
(i18n/label :t/unknown))}]]]]))
@@ -1,8 +1,9 @@
(ns status-im.contexts.wallet.wallet-connect.modals.sign-message.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.safe-area :as safe-area]
[status-im.contexts.wallet.wallet-connect.modals.common.data-block.view :as data-block]
[status-im.common.raw-data-block.view :as data-block]
[status-im.contexts.wallet.wallet-connect.modals.common.fees-data-item.view :as
fees-data-item]
[status-im.contexts.wallet.wallet-connect.modals.common.footer.view :as footer]
@@ -12,6 +13,37 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn typed-data-field
[{:keys [label value]}]
[quo/data-item
{:card? false
:container-style style/typed-data-item
:title label
:subtitle value}])
(defn typed-data-view
[]
(let [display-data (rf/sub [:wallet-connect/current-request-display-data])]
[gesture/flat-list
{:data display-data
:style style/data-border-container
:over-scroll-mode :never
:content-container-style {:padding-bottom 12}
:render-fn typed-data-field
:shows-vertical-scroll-indicator false}]))
(defn message-data-view
[]
(let [display-data (rf/sub [:wallet-connect/current-request-display-data])]
[data-block/view display-data]))
(defn display-data-view
[]
(let [typed-data? (rf/sub [:wallet-connect/typed-data-request?])]
(if typed-data?
[typed-data-view]
[message-data-view])))
(defn view
[]
(let [bottom (safe-area/get-bottom)
@@ -29,7 +61,7 @@
{:label (i18n/label :t/wallet-connect-sign-message-header)
:dapp dapp
:account account}]
[data-block/view]]
[display-data-view]]
[footer/view
{:warning-label (i18n/label :t/wallet-connect-sign-warning)
:slide-button-text (i18n/label :t/slide-to-sign)}
@@ -57,3 +57,25 @@
[chain-id]
(-> (rpc-events/call-async "wallet_getSuggestedFees" true chain-id)
(promesa/then transforms/js->clj)))
(defn wallet-disconnect-persisted-session
[topic]
(rpc-events/call-async "wallet_disconnectWalletConnectSession" true topic))
(defn wallet-get-persisted-sessions
([]
(let [now (-> (js/Date.) .getTime (quot 1000))]
(wallet-get-persisted-sessions now)))
([expiry-timestamp]
(rpc-events/call-async "wallet_getWalletConnectActiveSessions" false expiry-timestamp)))
(defn wallet-persist-session
[session]
(->> session
transforms/clj->json
(rpc-events/call-async "wallet_addWalletConnectSession" false)))
(defn wallet-get-transaction-estimated-time
[chain-id max-fee-per-gas]
(-> (rpc-events/call-async "wallet_getTransactionEstimatedTime" true chain-id max-fee-per-gas)
(promesa/then transforms/js->clj)))
@@ -1,6 +1,12 @@
(ns status-im.contexts.wallet.wallet-connect.utils.sessions
(:require
[clojure.string :as string]
[promesa.core :as promesa]
[react-native.wallet-connect :as wallet-connect]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[taoensso.timbre :as log]
[utils.transforms :as transforms]))
(defn sdk-session->db-session
@@ -42,3 +48,102 @@
first
(string/split #":")
last)))
(defn- parse-session-accounts
[{:keys [sessionJson] :as session}]
(assoc session
:accounts
(-> sessionJson
:namespaces
:eip155
:accounts)))
(defn- find-inactive-sessions
[active-sessions persisted-sessions]
(->> persisted-sessions
(filter #(->> %
:topic
(contains? (->> active-sessions
(map :topic)
set))
not))))
(defn get-persisted-sessions
[]
(-> (rpc/wallet-get-persisted-sessions)
(promesa/then #(map parse-session-accounts %))
(promesa/catch (fn [err]
(throw (ex-info "Failed to get persisted WalletConnect sessions"
{:error err
:code :error/wc-get-persisted-sessions}))))))
(defn get-active-sessions
[web3-wallet addresses]
(-> (wallet-connect/get-active-sessions web3-wallet)
(promesa/then #(->>
(transforms/js->clj %)
vals
(map sdk-session->db-session)
(filter-sessions-for-account-addresses addresses)))
(promesa/catch (fn [err]
(throw (ex-info "Failed to get active WalletConnect sessions"
{:error err
:code :error/wc-get-active-sessions}))))))
(defn sync-persisted-sessions
[active-sessions persisted-sessions]
(-> (promesa/all
(for [topic (find-inactive-sessions active-sessions
persisted-sessions)]
(do (log/info "Syncing disconnected session with persistance" topic)
(rpc/wallet-disconnect-persisted-session topic))))
(promesa/catch (fn [err]
(throw (ex-info "Failed to synchronize persisted sessions"
{:error err
:code :error/wc-sync-persisted-sessions}))))))
(defn get-sessions
[web3-wallet addresses online?]
(promesa/let [persisted-sessions (get-persisted-sessions)]
(if online?
(promesa/let [active-sessions (get-active-sessions web3-wallet addresses)]
(sync-persisted-sessions active-sessions persisted-sessions)
active-sessions)
persisted-sessions)))
(defn disconnect
[web3-wallet topic]
(let [reason (wallet-connect/get-sdk-error constants/wallet-connect-user-disconnected-reason-key)]
(->
(promesa/do
(wallet-connect/disconnect-session {:web3-wallet web3-wallet
:topic topic
:reason reason})
(rpc/wallet-disconnect-persisted-session topic))
(promesa/catch (fn [err]
(throw (ex-info "Failed to disconnect dapp"
{:err err
:code :error/wc-disconnect-dapp})))))))
(defn approve
[{:keys [web3-wallet address session-networks proposal-request]}]
(let [{:keys [params id]} proposal-request
accounts (-> (partial networks/format-eip155-address address)
(map session-networks))]
(-> (promesa/let [session
(wallet-connect/approve-session
{:web3-wallet web3-wallet
:id id
:approved-namespaces (->>
{:eip155
{:chains session-networks
:accounts accounts
:methods constants/wallet-connect-supported-methods
:events constants/wallet-connect-supported-events}}
(wallet-connect/build-approved-namespaces params))})]
(rpc/wallet-persist-session session)
(transforms/js->clj session))
(promesa/catch (fn [err]
(throw (ex-info "Failed to approve session"
{:err err
:code :error/wc-approve})))))))
@@ -1,28 +1,12 @@
(ns status-im.contexts.wallet.wallet-connect.utils.signing
(:require [native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.hex :as hex]
[utils.number :as number]
[utils.transforms :as transforms]))
(defn typed-data-chain-id
"Returns the `:chain-id` from typed data if it's present and if the EIP712 domain defines it. Without
the `:chain-id` in the domain type, it will not be signed as part of the typed-data."
[typed-data]
(let [chain-id-type? (->> typed-data
:types
:EIP712Domain
(some #(= "chainId" (:name %))))
data-chain-id (-> typed-data
:domain
:chainId
number/parse-int)]
(when chain-id-type?
data-chain-id)))
(:require
[native-module.core :as native-module]
[promesa.core :as promesa]
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.hex :as hex]
[utils.transforms :as transforms]))
(defn eth-sign
[password address data]
@@ -38,13 +22,3 @@
(-> (rpc/wallet-hash-message-eip-191 data)
(promesa/then #(rpc/wallet-sign-message % address password))
(promesa/then hex/prefix-hex)))
(defn eth-sign-typed-data
[password address data chain-id-eip155 version]
(let [legacy? (= version :v1)
chain-id (networks/eip155->chain-id chain-id-eip155)]
(rpc/wallet-safe-sign-typed-data data
address
password
chain-id
legacy?)))
@@ -51,12 +51,16 @@
(transforms/js-stringify 0)))
(defn beautify-transaction
[tx]
(-> tx
clj->js
(js/JSON.stringify nil 2)))
(defn transaction-hex-values->number
[tx]
(let [hex->number #(-> % (subs 2) native-module/hex-to-number)]
(-> tx
(format-tx-hex-values hex->number)
clj->js
(js/JSON.stringify nil 2))))
(format-tx-hex-values hex->number))))
(defn- gwei->hex
[gwei]
@@ -121,10 +125,14 @@
tx-priority
suggested-fees)
prepare-transaction-for-rpc
(rpc/wallet-build-transaction chain-id))]
(rpc/wallet-build-transaction chain-id))
estimated-time (rpc/wallet-get-transaction-estimated-time
chain-id
(:maxPriorityFeePerGas suggested-fees))]
{:tx-args tx-args
:tx-hash message-to-sign
:suggested-fees suggested-fees}))
:suggested-fees suggested-fees
:estimated-time estimated-time}))
(defn sign-transaction
[password address tx-hash tx-args chain-id]
@@ -141,3 +149,11 @@
tx-args
signature)]
tx))
(defn transactions->display-array
[data]
(remove (fn [[k v]]
(or (= v "0x")
(= k :MultiTransactionID)
(= k :Symbol)))
data))
@@ -0,0 +1,102 @@
(ns status-im.contexts.wallet.wallet-connect.utils.typed-data
(:require [clojure.string :as string]
[status-im.constants :as constants]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
[utils.number :as number]))
(declare flatten-data)
(defn- format-flattened-key
[k]
(cond
(keyword? k) (name k)
(number? k) (str k)
(string? k) k
:else "unsupported-key"))
(defn- flatten-map
[data path]
(reduce-kv (fn [acc k v]
(->> (format-flattened-key k)
(conj path)
(flatten-data v acc)))
[]
data))
(defn- flatten-vec
[data path]
(->> data
(map-indexed vector)
(reduce (fn [acc [idx v]]
(->> (str idx)
(conj path)
(flatten-data v acc)))
[])))
(defn flatten-data
"Recursively flatten a map or vector into a flat vector.
e.g. `[[[\"person\" \"first-name\"] \"Rich\"]
[[[\"person\" \"last-name\"] \"Hickey\"]]]`"
([value]
(flatten-data value [] []))
([value acc path]
(cond
(map? value) (into acc (flatten-map value path))
(vector? value) (into acc (flatten-vec value path))
:else (conj acc [path value]))))
(defn format-fields
"Format the fields into maps with `:label` & `:value`, where the label
is the flattened keys joined with a separator
e.g. `{:label \"person: first-name:\" :value \"Rich\"}`"
[data separator]
(mapv (fn [[kv v]]
{:label (-> separator
(string/join kv)
(str separator)
string/trim)
:value v})
data))
(defn flatten-typed-data
"Flatten typed data and prepare it for UI"
[typed-data]
(-> typed-data
(select-keys [:domain :message])
flatten-data
(format-fields ": ")))
(defn get-chain-id
"Returns the `:chain-id` from typed data if it's present and if the EIP712 domain defines it. Without
the `:chain-id` in the domain type, it will not be signed as part of the typed-data."
[typed-data]
(let [chain-id-type? (->> typed-data
:types
:EIP712Domain
(some #(= "chainId" (:name %))))
data-chain-id (-> typed-data
:domain
:chainId
number/parse-int)]
(when chain-id-type?
data-chain-id)))
(defn sign
[password address data chain-id-eip155 version]
(let [legacy? (= version :v1)
chain-id (networks/eip155->chain-id chain-id-eip155)]
(rpc/wallet-safe-sign-typed-data data
address
password
chain-id
legacy?)))
(defn typed-data-request?
[method]
(contains?
#{constants/wallet-connect-eth-sign-typed-v4-method
constants/wallet-connect-eth-sign-typed-method}
method))
@@ -0,0 +1,52 @@
(ns status-im.contexts.wallet.wallet-connect.utils.typed-data-test
(:require
[cljs.test :refer-macros [deftest is testing]]
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as sut]))
(deftest flatten-data-test
(testing "correctly returns the fallback when key is not string/keyword/number"
(is (match? [[["unsupported-key"] "value"]]
(sut/flatten-data {[1 2] "value"}))))
(testing "correctly flattens a simple map"
(is (match? [[["zero"] 0]
[["one"] 1]
[["2"] 2]]
(sut/flatten-data {:zero 0
"one" 1
2 2}))))
(testing "correctly flattens a simple vec"
(is (match? [[["0"] "zero"] [["1"] "one"]]
(sut/flatten-data ["zero" "one"]))))
(testing "correctly flattens a nested map"
(is (match? [[["nested" "child"] "child value"]
[["nested" "nested" "0"] "child one"]
[["nested" "nested" "1"] "child two"]
[["flat"] "child value"]]
(sut/flatten-data {:nested {:child "child value"
:nested ["child one" "child two"]}
:flat "child value"}))))
(testing "correctly flattens a nested vector"
(is (match? [[["0" "flat"] 1]
[["0" "nested-vector" "0"] 1]
[["0" "nested-vector" "1"] 2]
[["0" "nested-map" "one"] 1]]
(sut/flatten-data [{:flat 1
:nested-vector [1 2]
:nested-map {:one 1}}])))))
(deftest flatten-typed-data-test
(testing "successfully extracts, flattens and formats the typed data"
(is (match? [{:label "domain: chain-id:" :value 1}
{:label "message: to: address:" :value "0x"}
{:label "message: from: address:" :value "0x"}
{:label "message: amount:" :value "0x"}]
(sut/flatten-typed-data {:domain {:chain-id 1}
:types {:Tx [{:name "to"
:type "address"}]}
:message {:to {:address "0x"}
:from {:address "0x"}
:amount "0x"}})))))
-1
View File
@@ -31,7 +31,6 @@
::wallet.import-private-key (enabled-in-env? :FLAG_IMPORT_PRIVATE_KEY_ENABLED)
::wallet.long-press-watch-only-asset (enabled-in-env? :FLAG_LONG_PRESS_WATCH_ONLY_ASSET_ENABLED)
::wallet.saved-addresses (enabled-in-env? :WALLET_SAVED_ADDRESSES)
::wallet.swap (enabled-in-env? :FLAG_SWAP_ENABLED)
::wallet.wallet-connect (enabled-in-env? :FLAG_WALLET_CONNECT_ENABLED)
::wallet.custom-network-amounts (enabled-in-env? :FLAG_WALLET_CUSTOM_NETWORK_AMOUNTS_ENABLED)})
@@ -4,6 +4,7 @@
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
data-store]
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
[status-im.contexts.wallet.wallet-connect.utils.typed-data :as typed-data]
[utils.string]))
(rf/reg-sub
@@ -16,6 +17,14 @@
:<- [:wallet-connect/current-request]
:-> :display-data)
(rf/reg-sub
:wallet-connect/current-request-method
:<- [:wallet-connect/current-request]
(fn [request]
(-> request
:event
data-store/get-request-method)))
(rf/reg-sub
:wallet-connect/current-request-account-details
:<- [:wallet-connect/current-request-address]
@@ -45,3 +54,8 @@
:wallet-connect/current-request-network
:<- [:wallet-connect/chain-id]
networks/chain-id->network-details)
(rf/reg-sub
:wallet-connect/typed-data-request?
:<- [:wallet-connect/current-request-method]
typed-data/typed-data-request?)
@@ -54,20 +54,23 @@
:<- [:profile/currency-symbol]
(fn [[chain-id max-fees-wei transaction eth-token currency currency-symbol]]
(when transaction
(let [max-fees-ether (money/wei->ether max-fees-wei)
max-fees-fiat (wallet-utils/calculate-token-fiat-value {:currency currency
:balance max-fees-ether
:token eth-token})
max-fees-fiat-formatted (-> (wallet-utils/get-standard-crypto-format eth-token max-fees-ether)
(wallet-utils/get-standard-fiat-format currency-symbol
max-fees-fiat))
balance (-> eth-token
(get-in [:balances-per-chain chain-id :raw-balance])
money/bignumber)
tx-value (money/bignumber (:value transaction))
total-transaction-value (money/add max-fees-wei tx-value)]
(let [max-fees-ether (money/wei->ether max-fees-wei)
max-fees-fiat (wallet-utils/calculate-token-fiat-value {:currency currency
:balance max-fees-ether
:token eth-token})
max-fees-fiat-formatted (-> (wallet-utils/get-standard-crypto-format eth-token
max-fees-ether)
(wallet-utils/get-standard-fiat-format currency-symbol
max-fees-fiat))
balance (-> eth-token
(get-in [:balances-per-chain chain-id :raw-balance])
money/bignumber)
tx-value (money/bignumber (:value transaction))
total-transaction-value (money/add max-fees-wei tx-value)
estimated-time-formatted (wallet-utils/estimated-time-format (:estimated-time transaction))]
{:total-transaction-value total-transaction-value
:balance balance
:estimated-time estimated-time-formatted
:max-fees max-fees-wei
:max-fees-fiat-value max-fees-fiat
:max-fees-fiat-formatted max-fees-fiat-formatted
@@ -6,8 +6,3 @@
[distance-from-list-top chat-list-scroll-y callback]
(.messagesListOnScroll ^js worklets distance-from-list-top chat-list-scroll-y callback))
(defn use-messages-scrolled-to-threshold
[distance-from-list-top threshold]
(.useMessagesScrolledToThreshold ^js worklets
distance-from-list-top
threshold))
@@ -1,27 +0,0 @@
(ns utils.worklets.chat.messenger.navigation)
(def ^:private worklets (js/require "../src/js/worklets/chat/messenger/navigation.js"))
(defn navigation-header-opacity
[distance-from-list-top all-loaded? chat-screen-layout-calculations-complete? start-position]
(.navigationHeaderOpacity ^js worklets
distance-from-list-top
all-loaded?
chat-screen-layout-calculations-complete?
start-position))
(defn navigation-header-position
[distance-from-list-top all-loaded? top-bar-height start-position]
(.navigationHeaderPosition ^js worklets
distance-from-list-top
all-loaded?
top-bar-height
start-position))
(defn navigation-buttons-complete-opacity
[chat-screen-layout-calculations-complete?]
(.navigationButtonsCompleteOpacity ^js worklets chat-screen-layout-calculations-complete?))
(defn interpolate-navigation-view-opacity
[props]
(.interpolateNavigationViewOpacity ^js worklets (clj->js props)))
@@ -1,11 +0,0 @@
(ns utils.worklets.chat.messenger.placeholder)
(def ^:private worklets (js/require "../src/js/worklets/chat/messenger/placeholder.js"))
(defn placeholder-opacity
[chat-screen-layout-calculations-complete?]
(.placeholderOpacity ^js worklets chat-screen-layout-calculations-complete?))
(defn placeholder-z-index
[chat-screen-layout-calculations-complete?]
(.placeholderZIndex ^js worklets chat-screen-layout-calculations-complete?))
+3 -3
View File
@@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v2.3.0",
"commit-sha1": "effde33d493ff52b66c8013067c9aa6444d84473",
"src-sha256": "1rd0mczdkvjm9pbmza234v1jy1bvqh3ni2xck1l42ha4sld2aqjl"
"version": "292309c335d4f27911a691fa6de60cdb5da8ce01",
"commit-sha1": "292309c335d4f27911a691fa6de60cdb5da8ce01",
"src-sha256": "0kybmy30v0l5sj97fykrqv15z2vn0ij4rbwwm3p3nhmcqf58zc93"
}
+21 -16
View File
@@ -111,11 +111,13 @@ def get_app_path():
return app_path
log_file_names = ['geth', 'requests', 'Status']
def pull_geth(driver):
result = driver.pull_file(get_app_path() + 'geth.log')
return base64.b64decode(result)
def pull_log_file(driver, log_file_name):
result = driver.pull_file(get_app_path() + '%s.log' % log_file_name)
def pull_requests_log(driver):
result = driver.pull_file(get_app_path() + 'requests.log')
return base64.b64decode(result)
@@ -214,7 +216,7 @@ class SingleDeviceTestCase(AbstractTestCase):
self.print_sauce_lab_info(self.driver)
try:
self.add_alert_text_to_report(self.driver)
geth_content = pull_log_file(self.driver, 'geth')
geth_content = pull_geth(self.driver)
self.driver.quit()
if pytest_config_global['docker']:
appium_container.stop_container()
@@ -277,7 +279,7 @@ class SauceMultipleDeviceTestCase(AbstractTestCase):
self.add_alert_text_to_report(self.drivers[driver])
geth_names.append(
'%s_geth%s.log' % (test_suite_data.current_test.name, str(self.drivers[driver].number)))
geth_contents.append(pull_log_file(self.drivers[driver], log_file_name='geth'))
geth_contents.append(pull_geth(self.drivers[driver]))
self.drivers[driver].quit()
except (WebDriverException, AttributeError):
pass
@@ -381,12 +383,14 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
try:
self.print_sauce_lab_info(self.drivers[driver])
self.add_alert_text_to_report(self.drivers[driver])
for log in log_file_names:
log_names.append(
'%s_%s%s.log' % (test_suite_data.current_test.name, log, str(self.drivers[driver].number)))
log_contents.append(pull_log_file(self.drivers[driver], log_file_name=log))
except (WebDriverException, AttributeError, RemoteDisconnected, ProtocolError) as e:
raise e
log_names.append(
'%s_geth%s.log' % (test_suite_data.current_test.name, str(self.drivers[driver].number)))
log_contents.append(pull_geth(self.drivers[driver]))
log_names.append(
'%s_requests%s.log' % (test_suite_data.current_test.name, str(self.drivers[driver].number)))
log_contents.append(pull_requests_log(self.drivers[driver]))
except (WebDriverException, AttributeError, RemoteDisconnected, ProtocolError):
pass
finally:
try:
logs = {log_names[i]: log_contents[i] for i in range(len(log_names))}
@@ -416,14 +420,15 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
try:
for i, driver in cls.drivers.items():
if group_setup_failed:
for log in log_file_names:
log_contents.append(pull_log_file(driver=driver, log_file_name=log))
log_names.append('%s_%s%s.log' % (cls.__name__, log, i))
log_contents.append(pull_geth(driver=driver))
log_names.append('%s_geth%s.log' % (cls.__name__, i))
log_contents.append(pull_requests_log(driver=driver))
log_names.append('%s_requests%s.log' % (cls.__name__, i))
session_id = driver.session_id
try:
sauce.jobs.update_job(username=sauce_username, job_id=session_id, name=cls.__name__)
except (RemoteDisconnected, SauceException, requests.exceptions.ConnectionError) as e:
raise e
except (RemoteDisconnected, SauceException, requests.exceptions.ConnectionError):
pass
try:
driver.quit()
except WebDriverException:
+2 -2
View File
@@ -747,8 +747,8 @@ class BaseView(object):
return items_in_logcat
def find_values_in_geth(self, *args):
from tests.base_test_case import pull_log_file
b64_log = pull_log_file(self.driver, log_file_name='geth')
from tests.base_test_case import pull_geth
b64_log = pull_geth(self.driver)
file = base64.b64decode(b64_log)
result = False
for value in args:
+12 -12
View File
@@ -263,20 +263,20 @@ class SignInView(BaseView):
# self.identifiers_button.wait_and_click(30)
if enable_notifications:
self.enable_notifications_button.wait_and_click()
# self.cancel_button.click_if_shown() # TODO: remove when issue 20806 is fixed
# for _ in range(3):
self.allow_button.click_if_shown(sec=10)
# self.cancel_button.click_if_shown() # TODO: remove when issue 20806 is fixed
# self.enable_notifications_button.click_if_shown()
# if self.chats_tab.is_element_displayed():
# break
self.cancel_button.click_if_shown() # TODO: remove when issue 20806 is fixed
for _ in range(3):
self.allow_button.click_if_shown(sec=10)
self.cancel_button.click_if_shown() # TODO: remove when issue 20806 is fixed
self.enable_notifications_button.click_if_shown()
if self.chats_tab.is_element_displayed():
break
else:
self.maybe_later_button.wait_and_click()
# for _ in range(3):
# self.cancel_button.click_if_shown() # TODO: remove when issue 20806 is fixed
# self.maybe_later_button.click_if_shown()
# if self.chats_tab.is_element_displayed():
# break
for _ in range(3):
self.cancel_button.click_if_shown() # TODO: remove when issue 20806 is fixed
self.maybe_later_button.click_if_shown()
if self.chats_tab.is_element_displayed():
break
self.chats_tab.wait_for_visibility_of_element(30)
self.driver.info("## New multiaccount is created successfully!", device=False)
return self.get_home_view()
+5 -2
View File
@@ -1124,6 +1124,7 @@
"help-improve-status": "Help improve Status",
"help-us-improve-status": "Help us improve Status",
"here-is-a-cat-in-a-box-instead": "Heres a cat in a box instead",
"hex": "Hex",
"hide": "Hide",
"hide-content-when-switching-apps": "Block screenshots",
"hide-content-when-switching-apps-ios": "Hide preview",
@@ -1737,6 +1738,7 @@
"not-connected-nodes": "Not connected to a status node",
"not-connected-to-peers": "Not connected to any peers",
"not-enough-assets": "Not enough assets to complete transaction",
"not-enough-assets-for-transaction": "Not enough assets for transaction",
"not-enough-assets-to-pay-gas-fees": "Not enough assets to pay gas fees",
"not-enough-liquidity": "Not enough liquidity. Lower token amount or try again later.",
"not-enough-snt": "Not enough SNT",
@@ -2484,8 +2486,8 @@
"token-auto-validate-name-error": "Wrong name for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}",
"token-auto-validate-symbol-error": "Wrong symbol for token {{symbol}} at address {{address}} - set to {{expected}} but detected as {{actual}}",
"token-details": "Token details",
"token-gated-communities": "Token gated communities",
"token-gated-communities-info": "Here will be something relevant about this topic. This will help the user get more context and therefore have a better understanding of it.",
"token-gated-communities": "Token-gated communities",
"token-gated-communities-info": "If you want to join a token-gated community, you need to meet the token requirements set by the community Owner.\n\nThe Status app automatically checks if you meet the community requirements. If you meet the requirements, the option to join the community is available.",
"token-master": "Token Master",
"token-not-available-on-networks": "{{token-symbol}} is not available on {{networks}}.",
"token-not-available-on-receiver-networks": "{{token-symbol}} is not available on the recipients desired networks. Proceed with caution.",
@@ -2696,6 +2698,7 @@
"wallet-connect": "Wallet Connect",
"wallet-connect-2.0": "Wallet Connect 2.0",
"wallet-connect-app-connected": "is connected",
"wallet-connect-connections-error": "Failed to get dApps connections",
"wallet-connect-go-back": "Go back to your browser or dapp",
"wallet-connect-label": "WalletConnect",
"wallet-connect-networks-not-supported": "{{dapp}} requires an unsupported network.",