Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9fdd5c671 | ||
|
|
462101dbf5 | ||
|
|
00f1463e94 | ||
|
|
9135e915ab | ||
|
|
d7242f1c70 | ||
|
|
fa4ea0640d | ||
|
|
8f94332d59 | ||
|
|
337ec963b7 | ||
|
|
cf5a395b29 | ||
|
|
e2d597401c | ||
|
|
3ae500ff68 | ||
|
|
779eadd787 | ||
|
|
110475a973 | ||
|
|
74469c2b61 | ||
|
|
d5238ac82f | ||
|
|
c861d95d69 |
@@ -211,7 +211,7 @@ android {
|
||||
reset()
|
||||
enable getEnvOrConfig('ANDROID_ABI_SPLIT').toBoolean()
|
||||
include getEnvOrConfig('ANDROID_ABI_INCLUDE').split(";")
|
||||
universalApk false
|
||||
universalApk getEnvOrConfig('ORG_GRADLE_PROJECT_universalApk').toBoolean()
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
/* Options section can't access functions in objects. */
|
||||
def isPRBuild = utils.isPRBuild()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
agent { label 'macos' }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
agent { label params.AGENT_LABEL }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
agent { label 'linux' }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env groovy
|
||||
library 'status-jenkins-lib@v1.9.8'
|
||||
library 'status-jenkins-lib@v1.9.9'
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
# Android architectures to build for
|
||||
# Used to detect end-to-end builds
|
||||
androidAbiInclude ? lib.getEnvWithDefault "ANDROID_ABI_INCLUDE" "arm64-v8a",
|
||||
universalApk ? lib.getEnvWithDefault "ORG_GRADLE_PROJECT_universalApk" "false"
|
||||
}:
|
||||
|
||||
let
|
||||
@@ -89,6 +90,7 @@ in stdenv.mkDerivation rec {
|
||||
ORG_GRADLE_PROJECT_commitHash = commitHash;
|
||||
ORG_GRADLE_PROJECT_buildUrl = buildUrl;
|
||||
ORG_GRADLE_PROJECT_hermesEnabled = hermesEnabled;
|
||||
ORG_GRADLE_PROJECT_universalApk = universalApk;
|
||||
|
||||
# Fix for ERR_OSSL_EVP_UNSUPPORTED error.
|
||||
NODE_OPTIONS = "--openssl-legacy-provider";
|
||||
|
||||
|
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 752 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 547 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 306 KiB |
@@ -7,6 +7,7 @@
|
||||
[:map {:closed true}
|
||||
[:action? {:optional true} [:maybe boolean?]]
|
||||
[:text {:optional true} [:maybe string?]]
|
||||
[:container-style {:optional true} [:maybe :map]]
|
||||
[:button-text {:optional true} [:maybe string?]]
|
||||
[:on-button-press {:optional true} [:maybe fn?]]]]]
|
||||
:any])
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
(ns quo.components.banners.alert-banner.style
|
||||
(:require [quo.foundations.colors :as colors]))
|
||||
|
||||
(def container
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:height 50
|
||||
:padding-horizontal 20
|
||||
:padding-vertical 12})
|
||||
(defn container
|
||||
[container-style]
|
||||
(merge
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:height 50
|
||||
:padding-horizontal 20
|
||||
:padding-vertical 12}
|
||||
container-style))
|
||||
|
||||
(defn label
|
||||
[theme]
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [action? text button-text on-button-press]}]
|
||||
[{:keys [action? text button-text container-style on-button-press]}]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
[rn/view
|
||||
{:accessibility-label :alert-banner}
|
||||
[linear-gradient/linear-gradient
|
||||
{:style style/container
|
||||
{:style (style/container container-style)
|
||||
:start {:x 0 :y 0}
|
||||
:end {:x 0 :y 1}
|
||||
:colors [(colors/theme-colors
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
[:subtitle-type {:optional true} [:maybe [:enum :default :icon :network :account :editable]]]
|
||||
[:size {:optional true} [:maybe [:enum :default :small :large]]]
|
||||
[:title :string]
|
||||
[:subtitle {:optional true} [:maybe :string]]
|
||||
[:subtitle {:optional true} [:maybe [:or :string :double]]]
|
||||
[:custom-subtitle {:optional true} [:maybe fn?]]
|
||||
[:icon {:optional true} [:maybe :keyword]]
|
||||
[:emoji {:optional true} [:maybe :string]]
|
||||
|
||||
@@ -60,8 +60,12 @@
|
||||
shrinkable? (assoc :flex-shrink 1)))
|
||||
|
||||
(defn text
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-100 colors/white theme)})
|
||||
([theme]
|
||||
(text theme false))
|
||||
([theme gray-text?]
|
||||
{:color (if gray-text?
|
||||
colors/neutral-50
|
||||
(colors/theme-colors colors/neutral-100 colors/white theme))}))
|
||||
|
||||
(defn token-logo
|
||||
[size]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns quo.components.tags.context-tag.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.components.avatars.account-avatar.view :as account-avatar]
|
||||
[quo.components.avatars.group-avatar.view :as group-avatar]
|
||||
[quo.components.avatars.user-avatar.view :as user-avatar]
|
||||
@@ -17,15 +18,16 @@
|
||||
[schema.core :as schema]))
|
||||
|
||||
(defn- tag-skeleton
|
||||
[{:keys [size text theme shrinkable?]
|
||||
:or {size 24
|
||||
theme (quo.theme/use-theme)}}
|
||||
[{:keys [size text theme shrinkable? gray-text?]
|
||||
:or {size 24
|
||||
gray-text? false
|
||||
theme (quo.theme/use-theme)}}
|
||||
logo-component]
|
||||
[rn/view {:style (style/tag-container size)}
|
||||
logo-component
|
||||
[rn/view {:style (style/tag-spacing size shrinkable?)}
|
||||
[text/text
|
||||
{:style (style/text theme)
|
||||
{:style (style/text theme gray-text?)
|
||||
:weight :medium
|
||||
:size (if (= size 24) :paragraph-2 :paragraph-1)
|
||||
:number-of-lines 1
|
||||
@@ -94,92 +96,107 @@
|
||||
state :default
|
||||
theme (quo.theme/use-theme)}
|
||||
:as props}]
|
||||
[rn/view {:style (merge {:align-items :flex-start} container-style)}
|
||||
[rn/view
|
||||
{:style (style/container {:theme theme
|
||||
:type type
|
||||
:size size
|
||||
:state state
|
||||
:blur? blur?
|
||||
:customization-color customization-color})
|
||||
:accessibility-label :context-tag}
|
||||
(case type
|
||||
:default
|
||||
[tag-skeleton {:theme theme :size size :text full-name}
|
||||
[user-avatar/user-avatar
|
||||
{:full-name full-name
|
||||
:profile-picture profile-picture
|
||||
:size (if (= size 24) :xxs 28)
|
||||
:status-indicator? false
|
||||
:ring? false
|
||||
:customization-color customization-color}]]
|
||||
(let [[image-error? set-image-error] (rn/use-state false)]
|
||||
[rn/view {:style (merge {:align-items :flex-start} container-style)}
|
||||
[rn/view
|
||||
{:style (style/container {:theme theme
|
||||
:type type
|
||||
:size size
|
||||
:state state
|
||||
:blur? blur?
|
||||
:customization-color customization-color})
|
||||
:accessibility-label :context-tag}
|
||||
(case type
|
||||
:default
|
||||
[tag-skeleton {:theme theme :size size :text full-name}
|
||||
[user-avatar/user-avatar
|
||||
{:full-name full-name
|
||||
:profile-picture profile-picture
|
||||
:size (if (= size 24) :xxs 28)
|
||||
:status-indicator? false
|
||||
:ring? false
|
||||
:customization-color customization-color}]]
|
||||
|
||||
:multiuser
|
||||
[preview-list/view {:type :user :size :size-20}
|
||||
users]
|
||||
:multiuser
|
||||
[preview-list/view {:type :user :size :size-20}
|
||||
users]
|
||||
|
||||
:multinetwork
|
||||
[preview-list/view {:type :network :size :size-20}
|
||||
networks]
|
||||
:multinetwork
|
||||
[preview-list/view {:type :network :size :size-20}
|
||||
networks]
|
||||
|
||||
:audio
|
||||
[tag-skeleton {:theme theme :text (str duration)}
|
||||
[rn/view {:style (style/audio-tag-icon-container customization-color theme)}
|
||||
[icons/icon :i/play {:color style/audio-tag-icon-color :size 12}]]]
|
||||
:audio
|
||||
[tag-skeleton {:theme theme :text (str duration)}
|
||||
[rn/view {:style (style/audio-tag-icon-container customization-color theme)}
|
||||
[icons/icon :i/play {:color style/audio-tag-icon-color :size 12}]]]
|
||||
|
||||
:group
|
||||
[tag-skeleton {:theme theme :size size :text group-name}
|
||||
[group-avatar/view
|
||||
{:icon-name :i/members
|
||||
:size (if (= size 24) :size-20 :size-28)
|
||||
:customization-color (colors/custom-color customization-color 50)}]]
|
||||
:group
|
||||
[tag-skeleton {:theme theme :size size :text group-name}
|
||||
[group-avatar/view
|
||||
{:icon-name :i/members
|
||||
:size (if (= size 24) :size-20 :size-28)
|
||||
:customization-color (colors/custom-color customization-color 50)}]]
|
||||
|
||||
(:channel :community)
|
||||
[communities-tag (assoc props :channel? (= type :channel))]
|
||||
(:channel :community)
|
||||
[communities-tag (assoc props :channel? (= type :channel))]
|
||||
|
||||
:token
|
||||
[tag-skeleton {:theme theme :size size :text (str amount " " token)}
|
||||
[token/view
|
||||
{:style (style/token-logo size)
|
||||
:token token
|
||||
:size (if (= size 24) :size-20 :size-28)}]]
|
||||
:token
|
||||
[tag-skeleton {:theme theme :size size :text (str amount " " token)}
|
||||
[token/view
|
||||
{:style (style/token-logo size)
|
||||
:token token
|
||||
:size (if (= size 24) :size-20 :size-28)}]]
|
||||
|
||||
:network
|
||||
[tag-skeleton {:theme theme :size size :text network-name}
|
||||
[rn/image {:style (style/circle-logo size) :source network-logo}]]
|
||||
:network
|
||||
[tag-skeleton {:theme theme :size size :text network-name}
|
||||
[rn/image {:style (style/circle-logo size) :source network-logo}]]
|
||||
|
||||
:collectible
|
||||
[tag-skeleton
|
||||
{:theme theme
|
||||
:size size
|
||||
:text (str collectible-name " #" collectible-number)
|
||||
:shrinkable? true}
|
||||
[rn/image {:style (style/rounded-logo size) :source collectible}]]
|
||||
:collectible
|
||||
(let [gray-text? (string/blank? collectible-name)
|
||||
collectible-text (cond
|
||||
gray-text? "UNKNOWN"
|
||||
collectible-number (str collectible-name
|
||||
" #"
|
||||
collectible-number)
|
||||
:else collectible-name)
|
||||
nft-placeholder? (or image-error? (string/blank? collectible))]
|
||||
[tag-skeleton
|
||||
{:theme theme
|
||||
:size size
|
||||
:text collectible-text
|
||||
:shrinkable? true
|
||||
:gray-text? gray-text?}
|
||||
(if nft-placeholder?
|
||||
[icons/icon :i/nft {:size 20}]
|
||||
[rn/image
|
||||
{:style (style/rounded-logo size)
|
||||
:source collectible
|
||||
:on-error #(set-image-error true)}])])
|
||||
|
||||
:account
|
||||
[tag-skeleton {:theme theme :size size :text account-name}
|
||||
[account-avatar/view
|
||||
{:customization-color customization-color
|
||||
:emoji emoji
|
||||
:size (if (= size 24) 20 28)}]]
|
||||
:account
|
||||
[tag-skeleton {:theme theme :size size :text account-name}
|
||||
[account-avatar/view
|
||||
{:customization-color customization-color
|
||||
:emoji emoji
|
||||
:size (if (= size 24) 20 28)}]]
|
||||
|
||||
:address
|
||||
[address-tag props]
|
||||
:address
|
||||
[address-tag props]
|
||||
|
||||
:icon
|
||||
[icon-tag props]
|
||||
:icon
|
||||
[icon-tag props]
|
||||
|
||||
:wallet-user
|
||||
[tag-skeleton {:theme theme :size size :text full-name}
|
||||
[wallet-user-avatar/wallet-user-avatar
|
||||
{:full-name full-name
|
||||
:size (if (= size 24) :size-20 :size-24)
|
||||
:customization-color customization-color}]]
|
||||
:wallet-user
|
||||
[tag-skeleton {:theme theme :size size :text full-name}
|
||||
[wallet-user-avatar/wallet-user-avatar
|
||||
{:full-name full-name
|
||||
:size (if (= size 24) :size-20 :size-24)
|
||||
:customization-color customization-color}]]
|
||||
|
||||
:dapp
|
||||
[tag-skeleton {:theme theme :size size :text dapp-name}
|
||||
[rn/image {:style (style/circle-logo size) :source dapp-logo}]]
|
||||
:dapp
|
||||
[tag-skeleton {:theme theme :size size :text dapp-name}
|
||||
[rn/image {:style (style/circle-logo size) :source dapp-logo}]]
|
||||
|
||||
nil)]])
|
||||
nil)]]))
|
||||
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
[:token-value :string]
|
||||
[:token-symbol :string]
|
||||
[:container-style {:optional true} [:maybe :map]]
|
||||
[:show-view-button? {:optional true} [:maybe :boolean]]
|
||||
[:button-props {:optional true} [:maybe :map]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]]
|
||||
:any])
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [status token-value token-symbol
|
||||
container-style button-props]
|
||||
container-style button-props show-view-button?]
|
||||
:as props}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
customization-color (or (:customization-color props) :blue)
|
||||
@@ -56,7 +56,9 @@
|
||||
(i18n/label (status-message status)
|
||||
{:amount token-value
|
||||
:symbol token-symbol})]]
|
||||
(when button-props
|
||||
(when (and button-props
|
||||
(or (= status :approve)
|
||||
(and (not= status :approve) show-view-button?)))
|
||||
[button/button
|
||||
(merge {:type (if (= status :approve) :primary :grey)
|
||||
:background (when-not (= status :approve) :blur)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
(ns quo.components.wallet.swap-input.style
|
||||
(:require [quo.foundations.colors :as colors]
|
||||
[quo.foundations.shadows :as shadows]
|
||||
[quo.foundations.typography :as typography]))
|
||||
|
||||
(defn- border-color
|
||||
@@ -11,11 +12,13 @@
|
||||
(colors/theme-colors colors/neutral-5 colors/neutral-90 theme))
|
||||
|
||||
(defn content
|
||||
[theme]
|
||||
{:border-width 1
|
||||
:border-radius 16
|
||||
:border-color (border-color theme)
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)})
|
||||
[typing? theme]
|
||||
(merge
|
||||
{:border-width 1
|
||||
:border-radius 16
|
||||
:border-color (border-color theme)
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)}
|
||||
(when typing? (shadows/get 1 theme))))
|
||||
|
||||
(defn row-1
|
||||
[loading?]
|
||||
|
||||
@@ -19,13 +19,15 @@
|
||||
[:props
|
||||
[:map {:closed true}
|
||||
[:type {:optional true} [:maybe [:enum :pay :receive]]]
|
||||
[:status {:optional true} [:maybe [:enum :default :disabled :loading]]]
|
||||
[:status {:optional true} [:maybe [:enum :default :typing :disabled :loading]]]
|
||||
[:token {:optional true} [:maybe :string]]
|
||||
[:value {:optional true} [:maybe :string]]
|
||||
[:default-value {:optional true} [:maybe :string]]
|
||||
[:currency-symbol {:optional true} [:maybe :string]]
|
||||
[:fiat-value {:optional true} [:maybe :string]]
|
||||
[:show-approval-label? {:optional true} [:maybe :boolean]]
|
||||
[:auto-focus? {:optional true} [:maybe :boolean]]
|
||||
[:input-disabled? {:optional true} [:maybe :boolean]]
|
||||
[:error? {:optional true} [:maybe :boolean]]
|
||||
[:show-keyboard? {:optional true} [:maybe :boolean]]
|
||||
[:approval-label-props {:optional true} [:maybe approval-label.schema/?schema]]
|
||||
@@ -33,6 +35,7 @@
|
||||
[:on-change-text {:optional true} [:maybe fn?]]
|
||||
[:enable-swap? {:optional true} [:maybe :boolean]]
|
||||
[:on-swap-press {:optional true} [:maybe fn?]]
|
||||
[:on-input-focus {:optional true} [:maybe fn?]]
|
||||
[:on-token-press {:optional true} [:maybe fn?]]
|
||||
[:on-max-press {:optional true} [:maybe fn?]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
@@ -41,13 +44,14 @@
|
||||
|
||||
(defn view-internal
|
||||
[{:keys [type status token value fiat-value show-approval-label? error? network-tag-props
|
||||
approval-label-props default-value enable-swap?
|
||||
approval-label-props default-value auto-focus? input-disabled? enable-swap?
|
||||
currency-symbol on-change-text show-keyboard?
|
||||
container-style on-swap-press on-token-press on-max-press]}]
|
||||
container-style on-swap-press on-token-press on-max-press on-input-focus]}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
pay? (= type :pay)
|
||||
disabled? (= status :disabled)
|
||||
loading? (= status :loading)
|
||||
typing? (= status :typing)
|
||||
controlled-input? (some? value)
|
||||
input-ref (rn/use-ref-atom nil)
|
||||
set-input-ref (rn/use-callback (fn [ref] (reset! input-ref ref)) [])
|
||||
@@ -58,7 +62,7 @@
|
||||
[rn/view
|
||||
{:style container-style
|
||||
:accessibility-label :swap-input}
|
||||
[rn/view {:style (style/content theme)}
|
||||
[rn/view {:style (style/content typing? theme)}
|
||||
[rn/view
|
||||
{:style (style/row-1 loading?)}
|
||||
[rn/pressable {:on-press on-token-press}
|
||||
@@ -78,7 +82,9 @@
|
||||
colors/neutral-50
|
||||
theme)
|
||||
:keyboard-type :numeric
|
||||
:auto-focus true
|
||||
:editable (not input-disabled?)
|
||||
:auto-focus auto-focus?
|
||||
:on-focus on-input-focus
|
||||
:on-change-text on-change-text
|
||||
:show-soft-input-on-focus show-keyboard?
|
||||
:default-value default-value
|
||||
|
||||
@@ -200,13 +200,13 @@
|
||||
(.remove keyboard-hide-listener))))
|
||||
|
||||
(defn screen
|
||||
[{:keys [initial-insets title keypair navigation-icon recovering-keypair? render-controls]}]
|
||||
(let [{navigation-bar-top :top} initial-insets
|
||||
banner-offset (rf/sub [:alert-banners/top-margin])]
|
||||
[{:keys [title keypair navigation-icon recovering-keypair? render-controls]}]
|
||||
(let [[insets _] (rn/use-state (safe-area/get-insets))
|
||||
banner-offset (rf/sub [:alert-banners/top-margin])]
|
||||
[rn/view {:style style/full-layout}
|
||||
[rn/keyboard-avoiding-view {:style style/page-container}
|
||||
[quo/page-nav
|
||||
{:margin-top navigation-bar-top
|
||||
{:margin-top (:top insets)
|
||||
:background :blur
|
||||
:icon-name (or navigation-icon
|
||||
(if recovering-keypair? :i/close :i/arrow-left))
|
||||
@@ -216,15 +216,14 @@
|
||||
:keypair keypair
|
||||
:render-controls render-controls
|
||||
:banner-offset banner-offset
|
||||
:initial-insets initial-insets
|
||||
:initial-insets insets
|
||||
:recovering-keypair? recovering-keypair?}]]]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [insets (safe-area/get-insets)]
|
||||
(fn []
|
||||
(let [{:keys [recovering-keypair?]} (rf/sub [:get-screen-params])]
|
||||
[screen
|
||||
{:title (i18n/label :t/use-recovery-phrase)
|
||||
:initial-insets insets
|
||||
:recovering-keypair? recovering-keypair?}]))))
|
||||
(let [{:keys [recovering-keypair?]} (rf/sub [:get-screen-params])]
|
||||
(rn/use-unmount
|
||||
#(rf/dispatch [:onboarding/clear-navigated-to-enter-seed-phrase-from-screen]))
|
||||
[screen
|
||||
{:title (i18n/label :t/use-recovery-phrase)
|
||||
:recovering-keypair? recovering-keypair?}]))
|
||||
|
||||
@@ -551,6 +551,7 @@
|
||||
(def ^:const send-type-bridge 5)
|
||||
(def ^:const send-type-erc-721-transfer 6)
|
||||
(def ^:const send-type-erc-1155-transfer 7)
|
||||
(def ^:const send-type-swap 8)
|
||||
|
||||
(def ^:const multi-transaction-type-send 0)
|
||||
(def ^:const multi-transaction-type-approve 1)
|
||||
@@ -564,6 +565,7 @@
|
||||
(def ^:const bridge-name-erc-721-transfer "ERC721Transfer")
|
||||
(def ^:const bridge-name-erc-1155-transfer "ERC1155Transfer")
|
||||
(def ^:const bridge-name-hop "Hop")
|
||||
(def ^:const bridge-name-paraswap "Paraswap")
|
||||
|
||||
(def ^:const bridge-assets #{"ETH" "USDT" "USDC" "DAI"})
|
||||
|
||||
@@ -590,9 +592,20 @@
|
||||
(def ^:const default-slippage 0.5)
|
||||
(def ^:const max-recommended-slippage 5)
|
||||
(def ^:const max-slippage-decimal-places 2)
|
||||
(def ^:const swap-default-provider
|
||||
(def ^:const swap-provider-paraswap
|
||||
{:name :paraswap
|
||||
:full-name "Paraswap"
|
||||
:color :blue
|
||||
:contract-address "0xdef171fe48cf0115b1d80b88dc8eab59176fee57"
|
||||
:terms-and-conditions-url "https://files.paraswap.io/tos_v4.pdf"})
|
||||
(def ^:const swap-providers
|
||||
{:paraswap swap-provider-paraswap})
|
||||
|
||||
(def ^:const token-for-fees-symbol "ETH")
|
||||
|
||||
(def ^:const transaction-status-success "Success")
|
||||
(def ^:const transaction-status-pending "Pending")
|
||||
(def ^:const transaction-status-failed "Failed")
|
||||
|
||||
(def ^:const min-token-decimals-to-display 6)
|
||||
(def ^:const swap-proposal-refresh-interval-ms 15000)
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
item])))))
|
||||
|
||||
(defn add-manage-members
|
||||
[{:keys [scroll-enabled? on-scroll]}]
|
||||
[{:keys [on-scroll]}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
selected-participants (rf/sub [:group-chat/selected-participants])
|
||||
deselected-members (rf/sub [:group-chat/deselected-members])
|
||||
@@ -74,7 +74,6 @@
|
||||
(i18n/label (if admin? :t/manage-members :t/add-members))]
|
||||
[gesture/section-list
|
||||
{:key-fn :title
|
||||
:scroll-enabled @scroll-enabled?
|
||||
:on-scroll on-scroll
|
||||
:sticky-section-headers-enabled false
|
||||
:sections (rf/sub [:contacts/grouped-by-first-letter])
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
[{:keys [outgoing content pinned-by outgoing-status deleted? deleted-for-me? content-type
|
||||
bridge-message]
|
||||
:as message-data}
|
||||
{:keys [able-to-send-message? community? can-delete-message-for-everyone?
|
||||
{:keys [able-to-send-message? community? community-member? can-delete-message-for-everyone?
|
||||
message-pin-enabled group-chat group-admin?]}]
|
||||
(concat
|
||||
(when (and outgoing
|
||||
@@ -131,7 +131,8 @@
|
||||
:id :copy}])
|
||||
;; pinning images are temporarily disabled
|
||||
(when (and message-pin-enabled
|
||||
(not= content-type constants/content-type-image))
|
||||
(not= content-type constants/content-type-image)
|
||||
(or community-member? (not community?)))
|
||||
[{:type :main
|
||||
:on-press #(pin-message message-data)
|
||||
:label (i18n/label (if pinned-by
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im.contexts.chat.messenger.messages.constants :as messages.constants]))
|
||||
|
||||
(def permission-context-height 46)
|
||||
|
||||
(defn keyboard-avoiding-container
|
||||
[theme]
|
||||
{:flex 1
|
||||
@@ -73,3 +75,8 @@
|
||||
(reanimated/apply-animations-to-style
|
||||
{:top top}
|
||||
{:row-gap 16}))
|
||||
|
||||
(defn permission-context-sheet
|
||||
[margin-bottom?]
|
||||
{:flex 3 ;; Pushes composer to bottom
|
||||
:margin-bottom (when margin-bottom? permission-context-height)})
|
||||
|
||||
@@ -400,16 +400,18 @@
|
||||
{: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?])]
|
||||
[rn/view {:style {:flex 3}} ;; Pushes composer to bottom
|
||||
[rn/view {:style (style/permission-context-sheet margin-bottom?)}
|
||||
[rn/view {:style {:flex-shrink 1}} ;; Keeps flat list on top
|
||||
[reanimated/flat-list
|
||||
{:key-fn list-key-fn
|
||||
:ref list-ref
|
||||
:bounces false
|
||||
:header [:<>
|
||||
[list-header insets (:able-to-send-message? context)]
|
||||
[list-header insets able-to-send-message?]
|
||||
(when (= (:chat-type chat) constants/private-group-chat-type)
|
||||
[list-group-chat-header chat])]
|
||||
:footer [list-footer
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
[rn/view {:style (style/view-button-container false)}
|
||||
children])]))
|
||||
|
||||
(defn- f-page
|
||||
(defn- page
|
||||
[{:keys [onboarding-profile-data navigation-bar-top]}]
|
||||
(reagent/with-let [show-keyboard? (reagent/atom false)
|
||||
content-scroll-y (reagent/atom 0)
|
||||
@@ -205,7 +205,6 @@
|
||||
[]
|
||||
(let [{:keys [top]} (safe-area/get-insets)
|
||||
onboarding-profile-data (rf/sub [:onboarding/profile])]
|
||||
[:<>
|
||||
[:f> f-page
|
||||
{:navigation-bar-top top
|
||||
:onboarding-profile-data onboarding-profile-data}]]))
|
||||
[page
|
||||
{:navigation-bar-top top
|
||||
:onboarding-profile-data onboarding-profile-data}]))
|
||||
|
||||
@@ -10,15 +10,16 @@
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(rf/defn profile-data-set
|
||||
{:events [:onboarding/profile-data-set]}
|
||||
[{:keys [db]} onboarding-data]
|
||||
{:db (update db :onboarding/profile merge onboarding-data)
|
||||
:dispatch [:navigate-to-within-stack
|
||||
[:screen/onboarding.create-profile-password
|
||||
(get db
|
||||
:onboarding/navigated-to-enter-seed-phrase-from-screen
|
||||
:screen/onboarding.new-to-status)]]})
|
||||
(rf/reg-event-fx
|
||||
:onboarding/profile-data-set
|
||||
(fn [{:keys [db]} [onboarding-data]]
|
||||
(let [navigate-from-screen (get db
|
||||
:onboarding/navigated-to-enter-seed-phrase-from-screen
|
||||
:screen/onboarding.new-to-status)]
|
||||
{:db (update db :onboarding/profile merge onboarding-data)
|
||||
:fx [[:dispatch
|
||||
[:navigate-to-within-stack
|
||||
[:screen/onboarding.create-profile-password navigate-from-screen]]]]})))
|
||||
|
||||
(rf/defn enable-biometrics
|
||||
{:events [:onboarding/enable-biometrics]}
|
||||
@@ -33,6 +34,11 @@
|
||||
{:db (assoc db :onboarding/navigated-to-enter-seed-phrase-from-screen from-screen)
|
||||
:fx [[:dispatch [:navigate-to-within-stack [:screen/onboarding.enter-seed-phrase from-screen]]]]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:onboarding/clear-navigated-to-enter-seed-phrase-from-screen
|
||||
(fn [{:keys [db]}]
|
||||
{:db (dissoc db :onboarding/navigated-to-enter-seed-phrase-from-screen)}))
|
||||
|
||||
(rf/reg-event-fx :onboarding/navigate-to-enable-notifications-from-syncing
|
||||
(fn [{:keys [db]}]
|
||||
{:db (dissoc db :onboarding/profile)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.check-before-syncing.view :as check-before-syncing]
|
||||
[status-im.common.confirmation-drawer.view :as confirmation-drawer]
|
||||
[status-im.common.metrics-confirmation-modal.view :as metrics-modal]
|
||||
[status-im.common.standard-authentication.core :as standard-authentication]
|
||||
@@ -44,32 +43,32 @@
|
||||
:linear
|
||||
50))
|
||||
|
||||
(defn- show-check-before-syncing
|
||||
(defn- navigate-to-new-to-status
|
||||
[]
|
||||
(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn [] [check-before-syncing/view
|
||||
{:on-submit
|
||||
#(debounce/throttle-and-dispatch
|
||||
[:open-modal :screen/onboarding.sign-in]
|
||||
1000)}])
|
||||
:shell? true}]))
|
||||
(when @push-animation-fn-atom
|
||||
(@push-animation-fn-atom))
|
||||
(debounce/throttle-and-dispatch
|
||||
[:open-modal :screen/onboarding.new-to-status]
|
||||
1000))
|
||||
|
||||
(defn- navigate-to-sync-or-recover-profile
|
||||
[]
|
||||
(when @push-animation-fn-atom
|
||||
(@push-animation-fn-atom))
|
||||
(debounce/throttle-and-dispatch
|
||||
[:open-modal :screen/onboarding.sync-or-recover-profile]
|
||||
1000))
|
||||
|
||||
(defn new-account-options
|
||||
[]
|
||||
[quo/action-drawer
|
||||
[[{:icon :i/profile
|
||||
:label (i18n/label :t/create-new-profile)
|
||||
:on-press (fn []
|
||||
(when @push-animation-fn-atom
|
||||
(@push-animation-fn-atom))
|
||||
(debounce/throttle-and-dispatch
|
||||
[:open-modal :screen/onboarding.new-to-status]
|
||||
1000))
|
||||
:on-press navigate-to-new-to-status
|
||||
:accessibility-label :create-new-profile}
|
||||
{:icon :i/multi-profile
|
||||
:label (i18n/label :t/add-existing-status-profile)
|
||||
:on-press show-check-before-syncing
|
||||
:label (i18n/label :t/sync-or-recover-profile)
|
||||
:on-press navigate-to-sync-or-recover-profile
|
||||
:accessibility-label :multi-profile}]]])
|
||||
|
||||
(defn show-new-account-options
|
||||
|
||||
@@ -9,25 +9,45 @@
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- mention-text
|
||||
[literal]
|
||||
(let [mention (rf/sub [:messages/resolve-mention literal])]
|
||||
[quo/text
|
||||
{:style style/mention-text
|
||||
:size :paragraph-1}
|
||||
(str "@" mention)]))
|
||||
|
||||
(defn- parsed-text->hiccup
|
||||
[{:keys [literal destination] message-type :type}]
|
||||
(case message-type
|
||||
"mention" [mention-text literal]
|
||||
"link" destination
|
||||
literal))
|
||||
|
||||
(defn- message-body
|
||||
[message]
|
||||
(let [parsed-text (get-in message [:content :parsed-text])
|
||||
parsed-text-children (:children (first parsed-text))]
|
||||
(into [quo/text
|
||||
{:number-of-lines 1
|
||||
:style style/tag-text
|
||||
:accessibility-label :activity-message-body
|
||||
:size :paragraph-1}]
|
||||
(map-indexed (fn [index {:keys [type literal destination]}]
|
||||
^{:key index}
|
||||
(case type
|
||||
"mention" [quo/text
|
||||
{:style style/mention-text
|
||||
:size :paragraph-1}
|
||||
(str "@" (rf/sub [:messages/resolve-mention literal]))]
|
||||
"link" destination
|
||||
literal))
|
||||
parsed-text-children))))
|
||||
[{:keys [container parsed-text]}]
|
||||
(into container (map parsed-text->hiccup) parsed-text))
|
||||
|
||||
(defn- simple-message
|
||||
[content]
|
||||
(let [parsed-text (get-in content [:content :parsed-text 0 :children])]
|
||||
[message-body
|
||||
{:container [quo/text
|
||||
{:number-of-lines 1
|
||||
:style style/tag-text
|
||||
:accessibility-label :activity-message-body
|
||||
:size :paragraph-1}]
|
||||
:parsed-text parsed-text}]))
|
||||
|
||||
(defn- album-message
|
||||
[content]
|
||||
(let [parsed-text (get-in content [0 :parsedText 0 :children])
|
||||
images (map :image content)]
|
||||
[quo/activity-logs-photos
|
||||
{:photos images
|
||||
:message-text [message-body
|
||||
{:container [:<>]
|
||||
:parsed-text parsed-text}]}]))
|
||||
|
||||
(defn- swipeable
|
||||
[{:keys [extra-fn]} child]
|
||||
@@ -41,12 +61,12 @@
|
||||
|
||||
(defn view
|
||||
[{:keys [notification extra-fn]}]
|
||||
(let [{:keys [author chat-name community-id chat-id
|
||||
message read timestamp]} notification
|
||||
community-chat? (not (string/blank? community-id))
|
||||
community-name (rf/sub [:communities/name community-id])
|
||||
community-logo (rf/sub [:communities/logo community-id])
|
||||
customization-color (rf/sub [:profile/customization-color])]
|
||||
(let [{:keys [author chat-name community-id chat-id message read timestamp
|
||||
album-messages]} notification
|
||||
community-chat? (not (string/blank? community-id))
|
||||
community-name (rf/sub [:communities/name community-id])
|
||||
community-logo (rf/sub [:communities/logo community-id])
|
||||
customization-color (rf/sub [:profile/customization-color])]
|
||||
[swipeable {:extra-fn extra-fn}
|
||||
[gesture/touchable-without-feedback
|
||||
{:on-press (fn []
|
||||
@@ -74,4 +94,6 @@
|
||||
:group-name chat-name
|
||||
:blur? true
|
||||
:size 24}])]
|
||||
:message {:body (message-body message)}}]]]))
|
||||
:message {:body (if album-messages
|
||||
[album-message album-messages]
|
||||
[simple-message message])}}]]]))
|
||||
|
||||
@@ -24,35 +24,32 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn notification-component
|
||||
[{:keys [type] :as notification} index]
|
||||
[{notification-type :type :as notification} index]
|
||||
(let [extra-fn (rn/use-callback
|
||||
(fn []
|
||||
{:notification notification})
|
||||
[notification])
|
||||
props {:notification notification
|
||||
:extra-fn extra-fn}]
|
||||
;; Notifications are expensive to render. Without `delay-render` the opening
|
||||
;; animation of the Activity Center can be clunky and the time to open the
|
||||
;; AC after pressing the bell icon can be high.
|
||||
[rn/view {:style (style/notification-container index)}
|
||||
(cond
|
||||
(= type types/contact-verification)
|
||||
(= notification-type types/contact-verification)
|
||||
[contact-verification/view props]
|
||||
|
||||
(= type types/contact-request)
|
||||
(= notification-type types/contact-request)
|
||||
[contact-requests/view props]
|
||||
|
||||
(= type types/mention)
|
||||
(= notification-type types/mention)
|
||||
[mentions/view props]
|
||||
|
||||
(= type types/reply)
|
||||
(= notification-type types/reply)
|
||||
[reply/view props]
|
||||
|
||||
(= type types/admin)
|
||||
(= notification-type types/admin)
|
||||
[admin/view props]
|
||||
|
||||
(some types/membership [type])
|
||||
(condp = type
|
||||
(types/membership notification-type)
|
||||
(condp = notification-type
|
||||
types/private-group-chat [membership/view props]
|
||||
types/community-request [community-request/view props]
|
||||
types/community-kicked [community-kicked/view props]
|
||||
|
||||
@@ -70,10 +70,10 @@
|
||||
(when pass-through?
|
||||
[reanimated/blur-view (blur-overlay-params bottom-tabs-blur-overlay-style)])
|
||||
[rn/view {:style (style/bottom-tabs)}
|
||||
[gesture/gesture-detector {:gesture communities-double-tab-gesture}
|
||||
[bottom-tab :i/communities :communities-stack shared-values notifications-data]]
|
||||
[bottom-tab :i/wallet :wallet-stack shared-values notifications-data]
|
||||
[gesture/gesture-detector {:gesture messages-double-tap-gesture}
|
||||
[bottom-tab :i/messages :chats-stack shared-values notifications-data]]
|
||||
[bottom-tab :i/wallet :wallet-stack shared-values notifications-data]
|
||||
[gesture/gesture-detector {:gesture communities-double-tab-gesture}
|
||||
[bottom-tab :i/communities :communities-stack shared-values notifications-data]]
|
||||
(when config/show-not-implemented-features?
|
||||
[bottom-tab :i/browser :browser-stack shared-values notifications-data])]]]))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(def ^:const switcher-card-size 160)
|
||||
(def ^:const floating-shell-button-height 44)
|
||||
|
||||
(def ^:const default-selected-stack :communities-stack)
|
||||
(def ^:const default-selected-stack :wallet-stack)
|
||||
|
||||
;; Bottom tabs
|
||||
(def ^:const bottom-tabs-container-height-android 57)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[status-im.common.scan-qr-code.view :as scan-qr-code]
|
||||
[status-im.common.validation.general :as validators]
|
||||
[status-im.contexts.communities.events]
|
||||
[status-im.contexts.wallet.wallet-connect.utils :as wc-utils]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.uri :as wc-uri]
|
||||
[status-im.feature-flags :as ff]
|
||||
[utils.address :as utils-address]
|
||||
[utils.debounce :as debounce]
|
||||
@@ -100,7 +100,7 @@
|
||||
nil
|
||||
|
||||
(and
|
||||
(wc-utils/valid-uri? scanned-text)
|
||||
(wc-uri/valid-uri? scanned-text)
|
||||
(ff/enabled? ::ff/wallet.wallet-connect))
|
||||
(handle-wallet-connect scanned-text)
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
switcher-type :account-options
|
||||
type :no-title}}]
|
||||
(let [{:keys [color emoji watch-only?]} (rf/sub [:wallet/current-viewing-account])
|
||||
networks (rf/sub [:wallet/selected-network-details])]
|
||||
networks (rf/sub [:wallet/selected-network-details])
|
||||
sending-collectible? (rf/sub [:wallet/sending-collectible?])]
|
||||
[quo/page-nav
|
||||
{:type type
|
||||
:icon-name icon-name
|
||||
@@ -41,8 +42,9 @@
|
||||
(not watch-only?))
|
||||
{:icon-name :i/dapps
|
||||
:on-press #(rf/dispatch [:navigate-to :screen/wallet.connected-dapps])})
|
||||
{:content-type :account-switcher
|
||||
:customization-color color
|
||||
:on-press #(on-dapps-press switcher-type)
|
||||
:emoji emoji
|
||||
:type (when watch-only? :watch-only)}]}]))
|
||||
(when-not sending-collectible?
|
||||
{:content-type :account-switcher
|
||||
:customization-color color
|
||||
:on-press #(on-dapps-press switcher-type)
|
||||
:emoji emoji
|
||||
:type (when watch-only? :watch-only)})]}]))
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
:collectible-name (if (> amount 1)
|
||||
(str amount " " nft-name)
|
||||
nft-name)
|
||||
:collectible-number token-id}
|
||||
:collectible-number (when (not= token-id "0")
|
||||
token-id)}
|
||||
:second-tag-prefix :t/from
|
||||
:second-tag {:type :address :address sender}
|
||||
:third-tag-prefix :t/to
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
(ns status-im.contexts.wallet.common.utils
|
||||
(:require [clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[quo.foundations.resources :as resources]
|
||||
[status-im.common.qr-codes.view :as qr-codes]
|
||||
[status-im.constants :as constants]
|
||||
[utils.hex :as utils.hex]
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]
|
||||
[utils.string]))
|
||||
@@ -348,3 +350,131 @@
|
||||
[tokens]
|
||||
(let [priority #(get constants/token-sort-priority (:symbol %) ##Inf)]
|
||||
(sort-by (juxt (comp - :balance) priority) tokens)))
|
||||
|
||||
(defn- transaction-data
|
||||
[{:keys [from-address to-address token-address route data eth-transfer?]}]
|
||||
(let [{:keys [amount-in gas-amount gas-fees]} route
|
||||
eip-1559-enabled? (:eip-1559-enabled gas-fees)
|
||||
{:keys [gas-price max-fee-per-gas-medium
|
||||
max-priority-fee-per-gas]} gas-fees]
|
||||
(cond-> {:From from-address
|
||||
:To (or token-address to-address)
|
||||
:Gas (money/to-hex gas-amount)
|
||||
:Value (when eth-transfer? amount-in)
|
||||
:Nonce nil
|
||||
:Input ""
|
||||
:Data (or data "0x")}
|
||||
eip-1559-enabled? (assoc
|
||||
:TxType "0x02"
|
||||
:MaxFeePerGas
|
||||
(money/to-hex
|
||||
(money/->wei
|
||||
:gwei
|
||||
max-fee-per-gas-medium))
|
||||
:MaxPriorityFeePerGas
|
||||
(money/to-hex
|
||||
(money/->wei
|
||||
:gwei
|
||||
max-priority-fee-per-gas)))
|
||||
(not eip-1559-enabled?) (assoc :TxType "0x00"
|
||||
:GasPrice
|
||||
(money/to-hex
|
||||
(money/->wei
|
||||
:gwei
|
||||
gas-price))))))
|
||||
|
||||
(defn approval-path
|
||||
[{:keys [route from-address to-address token-address]}]
|
||||
(let [{:keys [from]} route
|
||||
from-chain-id (:chain-id from)
|
||||
approval-amount-required (:approval-amount-required route)
|
||||
approval-amount-required-sanitized (-> approval-amount-required
|
||||
(utils.hex/normalize-hex)
|
||||
(native-module/hex-to-number))
|
||||
approval-contract-address (:approval-contract-address route)
|
||||
data (native-module/encode-function-call
|
||||
constants/contract-function-signature-erc20-approve
|
||||
[approval-contract-address
|
||||
approval-amount-required-sanitized])
|
||||
tx-data (transaction-data {:from-address from-address
|
||||
:to-address to-address
|
||||
:token-address token-address
|
||||
:route route
|
||||
:data data
|
||||
:eth-transfer? false})]
|
||||
{:BridgeName constants/bridge-name-transfer
|
||||
:ChainID from-chain-id
|
||||
:TransferTx tx-data}))
|
||||
|
||||
(defn transaction-path
|
||||
[{:keys [from-address to-address token-id-from token-address token-id-to route data
|
||||
slippage-percentage eth-transfer?]}]
|
||||
(let [{:keys [bridge-name amount-in bonder-fees from
|
||||
to]} route
|
||||
tx-data (transaction-data {:from-address from-address
|
||||
:to-address to-address
|
||||
:token-address token-address
|
||||
:route route
|
||||
:data data
|
||||
:eth-transfer? eth-transfer?})
|
||||
to-chain-id (:chain-id to)
|
||||
from-chain-id (:chain-id from)]
|
||||
(cond-> {:BridgeName bridge-name
|
||||
:ChainID from-chain-id}
|
||||
|
||||
(= bridge-name constants/bridge-name-erc-721-transfer)
|
||||
(assoc :ERC721TransferTx
|
||||
(assoc tx-data
|
||||
:Recipient to-address
|
||||
:TokenID token-id-from
|
||||
:ChainID to-chain-id))
|
||||
|
||||
(= bridge-name constants/bridge-name-erc-1155-transfer)
|
||||
(assoc :ERC1155TransferTx
|
||||
(assoc tx-data
|
||||
:Recipient to-address
|
||||
:TokenID token-id-from
|
||||
:ChainID to-chain-id
|
||||
:Amount amount-in))
|
||||
|
||||
(= bridge-name constants/bridge-name-transfer)
|
||||
(assoc :TransferTx tx-data)
|
||||
|
||||
(= bridge-name constants/bridge-name-hop)
|
||||
(assoc :HopTx
|
||||
(assoc tx-data
|
||||
:ChainID from-chain-id
|
||||
:ChainIDTo to-chain-id
|
||||
:Symbol token-id-from
|
||||
:Recipient to-address
|
||||
:Amount amount-in
|
||||
:BonderFee bonder-fees))
|
||||
|
||||
(= bridge-name constants/bridge-name-paraswap)
|
||||
(assoc :SwapTx
|
||||
(assoc tx-data
|
||||
:ChainID from-chain-id
|
||||
:ChainIDTo to-chain-id
|
||||
:TokenIDFrom token-id-from
|
||||
:TokenIDTo token-id-to
|
||||
:SlippagePercentage slippage-percentage))
|
||||
|
||||
(not (or (= bridge-name constants/bridge-name-erc-721-transfer)
|
||||
(= bridge-name constants/bridge-name-transfer)
|
||||
(= bridge-name constants/bridge-name-hop)))
|
||||
(assoc :CbridgeTx
|
||||
(assoc tx-data
|
||||
:ChainID to-chain-id
|
||||
:Symbol token-id-from
|
||||
:Recipient to-address
|
||||
:Amount amount-in)))))
|
||||
|
||||
(defn multi-transaction-command
|
||||
[{:keys [from-address to-address from-asset to-asset amount-out multi-transaction-type]
|
||||
:or {multi-transaction-type constants/multi-transaction-type-unknown}}]
|
||||
{:fromAddress from-address
|
||||
:toAddress to-address
|
||||
:fromAsset from-asset
|
||||
:toAsset to-asset
|
||||
:fromAmount amount-out
|
||||
:type multi-transaction-type})
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
[status-im.common.resources :as resources]
|
||||
[status-im.contexts.wallet.connected-dapps.disconnect-dapp.view :as disconnect-dapp]
|
||||
[status-im.contexts.wallet.connected-dapps.style :as style]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as core]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.string]))
|
||||
@@ -107,9 +107,14 @@
|
||||
:content-container-style (style/dapps-list theme)
|
||||
:render-fn (fn [{:keys [topic pairingTopic name url iconUrl]}]
|
||||
[quo/dapp
|
||||
{:dapp {:avatar (core/compute-dapp-icon-path iconUrl
|
||||
url)
|
||||
:name (core/compute-dapp-name name url)
|
||||
{:dapp {:avatar
|
||||
(data-store/compute-dapp-icon-path
|
||||
iconUrl
|
||||
url)
|
||||
:name
|
||||
(data-store/compute-dapp-name
|
||||
name
|
||||
url)
|
||||
:value url
|
||||
:topic topic
|
||||
:pairing-topic pairingTopic
|
||||
|
||||
@@ -85,8 +85,9 @@
|
||||
(rf/reg-event-fx :wallet/close-account-page
|
||||
(fn [{:keys [db]}]
|
||||
(let [just-completed-transaction? (get-in db [:wallet :ui :send :just-completed-transaction?])]
|
||||
(when-not just-completed-transaction?
|
||||
{:fx [[:dispatch [:wallet/clear-account-tab]]]}))))
|
||||
{:db (update db :wallet dissoc :current-viewing-account-address)
|
||||
:fx [(when-not just-completed-transaction?
|
||||
[:dispatch [:wallet/clear-account-tab]])]})))
|
||||
|
||||
(defn log-rpc-error
|
||||
[_ [{:keys [event params]} error]]
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
:size 32
|
||||
:default-active default-active
|
||||
:data data
|
||||
:on-change #(on-change %)}])
|
||||
:on-change on-change}])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[taoensso.timbre :as log]
|
||||
[utils.address :as address]
|
||||
[utils.hex :as utils.hex]
|
||||
[utils.money :as money]
|
||||
[utils.money :as utils.money]
|
||||
[utils.number]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
@@ -263,9 +263,12 @@
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/set-collectible-to-send
|
||||
(fn [{db :db} [{:keys [collectible current-screen start-flow?]}]]
|
||||
(fn [{db :db} [{:keys [collectible current-screen start-flow? entry-point]}]]
|
||||
(let [viewing-account? (some? (-> db :wallet :current-viewing-account-address))
|
||||
entry-point (when-not viewing-account? :wallet-stack)
|
||||
entry-point (cond
|
||||
entry-point entry-point
|
||||
viewing-account? :account-collectible-tab
|
||||
:else :wallet-stack)
|
||||
collection-data (:collection-data collectible)
|
||||
collectible-data (:collectible-data collectible)
|
||||
contract-type (:contract-type collectible)
|
||||
@@ -518,23 +521,29 @@
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/handle-suggested-routes
|
||||
(fn [_ [data]]
|
||||
(if-let [{:keys [code details]} (-> data :ErrorResponse)]
|
||||
(let [error-message (if (= code "0") "An error occurred" details)]
|
||||
(log/error "failed to get suggested routes (async)"
|
||||
{:event :wallet/handle-suggested-routes
|
||||
:error error-message})
|
||||
{:fx [[:dispatch [:wallet/suggested-routes-error error-message]]]})
|
||||
(let [best-routes-fix (comp ->old-route-paths
|
||||
remove-invalid-bonder-fees-routes
|
||||
remove-multichain-routes)
|
||||
candidates-fix (comp ->old-route-paths
|
||||
remove-invalid-bonder-fees-routes)
|
||||
routes (-> data
|
||||
(data-store/rpc->suggested-routes)
|
||||
(update :best best-routes-fix)
|
||||
(update :candidates candidates-fix))]
|
||||
{:fx [[:dispatch [:wallet/suggested-routes-success routes]]]}))))
|
||||
(fn [{:keys [db]} [data]]
|
||||
(let [swap? (get-in db [:wallet :ui :swap])
|
||||
{:keys [code details] :as error-response} (-> data :ErrorResponse)]
|
||||
(if (and (not swap?) error-response)
|
||||
(let [error-message (if (= code "0") "An error occurred" details)]
|
||||
(log/error "failed to get suggested routes (async)"
|
||||
{:event :wallet/handle-suggested-routes
|
||||
:error error-message})
|
||||
{:fx [(if swap?
|
||||
[:dispatch [:wallet/swap-proposal-error error-message]]
|
||||
[:dispatch [:wallet/suggested-routes-error error-message]])]})
|
||||
(let [best-routes-fix (comp ->old-route-paths
|
||||
remove-invalid-bonder-fees-routes
|
||||
remove-multichain-routes)
|
||||
candidates-fix (comp ->old-route-paths
|
||||
remove-invalid-bonder-fees-routes)
|
||||
routes (-> data
|
||||
(data-store/rpc->suggested-routes)
|
||||
(update :best best-routes-fix)
|
||||
(update :candidates candidates-fix))]
|
||||
{:fx [(if swap?
|
||||
[:dispatch [:wallet/swap-proposal-success routes]]
|
||||
[:dispatch [:wallet/suggested-routes-success routes]])]})))))
|
||||
|
||||
(rf/reg-event-fx :wallet/add-authorized-transaction
|
||||
(fn [{:keys [db]} [transaction]]
|
||||
@@ -576,124 +585,6 @@
|
||||
[{:ms 20
|
||||
:dispatch [:wallet/clean-up-transaction-flow]}]]]})))
|
||||
|
||||
(defn- transaction-data
|
||||
[{:keys [from-address to-address token-address route data eth-transfer?]}]
|
||||
(let [{:keys [amount-in gas-amount gas-fees]} route
|
||||
eip-1559-enabled? (:eip-1559-enabled gas-fees)
|
||||
{:keys [gas-price max-fee-per-gas-medium
|
||||
max-priority-fee-per-gas]} gas-fees]
|
||||
(cond-> {:From from-address
|
||||
:To (or token-address to-address)
|
||||
:Gas (money/to-hex gas-amount)
|
||||
:Value (when eth-transfer? amount-in)
|
||||
:Nonce nil
|
||||
:Input ""
|
||||
:Data (or data "0x")}
|
||||
eip-1559-enabled? (assoc
|
||||
:TxType "0x02"
|
||||
:MaxFeePerGas
|
||||
(money/to-hex
|
||||
(money/->wei
|
||||
:gwei
|
||||
max-fee-per-gas-medium))
|
||||
:MaxPriorityFeePerGas
|
||||
(money/to-hex
|
||||
(money/->wei
|
||||
:gwei
|
||||
max-priority-fee-per-gas)))
|
||||
(not eip-1559-enabled?) (assoc :TxType "0x00"
|
||||
:GasPrice
|
||||
(money/to-hex
|
||||
(money/->wei
|
||||
:gwei
|
||||
gas-price))))))
|
||||
|
||||
(defn- approval-path
|
||||
[{:keys [route from-address to-address token-address]}]
|
||||
(let [{:keys [from]} route
|
||||
from-chain-id (:chain-id from)
|
||||
approval-amount-required (:approval-amount-required route)
|
||||
approval-amount-required-sanitized (-> approval-amount-required
|
||||
(utils.hex/normalize-hex)
|
||||
(native-module/hex-to-number))
|
||||
approval-contract-address (:approval-contract-address route)
|
||||
data (native-module/encode-function-call
|
||||
constants/contract-function-signature-erc20-approve
|
||||
[approval-contract-address
|
||||
approval-amount-required-sanitized])
|
||||
tx-data (transaction-data {:from-address from-address
|
||||
:to-address to-address
|
||||
:token-address token-address
|
||||
:route route
|
||||
:data data
|
||||
:eth-transfer? false})]
|
||||
{:BridgeName constants/bridge-name-transfer
|
||||
:ChainID from-chain-id
|
||||
:TransferTx tx-data}))
|
||||
|
||||
(defn- transaction-path
|
||||
[{:keys [from-address to-address token-id token-address route data eth-transfer?]}]
|
||||
(let [{:keys [bridge-name amount-in bonder-fees from
|
||||
to]} route
|
||||
tx-data (transaction-data {:from-address from-address
|
||||
:to-address to-address
|
||||
:token-address token-address
|
||||
:route route
|
||||
:data data
|
||||
:eth-transfer? eth-transfer?})
|
||||
to-chain-id (:chain-id to)
|
||||
from-chain-id (:chain-id from)]
|
||||
(cond-> {:BridgeName bridge-name
|
||||
:ChainID from-chain-id}
|
||||
|
||||
(= bridge-name constants/bridge-name-erc-721-transfer)
|
||||
(assoc :ERC721TransferTx
|
||||
(assoc tx-data
|
||||
:Recipient to-address
|
||||
:TokenID token-id
|
||||
:ChainID to-chain-id))
|
||||
|
||||
(= bridge-name constants/bridge-name-erc-1155-transfer)
|
||||
(assoc :ERC1155TransferTx
|
||||
(assoc tx-data
|
||||
:Recipient to-address
|
||||
:TokenID token-id
|
||||
:ChainID to-chain-id
|
||||
:Amount amount-in))
|
||||
|
||||
(= bridge-name constants/bridge-name-transfer)
|
||||
(assoc :TransferTx tx-data)
|
||||
|
||||
(= bridge-name constants/bridge-name-hop)
|
||||
(assoc :HopTx
|
||||
(assoc tx-data
|
||||
:ChainID from-chain-id
|
||||
:ChainIDTo to-chain-id
|
||||
:Symbol token-id
|
||||
:Recipient to-address
|
||||
:Amount amount-in
|
||||
:BonderFee bonder-fees))
|
||||
|
||||
(not (or (= bridge-name constants/bridge-name-erc-721-transfer)
|
||||
(= bridge-name constants/bridge-name-transfer)
|
||||
(= bridge-name constants/bridge-name-hop)))
|
||||
(assoc :CbridgeTx
|
||||
(assoc tx-data
|
||||
:ChainID to-chain-id
|
||||
:Symbol token-id
|
||||
:Recipient to-address
|
||||
:Amount amount-in)))))
|
||||
|
||||
(defn- multi-transaction-command
|
||||
[{:keys [from-address to-address from-asset to-asset amount-out multi-transaction-type]
|
||||
:or {multi-transaction-type constants/multi-transaction-type-unknown}}]
|
||||
{:fromAddress from-address
|
||||
:toAddress to-address
|
||||
:fromAsset from-asset
|
||||
:toAsset to-asset
|
||||
:fromAmount amount-out
|
||||
:type multi-transaction-type})
|
||||
|
||||
(rf/reg-event-fx :wallet/send-transaction
|
||||
(fn [{:keys [db]} [sha3-pwd]]
|
||||
(let [routes (get-in db [:wallet :ui :send :route])
|
||||
@@ -725,27 +616,27 @@
|
||||
(native-module/encode-transfer
|
||||
(address/normalized-hex to-address)
|
||||
(:amount-in route)))
|
||||
base-path (transaction-path
|
||||
base-path (utils/transaction-path
|
||||
{:to-address to-address
|
||||
:from-address from-address
|
||||
:route route
|
||||
:token-address token-address
|
||||
:token-id (if collectible
|
||||
:token-id-from (if collectible
|
||||
(utils.hex/number-to-hex
|
||||
token-id)
|
||||
token-id)
|
||||
:data data
|
||||
:eth-transfer? eth-transfer?})]
|
||||
(if approval-required?
|
||||
[(approval-path {:route route
|
||||
:token-address token-address
|
||||
:from-address from-address
|
||||
:to-address to-address})
|
||||
[(utils/approval-path {:route route
|
||||
:token-address token-address
|
||||
:from-address from-address
|
||||
:to-address to-address})
|
||||
base-path]
|
||||
[base-path]))))
|
||||
routes)
|
||||
request-params
|
||||
[(multi-transaction-command
|
||||
[(utils/multi-transaction-command
|
||||
{:from-address from-address
|
||||
:to-address to-address
|
||||
:from-asset token-id
|
||||
@@ -814,3 +705,27 @@
|
||||
{:current-screen stack-id
|
||||
:start-flow? start-flow?
|
||||
:flow-id flow-id}]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/transaction-confirmation-navigate-back
|
||||
(fn [{db :db} [{:keys []}]]
|
||||
(let [tx-type (-> db :wallet :ui :send :tx-type)
|
||||
keep-tx-data? (#{:account-collectible-tab :wallet-stack}
|
||||
(-> db :wallet :ui :send :entry-point))]
|
||||
{:db (cond-> db
|
||||
(and (= tx-type :tx/collectible-erc-721) (not keep-tx-data?))
|
||||
(update-in [:wallet :ui :send] dissoc :tx-type :amount :route :suggested-routes)
|
||||
|
||||
(= tx-type :tx/collectible-erc-1155)
|
||||
(update-in [:wallet :ui :send] dissoc :route :suggested-routes))
|
||||
:fx [[:dispatch [:navigate-back]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/collectible-amount-navigate-back
|
||||
(fn [{db :db} [{:keys []}]]
|
||||
(let [keep-tx-data? (#{:account-collectible-tab :wallet-stack}
|
||||
(-> db :wallet :ui :send :entry-point))]
|
||||
{:db (cond-> db
|
||||
:always (update-in [:wallet :ui :send] dissoc :amount :route)
|
||||
(not keep-tx-data?) (update-in [:wallet :ui :send] dissoc :tx-type))
|
||||
:fx [[:dispatch [:navigate-back]]]})))
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
:skip-step? (fn [db] (or (token-selected? db) (collectible-selected? db)))}
|
||||
{:screen-id :screen/wallet.send-input-amount
|
||||
:skip-step? (fn [db]
|
||||
(send-utils/tx-type-collectible? (get-in db [:wallet :ui :send :tx-type])))}
|
||||
(-> db :wallet :ui :send :tx-type send-utils/tx-type-collectible?))}
|
||||
{:screen-id :screen/wallet.select-collectible-amount
|
||||
:skip-step? (fn [db]
|
||||
(or (not (collectible-selected? db))
|
||||
|
||||
@@ -98,9 +98,10 @@
|
||||
:related-chain-id 1
|
||||
:layer 1}]
|
||||
:wallet/wallet-send-enabled-from-chain-ids [1]
|
||||
:wallet/wallet-send-amount nil
|
||||
:wallet/send-amount nil
|
||||
:wallet/wallet-send-tx-type :tx/send
|
||||
:wallet/wallet-send-fee-fiat-formatted "$5,00"
|
||||
:wallet/sending-collectible? false
|
||||
:wallet/total-amount (money/bignumber "250")})
|
||||
|
||||
(h/describe "Send > input amount screen"
|
||||
|
||||
@@ -9,6 +9,25 @@
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn other-account-item
|
||||
[{:keys [address color emoji network-preferences-names]
|
||||
account-name :name
|
||||
:as account}]
|
||||
(let [full-address (rf/sub [:wallet/account-address address network-preferences-names])]
|
||||
[quo/account-item
|
||||
{:account-props (assoc account
|
||||
:customization-color color
|
||||
:address full-address
|
||||
:full-address? true)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:wallet/select-send-address
|
||||
{:address address
|
||||
:recipient {:recipient-type :account
|
||||
:label account-name
|
||||
:customization-color color
|
||||
:emoji emoji}
|
||||
:stack-id :screen/wallet.select-address}]))}]))
|
||||
|
||||
(defn- my-accounts
|
||||
[theme]
|
||||
(let [other-accounts (rf/sub [:wallet/accounts-without-current-viewing-account])]
|
||||
@@ -20,22 +39,8 @@
|
||||
:container-style style/empty-container-style}]
|
||||
[rn/view {:style style/my-accounts-container}
|
||||
(doall
|
||||
(for [{:keys [color address] :as account} other-accounts]
|
||||
^{:key (str address)}
|
||||
(let [transformed-address (rf/sub [:wallet/account-address address
|
||||
(:network-preferences-names account)])]
|
||||
[quo/account-item
|
||||
{:account-props (assoc account
|
||||
:customization-color color
|
||||
:address transformed-address
|
||||
:full-address? true)
|
||||
:on-press #(rf/dispatch [:wallet/select-send-address
|
||||
{:address address
|
||||
:recipient {:recipient-type :account
|
||||
:label (:name account)
|
||||
:customization-color (:color account)
|
||||
:emoji (:emoji account)}
|
||||
:stack-id :screen/wallet.select-address}])}])))])))
|
||||
(for [{:keys [address] :as account} other-accounts]
|
||||
^{:key (str address)} [other-account-item account]))])))
|
||||
|
||||
(defn- recent-transactions
|
||||
[theme]
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
(let [current-screen-id (rf/sub [:view-id])
|
||||
scanned-address (rf/sub [:wallet/scanned-address])
|
||||
send-address (rf/sub [:wallet/wallet-send-to-address])
|
||||
recipient (rf/sub [:wallet/wallet-send-recipient])
|
||||
recipient (rf/sub [:wallet/send-recipient])
|
||||
recipient-plain-address? (= send-address recipient)
|
||||
valid-ens-or-address? (rf/sub [:wallet/valid-ens-or-address?])
|
||||
contacts (rf/sub [:contacts/active])]
|
||||
@@ -191,7 +191,7 @@
|
||||
[floating-button-page/view
|
||||
{:content-container-style {:flex 1}
|
||||
:footer-container-padding 0
|
||||
:keyboard-should-persist-taps true
|
||||
:keyboard-should-persist-taps :always
|
||||
:header [account-switcher/view
|
||||
{:on-press #(rf/dispatch [:navigate-back])
|
||||
:margin-top (safe-area/get-top)
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
:on-collectible-press (fn [{:keys [collectible]}]
|
||||
(rf/dispatch [:wallet/set-collectible-to-send
|
||||
{:collectible collectible
|
||||
:current-screen :screen/wallet.select-asset}]))}]))
|
||||
:current-screen :screen/wallet.select-asset
|
||||
:entry-point :account-send-button}]))}]))
|
||||
|
||||
(defn- tab-view
|
||||
[search-text selected-tab on-change-text]
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [on-close (rn/use-callback #(rf/dispatch [:navigate-back]))
|
||||
(let [on-close (rn/use-callback
|
||||
#(rf/dispatch [:wallet/collectible-amount-navigate-back]))
|
||||
send-transaction-data (rf/sub [:wallet/wallet-send])
|
||||
collectible (:collectible send-transaction-data)
|
||||
balance (utils/collectible-balance collectible)
|
||||
@@ -23,8 +24,9 @@
|
||||
increase-value (rn/use-callback #(set-value controlled-input/increase))
|
||||
decrease-value (rn/use-callback #(set-value controlled-input/decrease))
|
||||
delete-character (rn/use-callback #(set-value controlled-input/delete-last))
|
||||
add-character (rn/use-callback (fn [c]
|
||||
(set-value #(controlled-input/add-character % c))))]
|
||||
add-character (rn/use-callback
|
||||
(fn [c]
|
||||
(set-value #(controlled-input/add-character % c))))]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(set-value #(controlled-input/set-upper-limit % balance)))
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
|
||||
(defn view
|
||||
[_]
|
||||
(let [on-close #(rf/dispatch [:navigate-back])]
|
||||
(let [on-close #(rf/dispatch [:wallet/transaction-confirmation-navigate-back])]
|
||||
(fn []
|
||||
(let [theme (quo.theme/use-theme)
|
||||
send-transaction-data (rf/sub [:wallet/wallet-send])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
(ns status-im.contexts.wallet.signals
|
||||
(:require
|
||||
[oops.core :as oops]
|
||||
[status-im.constants :as constants]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.transforms :as transforms]))
|
||||
@@ -8,9 +9,33 @@
|
||||
(rf/reg-event-fx
|
||||
:wallet/pending-transaction-status-changed-received
|
||||
(fn [{:keys [db]} [{:keys [message]}]]
|
||||
(let [details (transforms/json->clj message)
|
||||
tx-hash (:hash details)]
|
||||
{:db (update-in db [:wallet :transactions tx-hash] assoc :status :confirmed :blocks 1)})))
|
||||
(let [details (transforms/json->clj message)
|
||||
tx-hash (:hash details)
|
||||
tx-status (:status details)
|
||||
status (cond
|
||||
(= tx-status constants/transaction-status-success)
|
||||
:confirmed
|
||||
(= tx-status constants/transaction-status-pending)
|
||||
:pending
|
||||
(= tx-status constants/transaction-status-failed)
|
||||
:failed)
|
||||
swap-approval-transaction-id (get-in db [:wallet :ui :swap :approval-transaction-id])
|
||||
swap-approval-transaction? (= swap-approval-transaction-id tx-hash)
|
||||
swap-transaction-ids (get-in db [:wallet :swap-transaction-ids])
|
||||
swap-transaction? (and swap-transaction-ids
|
||||
(contains? swap-transaction-ids tx-hash))]
|
||||
(cond-> {:db (update-in db [:wallet :transactions tx-hash] assoc :status status)}
|
||||
swap-approval-transaction?
|
||||
(assoc :fx
|
||||
[[:dispatch
|
||||
[:wallet.swap/approve-transaction-update
|
||||
{:status status}]]])
|
||||
swap-transaction?
|
||||
(assoc :fx
|
||||
[[:dispatch
|
||||
[:wallet.swap/swap-transaction-update
|
||||
{:tx-hash tx-hash
|
||||
:status status}]]])))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/signal-received
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
(ns status-im.contexts.wallet.swap.events
|
||||
(:require [re-frame.core :as rf]
|
||||
(:require [native-module.core :as native-module]
|
||||
[re-frame.core :as rf]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.send.utils :as send-utils]
|
||||
[status-im.contexts.wallet.sheets.network-selection.view :as network-selection]
|
||||
[utils.number]))
|
||||
[taoensso.timbre :as log]
|
||||
[utils.address :as address]
|
||||
[utils.debounce :as debounce]
|
||||
[utils.hex :as hex]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.number]
|
||||
[utils.number :as number]))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/start
|
||||
(fn [{:keys [_db]}]
|
||||
@@ -14,7 +23,9 @@
|
||||
(assoc-in [:wallet :ui :swap :asset-to-pay] token)
|
||||
(assoc-in [:wallet :ui :swap :network] network))
|
||||
:fx (if network
|
||||
[[:dispatch [:navigate-to :screen/wallet.swap-propasal]]
|
||||
[[:dispatch
|
||||
[: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
|
||||
@@ -30,14 +41,9 @@
|
||||
:stack-id
|
||||
:screen/wallet.swap-select-asset-to-pay}]))}])}]]])}))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/clean-asset-to-pay
|
||||
(fn [{:keys [db]}]
|
||||
{:db (update-in db [:wallet :ui :swap] dissoc :asset-to-pay)}))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/set-default-slippage
|
||||
(fn [{:keys [db]}]
|
||||
{:db
|
||||
(assoc-in db [:wallet :ui :swap :max-slippage] constants/default-slippage)}))
|
||||
{:db (assoc-in db [:wallet :ui :swap :max-slippage] constants/default-slippage)}))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/set-max-slippage
|
||||
(fn [{:keys [db]} [max-slippage]]
|
||||
@@ -47,18 +53,318 @@
|
||||
(fn [{:keys [db]} [{:keys [token]}]]
|
||||
{:db (assoc-in db [:wallet :ui :swap :asset-to-receive] token)}))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/set-pay-amount
|
||||
(fn [{:keys [db]} [amount]]
|
||||
{:db (assoc-in db [:wallet :ui :swap :pay-amount] amount)}))
|
||||
(rf/reg-event-fx :wallet/start-get-swap-proposal
|
||||
(fn [{:keys [db]} [{:keys [amount-in amount-out]}]]
|
||||
(let [wallet-address (get-in db [:wallet :current-viewing-account-address])
|
||||
{:keys [asset-to-pay asset-to-receive
|
||||
network]} (get-in db [:wallet :ui :swap])
|
||||
test-networks-enabled? (get-in db [:profile/profile :test-networks-enabled?])
|
||||
networks ((if test-networks-enabled? :test :prod)
|
||||
(get-in db [:wallet :networks]))
|
||||
network-chain-ids (map :chain-id networks)
|
||||
pay-token-decimal (:decimals asset-to-pay)
|
||||
pay-token-id (:symbol asset-to-pay)
|
||||
receive-token-id (:symbol asset-to-receive)
|
||||
receive-token-decimals (:decimals asset-to-receive)
|
||||
gas-rates constants/gas-rate-medium
|
||||
amount-in-hex (if amount-in
|
||||
(send-utils/amount-in-hex amount-in pay-token-decimal)
|
||||
0)
|
||||
amount-out-hex (when amount-out
|
||||
(send-utils/amount-in-hex amount-out receive-token-decimals))
|
||||
to-address wallet-address
|
||||
from-address wallet-address
|
||||
swap-chain-id (:chain-id network)
|
||||
disabled-to-chain-ids (filter #(not= % swap-chain-id) network-chain-ids)
|
||||
disabled-from-chain-ids (filter #(not= % swap-chain-id) network-chain-ids)
|
||||
from-locked-amount {}
|
||||
send-type constants/send-type-swap
|
||||
request-uuid (str (random-uuid))
|
||||
params [(cond->
|
||||
{:uuid request-uuid
|
||||
:sendType send-type
|
||||
:addrFrom from-address
|
||||
:addrTo to-address
|
||||
:tokenID pay-token-id
|
||||
:toTokenID receive-token-id
|
||||
:disabledFromChainIDs disabled-from-chain-ids
|
||||
:disabledToChainIDs disabled-to-chain-ids
|
||||
:gasFeeMode gas-rates
|
||||
:fromLockedAmount from-locked-amount}
|
||||
amount-in (assoc :amountIn amount-in-hex)
|
||||
amount-out (assoc :amountOut amount-out-hex))]]
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/set-swap-proposal
|
||||
(when-let [amount (or amount-in amount-out)]
|
||||
{:db (update-in db
|
||||
[:wallet :ui :swap]
|
||||
#(-> %
|
||||
(assoc
|
||||
:last-request-uuid request-uuid
|
||||
:amount amount
|
||||
:loading-swap-proposal? true)
|
||||
(dissoc :error-response)))
|
||||
:json-rpc/call [{:method "wallet_getSuggestedRoutesV2Async"
|
||||
:params params
|
||||
:on-error (fn [error]
|
||||
(rf/dispatch [:wallet/swap-proposal-error error])
|
||||
(log/error "failed to get suggested routes (async)"
|
||||
{:event :wallet/start-get-swap-proposal
|
||||
:error (:message error)
|
||||
:params params}))}]}))))
|
||||
|
||||
(rf/reg-event-fx :wallet/swap-proposal-success
|
||||
(fn [{:keys [db]} [swap-proposal]]
|
||||
{:db (assoc-in db [:wallet :ui :swap :swap-proposal] swap-proposal)}))
|
||||
(let [last-request-uuid (get-in db [:wallet :ui :swap :last-request-uuid])
|
||||
view-id (:view-id db)
|
||||
request-uuid (:uuid swap-proposal)
|
||||
best-routes (:best swap-proposal)
|
||||
error-response (:error-response swap-proposal)]
|
||||
(when (= request-uuid last-request-uuid)
|
||||
(cond-> {:db (update-in db
|
||||
[:wallet :ui :swap]
|
||||
assoc
|
||||
:swap-proposal (first best-routes)
|
||||
:error-response (when (empty? best-routes) error-response)
|
||||
:loading-swap-proposal? false)}
|
||||
;; Router is unstable and it can return a swap proposal and after auto-refetching it can
|
||||
;; return an error. Ideally this shouldn't happen, but adding this behavior so if the
|
||||
;; user is in swap confirmation screen or in token approval confirmation screen, we
|
||||
;; navigate back to setup swap screen so proper error is displayed.
|
||||
(and (empty? best-routes) (= view-id :screen/wallet.swap-set-spending-cap))
|
||||
(assoc :fx [[:dismiss-modal :screen/wallet.swap-set-spending-cap]])
|
||||
(and (empty? best-routes) (= view-id :screen/wallet.swap-confirmation))
|
||||
(assoc :fx [[:navigate-back]]))))))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/set-provider
|
||||
(rf/reg-event-fx :wallet/swap-proposal-error
|
||||
(fn [{:keys [db]} [error-message]]
|
||||
{:db (-> db
|
||||
(update-in [:wallet :ui :swap] dissoc :route :swap-proposal)
|
||||
(assoc-in [:wallet :ui :swap :loading-swap-proposal?] false)
|
||||
(assoc-in [:wallet :ui :swap :error-response] error-message))
|
||||
:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:id :swap-proposal-error
|
||||
:type :negative
|
||||
:text error-message}]]]}))
|
||||
|
||||
(rf/reg-event-fx :wallet/stop-get-swap-proposal
|
||||
(fn []
|
||||
{:json-rpc/call [{:method "wallet_stopSuggestedRoutesV2AsyncCalcualtion"
|
||||
:params []
|
||||
:on-error (fn [error]
|
||||
(log/error "failed to stop fetching swap proposals"
|
||||
{:event :wallet/stop-get-swap-proposal
|
||||
:error error}))}]}))
|
||||
|
||||
(rf/reg-event-fx :wallet/clean-swap-proposal
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc-in db [:wallet :ui :swap :providers] [constants/swap-default-provider])}))
|
||||
{:db (update-in db
|
||||
[:wallet :ui :swap]
|
||||
dissoc
|
||||
:last-request-uuid
|
||||
:swap-proposal
|
||||
:error-response
|
||||
:loading-swap-proposal?
|
||||
:approval-transaction-id)}))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/recalculate-fees
|
||||
(fn [{:keys [db]} [loading-fees?]]
|
||||
{:db (assoc-in db [:wallet :ui :swap :loading-fees?] loading-fees?)}))
|
||||
(rf/reg-event-fx :wallet/clean-swap
|
||||
(fn [{:keys [db]}]
|
||||
{:db (update-in db [:wallet :ui] dissoc :swap)}))
|
||||
|
||||
(rf/reg-event-fx :wallet/swap-transaction
|
||||
(fn [{:keys [db]} [sha3-pwd]]
|
||||
(let [wallet-address (get-in db
|
||||
[:wallet
|
||||
:current-viewing-account-address])
|
||||
{:keys [asset-to-pay asset-to-receive
|
||||
swap-proposal network amount
|
||||
approval-transaction-id
|
||||
max-slippage]} (get-in db [:wallet :ui :swap])
|
||||
transactions (get-in db [:wallet :transactions])
|
||||
approval-transaction (when approval-transaction-id
|
||||
(get transactions approval-transaction-id))
|
||||
already-approved? (and approval-transaction
|
||||
(= (:status approval-transaction)
|
||||
:confirmed))
|
||||
approval-required? (and (:approval-required swap-proposal)
|
||||
(not already-approved?))
|
||||
multi-transaction-type constants/multi-transaction-type-swap
|
||||
swap-chain-id (:chain-id network)
|
||||
token-id-from (:symbol asset-to-pay)
|
||||
token-id-to (:symbol asset-to-receive)
|
||||
erc20-transfer? (and asset-to-pay (not= token-id-from "ETH"))
|
||||
eth-transfer? (and asset-to-pay (not erc20-transfer?))
|
||||
token-address (when erc20-transfer?
|
||||
(get-in asset-to-pay
|
||||
[:balances-per-chain swap-chain-id
|
||||
:address]))
|
||||
data (when erc20-transfer?
|
||||
(native-module/encode-transfer
|
||||
(address/normalized-hex wallet-address)
|
||||
(:amount-in swap-proposal)))
|
||||
transaction-paths (if approval-required?
|
||||
[(utils/approval-path
|
||||
{:route swap-proposal
|
||||
:token-address token-address
|
||||
:from-address wallet-address
|
||||
:to-address wallet-address})]
|
||||
[(utils/transaction-path
|
||||
{:to-address wallet-address
|
||||
:from-address wallet-address
|
||||
:route swap-proposal
|
||||
:token-address token-address
|
||||
:token-id-from token-id-from
|
||||
:token-id-to token-id-to
|
||||
:data data
|
||||
:slippage-percentage max-slippage
|
||||
:eth-transfer? eth-transfer?})])
|
||||
request-params [(utils/multi-transaction-command
|
||||
{:from-address wallet-address
|
||||
:to-address wallet-address
|
||||
:from-asset token-id-from
|
||||
:to-asset (if approval-required?
|
||||
token-id-from
|
||||
token-id-to)
|
||||
:amount-out (if eth-transfer?
|
||||
(:amount-out swap-proposal)
|
||||
"0x0")
|
||||
:multi-transaction-type
|
||||
multi-transaction-type})
|
||||
transaction-paths
|
||||
sha3-pwd]]
|
||||
(log/info "multi transaction called")
|
||||
{:json-rpc/call [{:method "wallet_createMultiTransaction"
|
||||
:params request-params
|
||||
:on-success (fn [result]
|
||||
(when result
|
||||
(let [receive-token-decimals (:decimals asset-to-receive)
|
||||
amount-out (:amount-out swap-proposal)
|
||||
decimals-to-display
|
||||
(min
|
||||
receive-token-decimals
|
||||
constants/min-token-decimals-to-display)
|
||||
receive-amount (when amount-out
|
||||
(number/remove-trailing-zeroes
|
||||
(.toFixed (number/convert-to-whole-number
|
||||
(native-module/hex-to-number
|
||||
(hex/normalize-hex
|
||||
amount-out))
|
||||
receive-token-decimals)
|
||||
decimals-to-display)))]
|
||||
(rf/dispatch [:wallet.swap/add-authorized-transaction
|
||||
(cond-> {:transaction result
|
||||
:approval-transaction?
|
||||
approval-required?}
|
||||
(not approval-required?)
|
||||
(assoc :swap-data
|
||||
{:pay-token-symbol token-id-from
|
||||
:pay-amount amount
|
||||
:receive-token-symbol token-id-to
|
||||
:receive-amount receive-amount}))])
|
||||
(rf/dispatch [:hide-bottom-sheet])
|
||||
(rf/dispatch [:dismiss-modal
|
||||
(if approval-required?
|
||||
:screen/wallet.swap-set-spending-cap
|
||||
:screen/wallet.swap-confirmation)])
|
||||
(when-not approval-required?
|
||||
(rf/dispatch [:wallet/select-account-tab :activity])
|
||||
(debounce/debounce-and-dispatch [:wallet/clean-swap] 1000)
|
||||
(debounce/debounce-and-dispatch
|
||||
[:toasts/upsert
|
||||
{:id :swap-transaction-pending
|
||||
:icon :i/info
|
||||
:type :neutral
|
||||
:text (i18n/label :t/swapping-to
|
||||
{:pay-amount amount
|
||||
:pay-token-symbol token-id-from
|
||||
:receive-token-symbol token-id-to
|
||||
:receive-amount receive-amount})}]
|
||||
500)))))
|
||||
:on-error (fn [error]
|
||||
(log/error "failed swap transaction"
|
||||
{:event :wallet/swap-transaction
|
||||
:error error
|
||||
:params request-params})
|
||||
(rf/dispatch [:toasts/upsert
|
||||
{:id :swap-transaction-error
|
||||
:type :negative
|
||||
:text (:message error)}]))}]})))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/add-authorized-transaction
|
||||
(fn [{:keys [db]} [{:keys [transaction swap-data approval-transaction?]}]]
|
||||
(let [transactions (get-in db [:wallet :transactions] {})
|
||||
transaction-batch-id (:id transaction)
|
||||
transaction-hashes (:hashes transaction)
|
||||
transaction-ids (flatten (vals transaction-hashes))
|
||||
transaction-id (first transaction-ids)
|
||||
transaction-details (cond-> (send-utils/map-multitransaction-by-ids transaction-batch-id
|
||||
transaction-hashes)
|
||||
:always (assoc-in [transaction-id :tx-type] :swap)
|
||||
swap-data (assoc-in [transaction-id :swap-data] swap-data))
|
||||
swap-transaction-ids (get-in db [:wallet :swap-transaction-ids])]
|
||||
{:db (cond-> db
|
||||
:always (assoc-in [:wallet :transactions]
|
||||
(merge transactions transaction-details))
|
||||
:always (assoc-in [:wallet :ui :swap :transaction-ids] transaction-ids)
|
||||
approval-transaction? (assoc-in [:wallet :ui :swap :approval-transaction-id]
|
||||
transaction-id)
|
||||
(not approval-transaction?) (assoc-in [:wallet :swap-transaction-ids]
|
||||
(if swap-transaction-ids
|
||||
(conj swap-transaction-ids transaction-id)
|
||||
(hash-set transaction-id))))})))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/approve-transaction-update
|
||||
(fn [{:keys [db]} [{:keys [status]}]]
|
||||
(let [{:keys [amount asset-to-pay swap-proposal]} (get-in db [:wallet :ui :swap])
|
||||
provider-name (:bridge-name swap-proposal)
|
||||
token-symbol (:symbol asset-to-pay)
|
||||
current-viewing-account-address (get-in db
|
||||
[:wallet :current-viewing-account-address])
|
||||
account-name (get-in db
|
||||
[:wallet :accounts
|
||||
current-viewing-account-address :name])
|
||||
transaction-confirmed-or-failed? (#{:confirmed :failed} status)
|
||||
transaction-confirmed? (= status :confirmed)]
|
||||
(when transaction-confirmed-or-failed?
|
||||
(cond-> {:fx
|
||||
[[:dispatch
|
||||
[:toasts/upsert
|
||||
{:id :approve-transaction-update
|
||||
:type (if transaction-confirmed? :positive :negative)
|
||||
:text (if transaction-confirmed?
|
||||
(i18n/label :t/spending-cap-set
|
||||
{:amount amount
|
||||
:token-symbol token-symbol
|
||||
:provider-name provider-name
|
||||
:account-name account-name})
|
||||
(i18n/label :t/spending-cap-failed
|
||||
{:amount amount
|
||||
:token-symbol token-symbol
|
||||
:provider-name provider-name
|
||||
:account-name account-name}))}]]]}
|
||||
(not transaction-confirmed?)
|
||||
(assoc :db (update-in db [:wallet :ui :swap] dissoc :approval-transaction-id)))))))
|
||||
|
||||
(rf/reg-event-fx :wallet.swap/swap-transaction-update
|
||||
(fn [{:keys [db]} [{:keys [tx-hash status]}]]
|
||||
(let [{:keys [pay-amount pay-token-symbol
|
||||
receive-amount receive-token-symbol]} (get-in db
|
||||
[:wallet :transactions tx-hash
|
||||
:swap-data])
|
||||
transaction-confirmed-or-failed? (#{:confirmed :failed} status)
|
||||
transaction-confirmed? (= status :confirmed)]
|
||||
(when transaction-confirmed-or-failed?
|
||||
{:db (-> db
|
||||
(update-in [:wallet :swap-transaction-ids] disj tx-hash)
|
||||
(update-in [:wallet :transactions] dissoc tx-hash))
|
||||
:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:id :swap-transaction-update
|
||||
:type (if transaction-confirmed? :positive :negative)
|
||||
:text (if transaction-confirmed?
|
||||
(i18n/label :t/swapped-to
|
||||
{:pay-amount pay-amount
|
||||
:pay-token-symbol pay-token-symbol
|
||||
:receive-token-symbol receive-token-symbol
|
||||
:receive-amount receive-amount})
|
||||
(i18n/label :t/swap-failed))}]]]}))))
|
||||
|
||||
@@ -18,36 +18,18 @@
|
||||
:value search-text
|
||||
:on-change-text on-change-text}]])
|
||||
|
||||
(def dummy-swap-proposal
|
||||
{:from {:chain-id 1
|
||||
:native-currency-symbol "ETH"}
|
||||
:to {:chain-id 1
|
||||
:native-currency-symbol "ETH"}
|
||||
:gas-amount "23487"
|
||||
:gas-fees {:base-fee "32.325296406"
|
||||
:max-priority-fee-per-gas "0.011000001"
|
||||
:eip1559-enabled true}
|
||||
:estimated-time 3
|
||||
:receive-amount "99.98"
|
||||
:pay-token {:symbol "SNT"
|
||||
:address "0x432492384728934239789"}
|
||||
:receive-token {:symbol "USDT"
|
||||
:address "0x432492384728934239789"}})
|
||||
|
||||
(defn- assets-view
|
||||
[search-text on-change-text]
|
||||
(let [on-token-press (fn [token]
|
||||
(let [token-networks (:networks token)
|
||||
asset-to-receive (rf/sub [:wallet/token-by-symbol "SNT"])]
|
||||
asset-to-receive (rf/sub [:wallet/token-by-symbol "DAI"])]
|
||||
(rf/dispatch [:wallet.swap/select-asset-to-pay
|
||||
{:token token
|
||||
:network (when (= (count token-networks) 1)
|
||||
(first token-networks))
|
||||
:stack-id :screen/wallet.swap-select-asset-to-pay}])
|
||||
(rf/dispatch [:wallet.swap/select-asset-to-receive {:token asset-to-receive}])
|
||||
(rf/dispatch [:wallet.swap/set-pay-amount 100])
|
||||
(rf/dispatch [:wallet.swap/set-swap-proposal dummy-swap-proposal])
|
||||
(rf/dispatch [:wallet.swap/set-provider])))]
|
||||
(rf/dispatch [:wallet.swap/select-asset-to-receive
|
||||
{:token asset-to-receive}])))]
|
||||
[:<>
|
||||
[search-input search-text on-change-text]
|
||||
[asset-list/view
|
||||
@@ -59,7 +41,7 @@
|
||||
(let [[search-text set-search-text] (rn/use-state "")
|
||||
on-change-text #(set-search-text %)
|
||||
on-close (fn []
|
||||
(rf/dispatch [:wallet.swap/clean-asset-to-pay])
|
||||
(rf/dispatch [:wallet/clean-swap])
|
||||
(rf/dispatch [:navigate-back]))]
|
||||
[rn/safe-area-view {:style style/container}
|
||||
[account-switcher/view
|
||||
|
||||
@@ -7,84 +7,102 @@
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.common.floating-button-page.view :as floating-button-page]
|
||||
[status-im.common.standard-authentication.core :as standard-auth]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.utils.external-links :as external-links]
|
||||
[status-im.contexts.wallet.swap.set-spending-cap.style :as style]
|
||||
[utils.address :as address-utils]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(defn- swap-title
|
||||
[{:keys [pay-token-symbol pay-amount account provider]}]
|
||||
[rn/view {:style style/content-container}
|
||||
[rn/view {:style {:flex-direction :row}}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :set-spending-cap-of}
|
||||
(i18n/label :t/set-spending-cap-of)]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/summary-tag
|
||||
{:token pay-token-symbol
|
||||
:label (str pay-amount " " pay-token-symbol)
|
||||
:type :token}]
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :for}
|
||||
(i18n/label :t/for)]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/summary-tag
|
||||
{:label (:full-name provider)
|
||||
:type :network
|
||||
:image-source (resources/get-network (:name provider))
|
||||
:customization-color (:color provider)}]
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :on}
|
||||
(i18n/label :t/on)]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/summary-tag
|
||||
{:label (:name account)
|
||||
:type :account
|
||||
:emoji (:emoji account)
|
||||
:customization-color (:color account)}]]])
|
||||
[]
|
||||
(let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
pay-amount (rf/sub [:wallet/swap-pay-amount])
|
||||
account (rf/sub [:wallet/current-viewing-account])
|
||||
provider (rf/sub [:wallet/swap-proposal-provider])
|
||||
pay-token-symbol (:symbol asset-to-pay)]
|
||||
[rn/view {:style style/content-container}
|
||||
[rn/view {:style {:flex-direction :row}}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :set-spending-cap-of}
|
||||
(i18n/label :t/set-spending-cap-of)]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/summary-tag
|
||||
{:token pay-token-symbol
|
||||
:label (str pay-amount " " pay-token-symbol)
|
||||
:type :token}]
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :for}
|
||||
(i18n/label :t/for)]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/summary-tag
|
||||
{:label (:full-name provider)
|
||||
:type :network
|
||||
:image-source (resources/get-network (:name provider))
|
||||
:customization-color (:color provider)}]
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :on}
|
||||
(i18n/label :t/on)]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/summary-tag
|
||||
{:label (:name account)
|
||||
:type :account
|
||||
:emoji (:emoji account)
|
||||
:customization-color (:color account)}]]]))
|
||||
|
||||
(defn- spending-cap-section
|
||||
[{:keys [theme amount token-symbol]}]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :spending-cap-label}
|
||||
(i18n/label :t/spending-cap)]
|
||||
[quo/approval-info
|
||||
{:type :spending-cap
|
||||
:unlimited-icon? false
|
||||
:label (str amount " " token-symbol)
|
||||
:avatar-props {:token token-symbol}}]])
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
pay-amount (rf/sub [:wallet/swap-pay-amount])
|
||||
pay-token-symbol (:symbol asset-to-pay)]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :spending-cap-label}
|
||||
(i18n/label :t/spending-cap)]
|
||||
(when (and asset-to-pay pay-amount)
|
||||
[quo/approval-info
|
||||
{:type :spending-cap
|
||||
:unlimited-icon? false
|
||||
:label (str pay-amount " " pay-token-symbol)
|
||||
:avatar-props {:token pay-token-symbol}}])]))
|
||||
|
||||
(defn- account-section
|
||||
[{:keys [theme account pay-token-symbol pay-token-amount]}]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :account-label}
|
||||
(i18n/label :t/account)]
|
||||
[quo/approval-info
|
||||
{:type :account
|
||||
:unlimited-icon? false
|
||||
:label (:name account)
|
||||
:description (address-utils/get-short-wallet-address (:address account))
|
||||
:tag-label (str pay-token-amount " " pay-token-symbol)
|
||||
:avatar-props {:emoji (:emoji account)
|
||||
:customization-color (:color account)}}]])
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
account (rf/sub [:wallet/current-viewing-account])
|
||||
pay-amount (rf/sub [:wallet/swap-pay-amount])
|
||||
pay-token-symbol (:symbol asset-to-pay)]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :account-label}
|
||||
(i18n/label :t/account)]
|
||||
(when (and asset-to-pay pay-amount)
|
||||
[quo/approval-info
|
||||
{:type :account
|
||||
:unlimited-icon? false
|
||||
:label (:name account)
|
||||
:description (address-utils/get-short-wallet-address (:address account))
|
||||
:tag-label (str pay-amount " " pay-token-symbol)
|
||||
:avatar-props {:emoji (:emoji account)
|
||||
:customization-color (:color account)}}])]))
|
||||
|
||||
(defn- on-option-press
|
||||
[{:keys [chain-id contract-address]}]
|
||||
@@ -103,42 +121,54 @@
|
||||
:right-icon :i/external}]]])}]))
|
||||
|
||||
(defn- token-section
|
||||
[{:keys [theme token-address token-symbol network-chain-id]}]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :token-label}
|
||||
(i18n/label :t/token)]
|
||||
[quo/approval-info
|
||||
{:type :token-contract
|
||||
:option-icon :i/options
|
||||
:on-option-press #(on-option-press {:chain-id network-chain-id
|
||||
:contract-address token-address})
|
||||
:unlimited-icon? false
|
||||
:label token-symbol
|
||||
:description (address-utils/get-short-wallet-address token-address)
|
||||
:avatar-props {:token token-symbol}}]])
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
network-chain-id (:chain-id network)
|
||||
pay-token-address (get-in asset-to-pay [:balances-per-chain network-chain-id :address])]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :token-label}
|
||||
(i18n/label :t/token)]
|
||||
(when asset-to-pay
|
||||
[quo/approval-info
|
||||
{:type :token-contract
|
||||
:option-icon :i/options
|
||||
:on-option-press #(on-option-press {:chain-id network-chain-id
|
||||
:contract-address pay-token-address})
|
||||
:unlimited-icon? false
|
||||
:label pay-token-symbol
|
||||
:description (address-utils/get-short-wallet-address pay-token-address)
|
||||
:avatar-props {:token pay-token-symbol}}])]))
|
||||
|
||||
(defn- spender-contract-section
|
||||
[{:keys [theme provider network-chain-id]}]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :spender-contract-label}
|
||||
(i18n/label :t/spender-contract)]
|
||||
[quo/approval-info
|
||||
{:type :token-contract
|
||||
:option-icon :i/options
|
||||
:on-option-press #(on-option-press {:chain-id network-chain-id
|
||||
:contract-address (:contract-address provider)})
|
||||
:unlimited-icon? false
|
||||
:label (:full-name provider)
|
||||
:description (address-utils/get-short-wallet-address (:contract-address provider))
|
||||
:avatar-props {:image (resources/get-network (:name provider))}}]])
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
provider (rf/sub [:wallet/swap-proposal-provider])
|
||||
network-chain-id (:chain-id network)]
|
||||
[rn/view {:style style/summary-section-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style (style/section-label theme)
|
||||
:accessibility-label :spender-contract-label}
|
||||
(i18n/label :t/spender-contract)]
|
||||
(when provider
|
||||
[quo/approval-info
|
||||
{:type :token-contract
|
||||
:option-icon :i/options
|
||||
:on-option-press #(on-option-press {:chain-id network-chain-id
|
||||
:contract-address (:contract-address provider)})
|
||||
:unlimited-icon? false
|
||||
:label (:full-name provider)
|
||||
:description (address-utils/get-short-wallet-address (:contract-address provider))
|
||||
:avatar-props {:image (resources/get-network (:name provider))}}])]))
|
||||
|
||||
(defn- data-item
|
||||
[{:keys [network-image title subtitle size loading?]}]
|
||||
@@ -154,56 +184,57 @@
|
||||
:size size}])
|
||||
|
||||
(defn- transaction-details
|
||||
[{:keys [estimated-time-min max-fees network loading-fees?]}]
|
||||
[rn/view {:style style/details-container}
|
||||
[:<>
|
||||
[data-item
|
||||
{:title (i18n/label :t/network)
|
||||
:subtitle (:full-name network)
|
||||
:network-image (:source network)}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time-min)})}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-fees)
|
||||
:subtitle max-fees
|
||||
:loading? loading-fees?
|
||||
:size :small}]]])
|
||||
[]
|
||||
(let [network (rf/sub [:wallet/swap-network])
|
||||
max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted
|
||||
constants/token-for-fees-symbol])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
estimated-time (rf/sub [:wallet/swap-proposal-estimated-time])]
|
||||
[rn/view {:style style/details-container}
|
||||
[:<>
|
||||
[data-item
|
||||
{:title (i18n/label :t/network)
|
||||
:subtitle (:full-name network)
|
||||
:network-image (:source network)}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-fees)
|
||||
:subtitle (if (and estimated-time max-fees) max-fees (i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?
|
||||
:size :small}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (if estimated-time
|
||||
(i18n/label :t/time-in-mins {:minutes (str estimated-time)})
|
||||
(i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?
|
||||
:size :small}]]]))
|
||||
|
||||
(defn footer
|
||||
[{:keys [estimated-time-min native-currency-symbol network theme account-color loading-fees?]}]
|
||||
(let [fee-formatted (rf/sub [:wallet/wallet-send-fee-fiat-formatted native-currency-symbol])
|
||||
on-auth-success (rn/use-callback #(js/alert "Not implemented yet"))]
|
||||
[rn/view {:style {:margin-bottom -10}}
|
||||
[transaction-details
|
||||
{:estimated-time-min estimated-time-min
|
||||
:max-fees fee-formatted
|
||||
:network network
|
||||
:loading-fees? loading-fees?
|
||||
:theme theme}]
|
||||
[standard-auth/slide-button
|
||||
{:size :size-48
|
||||
:track-text (i18n/label :t/slide-to-swap)
|
||||
:container-style {:z-index 2}
|
||||
:customization-color account-color
|
||||
:disabled? loading-fees?
|
||||
:on-auth-success on-auth-success
|
||||
:auth-button-label (i18n/label :t/confirm)}]]))
|
||||
(defn- slide-button
|
||||
[]
|
||||
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
swap-proposal (rf/sub [:wallet/swap-proposal])
|
||||
account (rf/sub [:wallet/current-viewing-account])
|
||||
on-auth-success (rn/use-callback #(rf/dispatch
|
||||
[:wallet/swap-transaction
|
||||
(security/safe-unmask-data %)]))]
|
||||
[standard-auth/slide-button
|
||||
{:size :size-48
|
||||
:track-text (i18n/label :t/slide-to-sign)
|
||||
:container-style {:z-index 2}
|
||||
:customization-color (:color account)
|
||||
:disabled? (or loading-swap-proposal? (not swap-proposal))
|
||||
:on-auth-success on-auth-success
|
||||
:auth-button-label (i18n/label :t/confirm)}]))
|
||||
|
||||
(defn- footer
|
||||
[]
|
||||
[rn/view {:style {:margin-bottom -10}}
|
||||
[transaction-details]
|
||||
[slide-button]])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
swap-transaction-data (rf/sub [:wallet/swap])
|
||||
{:keys [asset-to-pay network pay-amount
|
||||
providers swap-proposal
|
||||
loading-fees?]} swap-transaction-data
|
||||
estimated-time-min (:estimated-time swap-proposal)
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
pay-token-address (:address asset-to-pay)
|
||||
native-currency-symbol (get-in swap-proposal [:from :native-currency-symbol])
|
||||
account (rf/sub [:wallet/current-viewing-account])
|
||||
account-color (:color account)
|
||||
provider (first providers)]
|
||||
(let [account (rf/sub [:wallet/current-viewing-account])]
|
||||
[rn/view {:style style/container}
|
||||
[floating-button-page/view
|
||||
{:footer-container-padding 0
|
||||
@@ -213,37 +244,12 @@
|
||||
:margin-top 8
|
||||
:background :blur
|
||||
:accessibility-label :top-bar}]
|
||||
:footer [footer
|
||||
{:estimated-time-min estimated-time-min
|
||||
:native-currency-symbol native-currency-symbol
|
||||
:network network
|
||||
:account-color account-color
|
||||
:provider provider
|
||||
:loading-fees? loading-fees?
|
||||
:theme theme}]
|
||||
:footer [footer]
|
||||
:gradient-cover? true
|
||||
:customization-color account-color}
|
||||
:customization-color (:color account)}
|
||||
[:<>
|
||||
[swap-title
|
||||
{:pay-token-symbol pay-token-symbol
|
||||
:pay-amount pay-amount
|
||||
:account account
|
||||
:provider provider}]
|
||||
[spending-cap-section
|
||||
{:token-symbol pay-token-symbol
|
||||
:amount pay-amount
|
||||
:theme theme}]
|
||||
[account-section
|
||||
{:account account
|
||||
:pay-token-symbol pay-token-symbol
|
||||
:pay-token-amount pay-amount
|
||||
:theme theme}]
|
||||
[token-section
|
||||
{:token-symbol pay-token-symbol
|
||||
:token-address pay-token-address
|
||||
:network-chain-id (:chain-id network)
|
||||
:theme theme}]
|
||||
[spender-contract-section
|
||||
{:provider provider
|
||||
:network-chain-id (:chain-id network)
|
||||
:theme theme}]]]]))
|
||||
[swap-title]
|
||||
[spending-cap-section]
|
||||
[account-section]
|
||||
[token-section]
|
||||
[spender-contract-section]]]]))
|
||||
|
||||
@@ -22,14 +22,19 @@
|
||||
:height 36
|
||||
:background-color :transparent})
|
||||
|
||||
(def swap-order-button
|
||||
{:margin-top -9
|
||||
(defn swap-order-button
|
||||
[approval-required?]
|
||||
{:margin-top (if approval-required? 3 -9)
|
||||
:z-index 2
|
||||
:align-self :center})
|
||||
|
||||
(def receive-token-swap-input-container
|
||||
{:margin-top -9})
|
||||
(defn receive-token-swap-input-container
|
||||
[approval-required?]
|
||||
{:margin-top (if approval-required? 3 -9)})
|
||||
|
||||
(def footer-container
|
||||
{:flex 1
|
||||
:justify-content :flex-end})
|
||||
|
||||
(def alert-banner
|
||||
{:height 40})
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
(ns status-im.contexts.wallet.swap.setup-swap.view
|
||||
(:require [quo.core :as quo]
|
||||
(:require [clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.controlled-input.utils :as controlled-input]
|
||||
[status-im.common.events-helper :as events-helper]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[status-im.contexts.wallet.swap.setup-swap.style :as style]
|
||||
[utils.hex :as hex]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.string :as utils.string]))
|
||||
|
||||
(def ^:private default-text-for-unfocused-input "0.00")
|
||||
|
||||
(defn- on-close
|
||||
[]
|
||||
(rf/dispatch [:wallet/clean-swap-proposal])
|
||||
(events-helper/navigate-back))
|
||||
|
||||
(defn- fetch-swap-proposal
|
||||
[{:keys [amount valid-input?]}]
|
||||
(if valid-input?
|
||||
(rf/dispatch [:wallet/start-get-swap-proposal {:amount-in amount}])
|
||||
(rf/dispatch [:wallet/clean-swap-proposal])))
|
||||
|
||||
(defn- data-item
|
||||
[{:keys [title subtitle size subtitle-icon loading?]}]
|
||||
@@ -23,110 +45,300 @@
|
||||
:icon subtitle-icon}])
|
||||
|
||||
(defn- transaction-details
|
||||
[{:keys [max-slippage native-currency-symbol loading-fees?]}]
|
||||
(let [max-fees (rf/sub [:wallet/wallet-send-fee-fiat-formatted native-currency-symbol])]
|
||||
[]
|
||||
(let [max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted
|
||||
constants/token-for-fees-symbol])
|
||||
max-slippage (rf/sub [:wallet/swap-max-slippage])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])]
|
||||
[rn/view {:style style/details-container}
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-fees)
|
||||
:subtitle max-fees
|
||||
:loading? loading-fees?
|
||||
:loading? loading-swap-proposal?
|
||||
:size :small}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-slippage)
|
||||
:subtitle max-slippage
|
||||
:subtitle-icon :i/edit
|
||||
:loading? loading-fees?}]]))
|
||||
:size :small}]]))
|
||||
|
||||
(defn- pay-token-input
|
||||
[{:keys [input-state on-max-press on-input-focus on-token-press on-approve-press input-focused?]}]
|
||||
(let [account-color (rf/sub [:wallet/current-viewing-account-color])
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
currency (rf/sub [:profile/currency])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
swap-proposal (rf/sub [:wallet/swap-proposal])
|
||||
approval-required (rf/sub [:wallet/swap-proposal-approval-required])
|
||||
approval-amount-required (rf/sub [:wallet/swap-proposal-approval-amount-required])
|
||||
currency-symbol (rf/sub [:profile/currency-symbol])
|
||||
approval-transaction-status (rf/sub [:wallet/swap-approval-transaction-status])
|
||||
pay-input-num-value (controlled-input/numeric-value input-state)
|
||||
pay-input-amount (controlled-input/input-value input-state)
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
pay-token-decimals (:decimals asset-to-pay)
|
||||
pay-token-balance-selected-chain (get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :balance]
|
||||
0)
|
||||
pay-token-fiat-value (str
|
||||
(utils/calculate-token-fiat-value
|
||||
{:currency currency
|
||||
:balance (or pay-input-num-value 0)
|
||||
:token asset-to-pay}))
|
||||
available-crypto-limit (number/remove-trailing-zeroes
|
||||
(.toFixed (money/bignumber
|
||||
pay-token-balance-selected-chain)
|
||||
(min pay-token-decimals
|
||||
constants/min-token-decimals-to-display)))
|
||||
approval-amount-required-num (when approval-amount-required
|
||||
(str (number/convert-to-whole-number
|
||||
(native-module/hex-to-number
|
||||
(hex/normalize-hex
|
||||
approval-amount-required))
|
||||
pay-token-decimals)))
|
||||
pay-input-error? (and (not (string/blank? pay-input-amount))
|
||||
(money/greater-than
|
||||
(money/bignumber pay-input-num-value)
|
||||
(money/bignumber
|
||||
pay-token-balance-selected-chain)))
|
||||
valid-pay-input? (and
|
||||
(not (string/blank?
|
||||
pay-input-amount))
|
||||
(> pay-input-amount 0)
|
||||
(not pay-input-error?))
|
||||
request-fetch-swap-proposal (rn/use-callback
|
||||
(fn []
|
||||
(fetch-swap-proposal
|
||||
{:amount pay-input-amount
|
||||
:valid-input? valid-pay-input?}))
|
||||
[pay-input-amount])]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(request-fetch-swap-proposal))
|
||||
[pay-input-amount])
|
||||
[quo/swap-input
|
||||
{:type :pay
|
||||
:error? pay-input-error?
|
||||
:token pay-token-symbol
|
||||
:customization-color :blue
|
||||
:show-approval-label? (and swap-proposal approval-required)
|
||||
:auto-focus? true
|
||||
:show-keyboard? false
|
||||
:status (cond
|
||||
(and loading-swap-proposal? (not input-focused?)) :loading
|
||||
input-focused? :typing
|
||||
:else :disabled)
|
||||
:currency-symbol currency-symbol
|
||||
:on-token-press on-token-press
|
||||
:on-max-press #(on-max-press available-crypto-limit)
|
||||
:on-input-focus on-input-focus
|
||||
:value pay-input-amount
|
||||
:fiat-value pay-token-fiat-value
|
||||
:network-tag-props {:title (i18n/label :t/max-token
|
||||
{:number available-crypto-limit
|
||||
:token-symbol pay-token-symbol})
|
||||
:networks [{:source (:source network)}]}
|
||||
:approval-label-props {:status (case approval-transaction-status
|
||||
:pending :approving
|
||||
:confirmed :approved
|
||||
:finalised :approved
|
||||
:approve)
|
||||
:token-value approval-amount-required-num
|
||||
:button-props {:on-press on-approve-press}
|
||||
:customization-color account-color
|
||||
:token-symbol pay-token-symbol}}]))
|
||||
|
||||
(defn- swap-order-button
|
||||
[{:keys [on-press]}]
|
||||
(let [approval-required? (rf/sub [:wallet/swap-proposal-approval-required])]
|
||||
[quo/swap-order-button
|
||||
{:container-style (style/swap-order-button approval-required?)
|
||||
:on-press on-press}]))
|
||||
|
||||
(defn- receive-token-input
|
||||
[{:keys [on-input-focus on-token-press input-focused?]}]
|
||||
(let [account-color (rf/sub [:wallet/current-viewing-account-color])
|
||||
asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
currency (rf/sub [:profile/currency])
|
||||
currency-symbol (rf/sub [:profile/currency-symbol])
|
||||
amount-out (rf/sub [:wallet/swap-proposal-amount-out])
|
||||
approval-required? (rf/sub [:wallet/swap-proposal-approval-required])
|
||||
receive-token-symbol (:symbol asset-to-receive)
|
||||
receive-token-decimals (:decimals asset-to-receive)
|
||||
amount-out-whole-number (when amount-out
|
||||
(number/convert-to-whole-number
|
||||
(native-module/hex-to-number
|
||||
(utils.hex/normalize-hex
|
||||
amount-out))
|
||||
receive-token-decimals))
|
||||
amount-out-num (if amount-out-whole-number
|
||||
(number/remove-trailing-zeroes
|
||||
(.toFixed amount-out-whole-number receive-token-decimals))
|
||||
default-text-for-unfocused-input)
|
||||
receive-token-fiat-value (str (utils/calculate-token-fiat-value
|
||||
{:currency currency
|
||||
:balance (or amount-out-whole-number 0)
|
||||
:token asset-to-receive}))]
|
||||
[quo/swap-input
|
||||
{:type :receive
|
||||
:error? false
|
||||
:token receive-token-symbol
|
||||
:customization-color account-color
|
||||
:show-approval-label? false
|
||||
:enable-swap? true
|
||||
:input-disabled? true
|
||||
:show-keyboard? false
|
||||
:status (cond
|
||||
(and loading-swap-proposal? (not input-focused?)) :loading
|
||||
input-focused? :typing
|
||||
:else :disabled)
|
||||
:currency-symbol currency-symbol
|
||||
:on-token-press on-token-press
|
||||
:on-input-focus on-input-focus
|
||||
:value amount-out-num
|
||||
:fiat-value receive-token-fiat-value
|
||||
:container-style (style/receive-token-swap-input-container approval-required?)}]))
|
||||
|
||||
(defn- action-button
|
||||
[{:keys [on-press]}]
|
||||
(let [account-color (rf/sub [:wallet/current-viewing-account-color])
|
||||
swap-proposal (rf/sub [:wallet/swap-proposal])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
approval-required? (rf/sub [:wallet/swap-proposal-approval-required])
|
||||
approval-transaction-status (rf/sub [:wallet/swap-approval-transaction-status])]
|
||||
[quo/bottom-actions
|
||||
{:actions :one-action
|
||||
:button-one-label (i18n/label :t/review-swap)
|
||||
:button-one-props {:disabled? (or (not swap-proposal)
|
||||
(and approval-required?
|
||||
(not= approval-transaction-status :confirmed))
|
||||
loading-swap-proposal?)
|
||||
:customization-color account-color
|
||||
:on-press on-press}}]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [[pay-value set-pay-value] (rn/use-state "")
|
||||
{:keys [color]} (rf/sub [:wallet/current-viewing-account])
|
||||
{:keys [max-slippage swap-proposal loading-fees?
|
||||
receive-amount network]} (rf/sub [:wallet/swap])
|
||||
currency (rf/sub [:profile/currency])
|
||||
currency-symbol (rf/sub [:profile/currency-symbol])
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])
|
||||
|
||||
pay-token-fiat-value (utils/calculate-token-fiat-value
|
||||
{:currency currency
|
||||
:balance (or pay-value 0)
|
||||
:token asset-to-pay})
|
||||
receive-token-fiat-value (utils/calculate-token-fiat-value
|
||||
{:currency currency
|
||||
:balance (or receive-amount 0)
|
||||
:token asset-to-receive})
|
||||
native-currency-symbol (get-in swap-proposal
|
||||
[:from :native-currency-symbol])
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
receive-token-symbol (:symbol asset-to-receive)
|
||||
on-press (fn [v] (set-pay-value (str pay-value v)))
|
||||
delete (fn []
|
||||
(set-pay-value #(subs % 0 (dec (count %)))))]
|
||||
(let [[pay-input-state set-pay-input-state] (rn/use-state controlled-input/init-state)
|
||||
[pay-input-focused? set-pay-input-focused?] (rn/use-state true)
|
||||
[refetch-interval set-refetch-interval] (rn/use-state nil)
|
||||
error-response (rf/sub [:wallet/swap-error-response])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
swap-proposal (rf/sub [:wallet/swap-proposal])
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
pay-input-amount (controlled-input/input-value pay-input-state)
|
||||
pay-token-decimals (:decimals asset-to-pay)
|
||||
pay-input-num-value (controlled-input/numeric-value pay-input-state)
|
||||
pay-token-balance-selected-chain (get-in asset-to-pay
|
||||
[:balances-per-chain
|
||||
(:chain-id network) :balance]
|
||||
0)
|
||||
pay-input-error? (and (not (string/blank? pay-input-amount))
|
||||
(money/greater-than
|
||||
(money/bignumber pay-input-num-value)
|
||||
(money/bignumber
|
||||
pay-token-balance-selected-chain)))
|
||||
valid-pay-input? (and
|
||||
(not (string/blank?
|
||||
pay-input-amount))
|
||||
(> pay-input-amount 0)
|
||||
(not pay-input-error?))
|
||||
on-review-swap-press (rn/use-callback
|
||||
(fn []
|
||||
(rf/dispatch [:navigate-to-within-stack
|
||||
[:screen/wallet.swap-confirmation
|
||||
:screen/wallet.setup-swap]])))
|
||||
on-press (rn/use-callback
|
||||
(fn [c]
|
||||
(let
|
||||
[new-text (str pay-input-amount c)
|
||||
valid-amount?
|
||||
(utils.string/valid-amount-for-token-decimals?
|
||||
pay-token-decimals
|
||||
new-text)]
|
||||
(when valid-amount?
|
||||
(set-pay-input-state
|
||||
#(controlled-input/add-character % c))))))
|
||||
on-long-press (rn/use-callback
|
||||
(fn []
|
||||
(set-pay-input-state controlled-input/delete-all)
|
||||
(rf/dispatch [:wallet/clean-suggested-routes])))
|
||||
delete (rn/use-callback
|
||||
(fn []
|
||||
(set-pay-input-state
|
||||
controlled-input/delete-last)
|
||||
(rf/dispatch [:wallet/clean-swap-proposal])))
|
||||
on-max-press (rn/use-callback
|
||||
(fn [max-value]
|
||||
(set-pay-input-state
|
||||
(fn [input-state]
|
||||
(controlled-input/set-input-value
|
||||
input-state
|
||||
max-value)))))
|
||||
on-refresh-swap-proposal (rn/use-callback
|
||||
(fn []
|
||||
(let [bottom-sheets (rf/sub
|
||||
[:bottom-sheet-sheets])]
|
||||
(when-not valid-pay-input?
|
||||
(js/clearInterval refetch-interval)
|
||||
(set-refetch-interval nil))
|
||||
(when (and valid-pay-input?
|
||||
(not loading-swap-proposal?)
|
||||
(not bottom-sheets))
|
||||
(fetch-swap-proposal
|
||||
{:amount pay-input-amount
|
||||
:valid-input? valid-pay-input?}))))
|
||||
[valid-pay-input? loading-swap-proposal?
|
||||
pay-input-amount])]
|
||||
(rn/use-effect (fn []
|
||||
(when refetch-interval
|
||||
(js/clearInterval refetch-interval)
|
||||
(set-refetch-interval nil))
|
||||
(when (or swap-proposal error-response)
|
||||
(set-refetch-interval (js/setInterval
|
||||
on-refresh-swap-proposal
|
||||
constants/swap-proposal-refresh-interval-ms))))
|
||||
[swap-proposal error-response])
|
||||
(rn/use-unmount (fn []
|
||||
(when refetch-interval
|
||||
(js/clearInterval refetch-interval)
|
||||
(set-refetch-interval nil))))
|
||||
[rn/view {:style style/container}
|
||||
[account-switcher/view
|
||||
{:on-press events-helper/navigate-back
|
||||
{:on-press on-close
|
||||
:icon-name :i/arrow-left
|
||||
:margin-top (safe-area/get-top)
|
||||
:switcher-type :select-account}]
|
||||
[rn/view {:style style/inputs-container}
|
||||
[quo/swap-input
|
||||
{:type :pay
|
||||
:error? false
|
||||
:token pay-token-symbol
|
||||
:customization-color :blue
|
||||
:show-approval-label? false
|
||||
:status :default
|
||||
:currency-symbol currency-symbol
|
||||
:on-swap-press #(js/alert "Swap Pressed")
|
||||
:on-token-press #(js/alert "Token Pressed")
|
||||
:on-max-press #(js/alert "Max Pressed")
|
||||
:value pay-value
|
||||
:fiat-value pay-token-fiat-value
|
||||
:network-tag-props {:title (i18n/label :t/max-token
|
||||
{:number 200
|
||||
:token-symbol pay-token-symbol})
|
||||
:networks [{:source (:source network)}]}
|
||||
:approval-label-props {:status :approve
|
||||
:token-value pay-value
|
||||
:button-props {:on-press
|
||||
#(js/alert "Approve Pressed")}
|
||||
:customization-color color
|
||||
:token-symbol pay-token-symbol}}]
|
||||
[quo/swap-order-button
|
||||
{:container-style style/swap-order-button
|
||||
:on-press #(js/alert "Pressed")}]
|
||||
[quo/swap-input
|
||||
{:type :receive
|
||||
:error? false
|
||||
:token receive-token-symbol
|
||||
:customization-color color
|
||||
:show-approval-label? false
|
||||
:enable-swap? true
|
||||
:status :default
|
||||
:currency-symbol currency-symbol
|
||||
:on-swap-press #(js/alert "Swap Pressed")
|
||||
:on-token-press #(js/alert "Token Pressed")
|
||||
:on-max-press #(js/alert "Max Pressed")
|
||||
:value receive-amount
|
||||
:fiat-value receive-token-fiat-value
|
||||
:container-style style/receive-token-swap-input-container}]]
|
||||
[pay-token-input
|
||||
{:input-state pay-input-state
|
||||
:on-max-press on-max-press
|
||||
:input-focused? pay-input-focused?
|
||||
:on-token-press #(js/alert "Token Pressed")
|
||||
:on-approve-press #(rf/dispatch [:open-modal :screen/wallet.swap-set-spending-cap])
|
||||
:on-input-focus (fn []
|
||||
(when platform/android? (rf/dispatch [:dismiss-keyboard]))
|
||||
(set-pay-input-focused? true))}]
|
||||
[swap-order-button {:on-press #(js/alert "Swap Order Pressed")}]
|
||||
[receive-token-input
|
||||
{:input-focused? (not pay-input-focused?)
|
||||
:on-token-press #(js/alert "Token Pressed")
|
||||
:on-input-focus #(set-pay-input-focused? false)}]]
|
||||
[rn/view {:style style/footer-container}
|
||||
(when swap-proposal
|
||||
[transaction-details
|
||||
{:native-currency-symbol native-currency-symbol
|
||||
:max-slippage max-slippage
|
||||
:loading-fees? loading-fees?}])
|
||||
[quo/bottom-actions
|
||||
{:actions :one-action
|
||||
:button-one-label (i18n/label :t/review-swap)
|
||||
:button-one-props {:disabled? (or (not swap-proposal)
|
||||
loading-fees?)
|
||||
:customization-color color
|
||||
:on-press #(js/alert "Review swap pressed")}}]]
|
||||
(when error-response
|
||||
[quo/alert-banner
|
||||
{:container-style style/alert-banner
|
||||
:text (i18n/label :t/something-went-wrong-please-try-again-later)}])
|
||||
(when (or loading-swap-proposal? swap-proposal)
|
||||
[transaction-details])
|
||||
[action-button {:on-press on-review-swap-press}]]
|
||||
[quo/numbered-keyboard
|
||||
{:container-style style/keyboard-container
|
||||
:left-action :dot
|
||||
:delete-key? true
|
||||
:on-press on-press
|
||||
:on-delete delete}]]))
|
||||
:on-delete delete
|
||||
:on-long-press on-long-press}]]))
|
||||
|
||||
@@ -37,3 +37,7 @@
|
||||
(def providers-container
|
||||
{:align-items :center
|
||||
:margin-top 12})
|
||||
|
||||
(defn swaps-powered-by
|
||||
[theme]
|
||||
{:color (colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-70 theme)})
|
||||
|
||||
@@ -1,58 +1,66 @@
|
||||
(ns status-im.contexts.wallet.swap.swap-confirmation.view
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.floating-button-page.view :as floating-button-page]
|
||||
[status-im.common.standard-authentication.core :as standard-auth]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.swap.swap-confirmation.style :as style]
|
||||
[utils.address :as address-utils]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(defn- on-close-action
|
||||
[]
|
||||
(rf/dispatch [:navigate-back]))
|
||||
|
||||
(defn- swap-title
|
||||
[{:keys [pay-token-symbol pay-amount receive-token-symbol receive-amount account]}]
|
||||
[rn/view {:style style/content-container}
|
||||
[rn/view {:style {:flex-direction :row}}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :title-label}
|
||||
(i18n/label :t/swap)]
|
||||
[quo/summary-tag
|
||||
{:token pay-token-symbol
|
||||
:label (str pay-amount " " pay-token-symbol)
|
||||
:type :token}]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :title-label}
|
||||
(i18n/label :t/to)]
|
||||
[quo/summary-tag
|
||||
{:token receive-token-symbol
|
||||
:label (str receive-amount " " receive-token-symbol)
|
||||
:type :token}]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :send-label}
|
||||
(i18n/label :t/in)]
|
||||
[quo/summary-tag
|
||||
{:label (:name account)
|
||||
:type :account
|
||||
:emoji (:emoji account)
|
||||
:customization-color (:color account)}]]])
|
||||
[]
|
||||
(let [asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])
|
||||
account (rf/sub [:wallet/current-viewing-account])
|
||||
receive-amount (rf/sub [:wallet/swap-receive-amount])
|
||||
pay-amount (rf/sub [:wallet/swap-pay-amount])
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
receive-token-symbol (:symbol asset-to-receive)]
|
||||
[rn/view {:style style/content-container}
|
||||
[rn/view {:style {:flex-direction :row}}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :title-label}
|
||||
(i18n/label :t/swap)]
|
||||
[quo/summary-tag
|
||||
{:token pay-token-symbol
|
||||
:label (str pay-amount " " pay-token-symbol)
|
||||
:type :token}]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :title-label}
|
||||
(i18n/label :t/to)]
|
||||
[quo/summary-tag
|
||||
{:token receive-token-symbol
|
||||
:label (str receive-amount " " receive-token-symbol)
|
||||
:type :token}]]
|
||||
[rn/view {:style style/title-line-with-margin-top}
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/title-container
|
||||
:accessibility-label :send-label}
|
||||
(i18n/label :t/in)]
|
||||
[quo/summary-tag
|
||||
{:label (:name account)
|
||||
:type :account
|
||||
:emoji (:emoji account)
|
||||
:customization-color (:color account)}]]]))
|
||||
|
||||
(defn- summary-section
|
||||
[{:keys [theme label title-accessibility-label amount token-symbol token-address network]}]
|
||||
@@ -74,6 +82,44 @@
|
||||
:address (address-utils/get-shortened-compressed-key token-address)
|
||||
:size 32}}]]))
|
||||
|
||||
(defn- pay-section
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
asset-to-pay (rf/sub [:wallet/swap-asset-to-pay])
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
pay-amount (rf/sub [:wallet/swap-pay-amount])
|
||||
network-chain-id (:chain-id network)
|
||||
network-name (:network-name network)
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
pay-token-address (get-in asset-to-pay [:balances-per-chain network-chain-id :address])]
|
||||
[summary-section
|
||||
{:title-accessibility-label :summary-section-pay
|
||||
:label (i18n/label :t/pay)
|
||||
:token-symbol pay-token-symbol
|
||||
:amount pay-amount
|
||||
:token-address pay-token-address
|
||||
:network network-name
|
||||
:theme theme}]))
|
||||
|
||||
(defn- receive-section
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
asset-to-receive (rf/sub [:wallet/swap-asset-to-receive])
|
||||
network (rf/sub [:wallet/swap-network])
|
||||
receive-amount (rf/sub [:wallet/swap-receive-amount])
|
||||
network-chain-id (:chain-id network)
|
||||
network-name (:network-name network)
|
||||
receive-token-symbol (:symbol asset-to-receive)
|
||||
receive-token-address (get-in asset-to-receive [:balances-per-chain network-chain-id :address])]
|
||||
[summary-section
|
||||
{:title-accessibility-label :summary-section-receive
|
||||
:label (i18n/label :t/receive)
|
||||
:token-symbol receive-token-symbol
|
||||
:amount receive-amount
|
||||
:token-address receive-token-address
|
||||
:network network-name
|
||||
:theme theme}]))
|
||||
|
||||
(defn- data-item
|
||||
[{:keys [title subtitle loading?]}]
|
||||
[quo/data-item
|
||||
@@ -86,64 +132,64 @@
|
||||
:subtitle subtitle}])
|
||||
|
||||
(defn- transaction-details
|
||||
[{:keys [estimated-time-min max-fees max-slippage loading-fees?]}]
|
||||
[rn/view {:style style/details-container}
|
||||
[:<>
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time-min)})}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-fees)
|
||||
:subtitle max-fees
|
||||
:loading? loading-fees?}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-slippage)
|
||||
:subtitle (str max-slippage "%")}]]])
|
||||
[]
|
||||
(let [max-fees (rf/sub [:wallet/wallet-swap-proposal-fee-fiat-formatted
|
||||
constants/token-for-fees-symbol])
|
||||
estimated-time (rf/sub [:wallet/swap-proposal-estimated-time])
|
||||
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
max-slippage (rf/sub [:wallet/swap-max-slippage])]
|
||||
[rn/view {:style style/details-container}
|
||||
[:<>
|
||||
[data-item
|
||||
{:title (i18n/label :t/est-time)
|
||||
:subtitle (if estimated-time
|
||||
(i18n/label :t/time-in-mins {:minutes (str estimated-time)})
|
||||
(i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-fees)
|
||||
:subtitle (if (and estimated-time max-fees) max-fees (i18n/label :t/unknown))
|
||||
:loading? loading-swap-proposal?}]
|
||||
[data-item
|
||||
{:title (i18n/label :t/max-slippage)
|
||||
:subtitle (str max-slippage "%")}]]]))
|
||||
|
||||
(defn- slide-button
|
||||
[]
|
||||
(let [loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
|
||||
swap-proposal (rf/sub [:wallet/swap-proposal])
|
||||
account (rf/sub [:wallet/current-viewing-account])
|
||||
account-color (:color account)
|
||||
on-auth-success (rn/use-callback #(rf/dispatch
|
||||
[:wallet/swap-transaction
|
||||
(security/safe-unmask-data %)]))]
|
||||
[standard-auth/slide-button
|
||||
{:size :size-48
|
||||
:track-text (i18n/label :t/slide-to-swap)
|
||||
:container-style {:z-index 2}
|
||||
:customization-color account-color
|
||||
:disabled? (or loading-swap-proposal? (not swap-proposal))
|
||||
:auth-button-label (i18n/label :t/confirm)
|
||||
:on-auth-success on-auth-success}]))
|
||||
|
||||
(defn footer
|
||||
[{:keys [estimated-time-min native-currency-symbol max-slippage theme account-color provider
|
||||
loading-fees?]}]
|
||||
(let [fee-formatted (rf/sub [:wallet/wallet-send-fee-fiat-formatted native-currency-symbol])]
|
||||
[]
|
||||
(let [provider (rf/sub [:wallet/swap-proposal-provider])
|
||||
theme (quo.theme/use-theme)]
|
||||
[:<>
|
||||
[transaction-details
|
||||
{:estimated-time-min estimated-time-min
|
||||
:max-fees fee-formatted
|
||||
:max-slippage max-slippage
|
||||
:loading-fees? loading-fees?
|
||||
:theme theme}]
|
||||
[standard-auth/slide-button
|
||||
{:size :size-48
|
||||
:track-text (i18n/label :t/slide-to-swap)
|
||||
:container-style {:z-index 2}
|
||||
:customization-color account-color
|
||||
:disabled? loading-fees?
|
||||
:auth-button-label (i18n/label :t/confirm)}]
|
||||
[transaction-details]
|
||||
[slide-button]
|
||||
[rn/view {:style style/providers-container}
|
||||
[quo/text
|
||||
{:size :paragraph-2
|
||||
:style {:color (colors/theme-colors colors/neutral-80-opa-40
|
||||
colors/white-opa-70
|
||||
theme)}}
|
||||
(i18n/label :t/swaps-powered-by {:provider (:name provider)})]]]))
|
||||
:style (style/swaps-powered-by theme)}
|
||||
(i18n/label :t/swaps-powered-by
|
||||
{:provider (if provider (:full-name provider) (i18n/label :t/unknown))})]]]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
swap-transaction-data (rf/sub [:wallet/swap])
|
||||
{:keys [asset-to-pay max-slippage network
|
||||
pay-amount providers swap-proposal
|
||||
loading-fees?]} swap-transaction-data
|
||||
receive-amount (:receive-amount swap-proposal)
|
||||
receive-token (:receive-token swap-proposal)
|
||||
receive-token-symbol (:symbol receive-token)
|
||||
receive-token-address (:address receive-token)
|
||||
estimated-time-min (:estimated-time swap-proposal)
|
||||
pay-token-symbol (:symbol asset-to-pay)
|
||||
pay-token-address (:address asset-to-pay)
|
||||
native-currency-symbol (get-in swap-proposal [:from :native-currency-symbol])
|
||||
account (rf/sub [:wallet/current-viewing-account])
|
||||
account-color (:color account)
|
||||
provider (first providers)]
|
||||
(let [account (rf/sub [:wallet/current-viewing-account])
|
||||
account-color (:color account)]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[floating-button-page/view
|
||||
{:footer-container-padding 0
|
||||
@@ -153,36 +199,10 @@
|
||||
:margin-top (safe-area/get-top)
|
||||
:background :blur
|
||||
:accessibility-label :top-bar}]
|
||||
:footer [footer
|
||||
{:estimated-time-min estimated-time-min
|
||||
:native-currency-symbol native-currency-symbol
|
||||
:max-slippage max-slippage
|
||||
:account-color account-color
|
||||
:provider provider
|
||||
:loading-fees? loading-fees?
|
||||
:theme theme}]
|
||||
:footer [footer]
|
||||
:gradient-cover? true
|
||||
:customization-color account-color}
|
||||
[rn/view
|
||||
[swap-title
|
||||
{:pay-token-symbol pay-token-symbol
|
||||
:pay-amount pay-amount
|
||||
:receive-token-symbol receive-token-symbol
|
||||
:receive-amount receive-amount
|
||||
:account account}]
|
||||
[summary-section
|
||||
{:title-accessibility-label :summary-section-pay
|
||||
:label (i18n/label :t/pay)
|
||||
:token-symbol pay-token-symbol
|
||||
:amount pay-amount
|
||||
:token-address pay-token-address
|
||||
:network (:network-name network)
|
||||
:theme theme}]
|
||||
[summary-section
|
||||
{:title-accessibility-label :summary-section-receive
|
||||
:label (i18n/label :t/receive)
|
||||
:token-symbol receive-token-symbol
|
||||
:amount receive-amount
|
||||
:token-address receive-token-address
|
||||
:network (:network-name network)
|
||||
:theme theme}]]]]))
|
||||
[swap-title]
|
||||
[pay-section]
|
||||
[receive-section]]]]))
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.core
|
||||
(:require [clojure.edn :as edn]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.utils.networks :as networks]
|
||||
[utils.security.core :as security]
|
||||
[utils.string]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(def method-to-screen
|
||||
{constants/wallet-connect-personal-sign-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-typed-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-typed-v4-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-send-transaction-method :screen/wallet-connect.send-transaction
|
||||
constants/wallet-connect-eth-sign-transaction-method :screen/wallet-connect.sign-transaction})
|
||||
|
||||
(defn extract-native-call-signature
|
||||
[data]
|
||||
(-> data transforms/json->clj :result))
|
||||
|
||||
(defn chain-id->eip155
|
||||
[chain-id]
|
||||
(str "eip155:" chain-id))
|
||||
|
||||
(defn eip155->chain-id
|
||||
[chain-id-str]
|
||||
(-> chain-id-str
|
||||
(string/split #":")
|
||||
last
|
||||
edn/read-string))
|
||||
|
||||
(defn format-eip155-address
|
||||
[address chain-id]
|
||||
(str chain-id ":" address))
|
||||
|
||||
(defn get-request-method
|
||||
[event]
|
||||
(get-in event [:params :request :method]))
|
||||
|
||||
(defn get-request-params
|
||||
[event]
|
||||
(get-in event [:params :request :params]))
|
||||
|
||||
(defn get-db-current-request-event
|
||||
[db]
|
||||
(get-in db [:wallet-connect/current-request :event]))
|
||||
|
||||
(defn get-session-dapp-metadata
|
||||
[proposal]
|
||||
(get-in proposal [:params :proposer :metadata]))
|
||||
|
||||
(defn get-current-request-dapp
|
||||
[request sessions]
|
||||
(let [dapp-url (get-in request [:event :verifyContext :verified :origin])]
|
||||
(->> sessions
|
||||
(filter (fn [session]
|
||||
(= (utils.string/remove-trailing-slash dapp-url)
|
||||
(utils.string/remove-trailing-slash (get session :url)))))
|
||||
first)))
|
||||
|
||||
(defn get-dapp-redirect-url
|
||||
[session]
|
||||
(get-in session [:peer :metadata :redirect :native]))
|
||||
|
||||
(defn get-db-current-request-params
|
||||
[db]
|
||||
(-> db
|
||||
get-db-current-request-event
|
||||
get-request-params))
|
||||
|
||||
(def ^:private sign-typed-data-by-version
|
||||
{:v1 native-module/sign-typed-data
|
||||
:v4 native-module/sign-typed-data-v4})
|
||||
|
||||
(defn sign-typed-data
|
||||
[version data address password]
|
||||
(let [f (get sign-typed-data-by-version version)]
|
||||
(->> password
|
||||
security/safe-unmask-data
|
||||
(f data address))))
|
||||
|
||||
(defn get-proposal-networks
|
||||
[proposal]
|
||||
(let [required-namespaces (get-in proposal [:params :requiredNamespaces])
|
||||
optional-namespaces (get-in proposal [:params :optionalNamespaces])]
|
||||
(->> [required-namespaces optional-namespaces]
|
||||
(map #(get-in % [:eip155 :chains]))
|
||||
(apply concat)
|
||||
(into #{}))))
|
||||
|
||||
(defn proposal-networks-intersection
|
||||
[proposal supported-networks]
|
||||
(let [proposed-networks (get-proposal-networks proposal)]
|
||||
(->> supported-networks
|
||||
(filter #(->> %
|
||||
chain-id->eip155
|
||||
(contains? proposed-networks))))))
|
||||
|
||||
(defn required-networks-supported?
|
||||
[proposal supported-networks]
|
||||
(let [supported-namespaces #{:eip155}
|
||||
required-namespaces (get-in proposal [:params :requiredNamespaces])]
|
||||
(when (every? #(contains? supported-namespaces %)
|
||||
(keys required-namespaces))
|
||||
(let [required-networks (get-in required-namespaces [:eip155 :chains])
|
||||
supported-eip155 (set (map chain-id->eip155 supported-networks))]
|
||||
(every? #(contains? supported-eip155 %)
|
||||
required-networks)))))
|
||||
|
||||
(defn get-networks-by-mode
|
||||
[db]
|
||||
(let [test-mode? (get-in db [:profile/profile :test-networks-enabled?])
|
||||
networks (get-in db [:wallet :networks (if test-mode? :test :prod)])]
|
||||
(mapv #(-> % :chain-id) networks)))
|
||||
|
||||
(defn- add-full-testnet-name
|
||||
"Updates the `:full-name` key with the full testnet name if using testnet `:chain-id`.\n
|
||||
e.g. `{:full-name \"Mainnet\"}` -> `{:full-name \"Mainnet Sepolia\"`}`"
|
||||
[network]
|
||||
(let [add-testnet-name (fn [testnet-name]
|
||||
(update network :full-name #(str % " " testnet-name)))]
|
||||
(condp #(contains? %1 %2) (:chain-id network)
|
||||
constants/sepolia-chain-ids (add-testnet-name constants/sepolia-full-name)
|
||||
constants/goerli-chain-ids (add-testnet-name constants/goerli-full-name)
|
||||
network)))
|
||||
|
||||
(defn chain-id->network-details
|
||||
[chain-id]
|
||||
(-> chain-id
|
||||
(networks/get-network-details)
|
||||
(add-full-testnet-name)))
|
||||
|
||||
(defn session-networks-allowed?
|
||||
[testnet-mode? {:keys [chains]}]
|
||||
(let [chain-ids (set (map (fn [chain]
|
||||
(-> chain
|
||||
(string/split ":")
|
||||
second
|
||||
js/parseInt))
|
||||
chains))]
|
||||
(if testnet-mode?
|
||||
(set/subset? chain-ids (set/union constants/sepolia-chain-ids constants/goerli-chain-ids))
|
||||
(set/subset? chain-ids constants/mainnet-chain-ids))))
|
||||
|
||||
(defn event-should-be-handled?
|
||||
[db {:keys [topic]}]
|
||||
(let [testnet-mode? (get-in db [:profile/profile :test-networks-enabled?])]
|
||||
(some #(and (= (:topic %) topic)
|
||||
(session-networks-allowed? testnet-mode? %))
|
||||
(:wallet-connect/sessions db))))
|
||||
|
||||
(defn sdk-session->db-session
|
||||
[{:keys [topic expiry pairingTopic] :as session}]
|
||||
{:topic topic
|
||||
:expiry expiry
|
||||
:sessionJson (transforms/clj->json session)
|
||||
:pairingTopic pairingTopic
|
||||
:name (get-in session [:peer :metadata :name])
|
||||
:iconUrl (get-in session [:peer :metadata :icons 0])
|
||||
:url (get-in session [:peer :metadata :url])
|
||||
:accounts (get-in session [:namespaces :eip155 :accounts])
|
||||
:chains (get-in session [:namespaces :eip155 :chains])
|
||||
:disconnected false})
|
||||
|
||||
(defn filter-operable-accounts
|
||||
[accounts]
|
||||
(filter #(and (:operable? %)
|
||||
(not (:watch-only? %)))
|
||||
accounts))
|
||||
|
||||
(defn filter-sessions-for-account-addresses
|
||||
[account-addresses sessions]
|
||||
(filter (fn [{:keys [accounts]}]
|
||||
(some (fn [account]
|
||||
(some (fn [account-address]
|
||||
(clojure.string/includes? account account-address))
|
||||
account-addresses))
|
||||
accounts))
|
||||
sessions))
|
||||
|
||||
(defn compute-dapp-name
|
||||
"Sometimes dapps have no name or an empty name. Return url as name in that case"
|
||||
[name url]
|
||||
(if (seq name)
|
||||
name
|
||||
(when (seq url)
|
||||
(-> url
|
||||
utils.string/remove-trailing-slash
|
||||
utils.string/remove-http-prefix
|
||||
string/capitalize))))
|
||||
|
||||
(defn compute-dapp-icon-path
|
||||
"Some dapps have icons with relative paths, make paths absolute in those cases, send nil if icon is missing"
|
||||
[icon-path url]
|
||||
(when (and (seq icon-path)
|
||||
(seq url))
|
||||
(if (string/starts-with? icon-path "http")
|
||||
icon-path
|
||||
(str (utils.string/remove-trailing-slash url) icon-path))))
|
||||
@@ -1,27 +0,0 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.core-test
|
||||
(:require
|
||||
[cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as sut]))
|
||||
|
||||
(deftest get-current-request-dapp-test
|
||||
(testing "returns the correct dapp based on the request's origin"
|
||||
(let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}}
|
||||
sessions [{:url "https://dapp.com"}
|
||||
{:url "https://anotherdapp.com"}]]
|
||||
(is (= {:url "https://dapp.com"}
|
||||
(sut/get-current-request-dapp request sessions)))))
|
||||
|
||||
(testing "returns nil if no matching dapp is found"
|
||||
(let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}}
|
||||
sessions [{:url "https://anotherdapp.com"}]]
|
||||
(is (nil? (sut/get-current-request-dapp request sessions))))))
|
||||
|
||||
(deftest get-dapp-redirect-url-test
|
||||
(testing "returns the native redirect URL if it exists"
|
||||
(let [session {:peer {:metadata {:redirect {:native "native://redirect-url"}}}}]
|
||||
(is (= "native://redirect-url"
|
||||
(sut/get-dapp-redirect-url session)))))
|
||||
|
||||
(testing "returns nil if no redirect URL is found"
|
||||
(let [session {:peer {:metadata {}}}]
|
||||
(is (nil? (sut/get-dapp-redirect-url session))))))
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.events
|
||||
(:require [re-frame.core :as rf]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core]
|
||||
status-im.contexts.wallet.wallet-connect.effects
|
||||
status-im.contexts.wallet.wallet-connect.processing-events
|
||||
status-im.contexts.wallet.wallet-connect.responding-events
|
||||
[status-im.contexts.wallet.wallet-connect.utils :as wc-utils]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.transforms :as types]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/init
|
||||
(fn [{:keys [db]}]
|
||||
(let [network-status (:network/status db)
|
||||
web3-wallet-missing? (-> db :wallet-connect/web3-wallet boolean not)]
|
||||
(if (and (= network-status :online) web3-wallet-missing?)
|
||||
(do (log/info "Initialising WalletConnect SDK")
|
||||
{: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]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-init-success
|
||||
(fn [{:keys [db]} [web3-wallet]]
|
||||
(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]]]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/reload-on-network-change
|
||||
(fn [{:keys [db]} [is-connected?]]
|
||||
(let [logged-in? (-> db :profile/profile boolean)]
|
||||
(when (and is-connected? logged-in?)
|
||||
(log/info "Re-Initialising WalletConnect SDK due to network change")
|
||||
{:fx [[:dispatch [:wallet-connect/init]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/register-event-listeners
|
||||
(fn [{:keys [db]}]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)]
|
||||
{:fx [[:effects.wallet-connect/register-event-listener
|
||||
[web3-wallet
|
||||
constants/wallet-connect-session-proposal-event
|
||||
#(rf/dispatch [:wallet-connect/on-session-proposal %])]]
|
||||
[:effects.wallet-connect/register-event-listener
|
||||
[web3-wallet
|
||||
constants/wallet-connect-session-request-event
|
||||
#(rf/dispatch [:wallet-connect/on-session-request %])]]
|
||||
[:effects.wallet-connect/register-event-listener
|
||||
[web3-wallet
|
||||
constants/wallet-connect-session-delete-event
|
||||
#(rf/dispatch [:wallet-connect/on-session-delete %])]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-init-fail
|
||||
(fn [_ [error]]
|
||||
(log/error "Failed to initialize Wallet Connect"
|
||||
{:error error
|
||||
:event :wallet-connect/on-init-fail})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-session-proposal
|
||||
(fn [{:keys [db]} [proposal]]
|
||||
(log/info "Received Wallet Connect session proposal: " proposal)
|
||||
(let [accounts (get-in db [:wallet :accounts])
|
||||
current-viewing-address (get-in db [:wallet :current-viewing-account-address])
|
||||
available-accounts (wallet-connect-core/filter-operable-accounts (vals accounts))
|
||||
networks (wallet-connect-core/get-networks-by-mode db)
|
||||
session-networks (wallet-connect-core/proposal-networks-intersection proposal
|
||||
networks)
|
||||
required-networks-supported? (wallet-connect-core/required-networks-supported? proposal
|
||||
networks)]
|
||||
(if (and (not-empty session-networks) required-networks-supported?)
|
||||
{:db (update db
|
||||
:wallet-connect/current-proposal assoc
|
||||
:request proposal
|
||||
:session-networks session-networks
|
||||
:address (or current-viewing-address
|
||||
(-> available-accounts
|
||||
first
|
||||
:address)))
|
||||
:fx [[:dispatch
|
||||
[:open-modal :screen/wallet.wallet-connect-session-proposal]]]}
|
||||
{:fx [[:dispatch
|
||||
[:wallet-connect/show-session-networks-unsupported-toast proposal]]
|
||||
[:dispatch
|
||||
[:wallet-connect/reject-session-proposal proposal]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/show-session-networks-unsupported-toast
|
||||
(fn [{:keys [db]} [proposal]]
|
||||
(let [{:keys [name url]} (wallet-connect-core/get-session-dapp-metadata proposal)]
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :negative
|
||||
:theme (:theme db)
|
||||
:text (i18n/label :t/wallet-connect-networks-not-supported
|
||||
{:dapp (wallet-connect-core/compute-dapp-name name url)})}]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-session-request
|
||||
(fn [{:keys [db]} [event]]
|
||||
(if (wallet-connect-core/event-should-be-handled? db event)
|
||||
{:fx [[:dispatch [:wallet-connect/process-session-request event]]]}
|
||||
{:fx [[:dispatch
|
||||
[:wallet-connect/send-response
|
||||
{:error (wallet-connect/get-sdk-error
|
||||
constants/wallet-connect-user-rejected-chains-error-key)}]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-session-delete
|
||||
(fn [{:keys [db]} [{:keys [topic] :as event}]]
|
||||
(when (wallet-connect-core/event-should-be-handled? db event)
|
||||
(log/info "Received Wallet Connect session delete from the SDK: " event)
|
||||
{:fx [[:dispatch [:wallet-connect/disconnect-session topic]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/reset-current-session-proposal
|
||||
(fn [{:keys [db]}]
|
||||
{:db (dissoc db :wallet-connect/current-proposal)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/set-current-proposal-address
|
||||
(fn [{:keys [db]} [address]]
|
||||
{:db (assoc-in db [:wallet-connect/current-proposal :address] address)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/reset-current-request
|
||||
(fn [{:keys [db]}]
|
||||
{:db (dissoc db :wallet-connect/current-request)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/disconnect-dapp
|
||||
(fn [{:keys [db]} [{:keys [topic on-success on-fail]}]]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)
|
||||
network-status (:network/status db)]
|
||||
(log/info "Disconnecting dApp session" topic)
|
||||
(if (= network-status :online)
|
||||
{: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-session topic])
|
||||
(when on-success
|
||||
(on-success)))}]]}
|
||||
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/pair
|
||||
(fn [{:keys [db]} [url]]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)]
|
||||
{:fx [[:effects.wallet-connect/pair
|
||||
{:web3-wallet web3-wallet
|
||||
:url url
|
||||
:on-fail #(log/error "Failed to pair with dApp" {:error %})
|
||||
:on-success #(log/info "dApp paired successfully")}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/approve-session
|
||||
(fn [{:keys [db]}]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)
|
||||
current-proposal (get-in db [:wallet-connect/current-proposal :request])
|
||||
session-networks (->> (get-in db [:wallet-connect/current-proposal :session-networks])
|
||||
(map wallet-connect-core/chain-id->eip155)
|
||||
vec)
|
||||
current-address (get-in db [:wallet-connect/current-proposal :address])
|
||||
accounts (-> (partial wallet-connect-core/format-eip155-address current-address)
|
||||
(map session-networks))
|
||||
network-status (:network/status db)
|
||||
expiry (get-in current-proposal [:params :expiryTimestamp])]
|
||||
(if (= network-status :online)
|
||||
{:db (assoc-in db [:wallet-connect/current-proposal :response-sent?] true)
|
||||
:fx [(if (wc-utils/timestamp-expired? expiry)
|
||||
[: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]))}])
|
||||
[:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]}
|
||||
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-scan-connection
|
||||
(fn [{:keys [db]} [scanned-text]]
|
||||
(let [network-status (:network/status db)
|
||||
parsed-uri (wallet-connect/parse-uri scanned-text)
|
||||
version (:version parsed-uri)
|
||||
valid-wc-uri? (wc-utils/valid-wc-uri? parsed-uri)
|
||||
expired? (-> parsed-uri
|
||||
:expiryTimestamp
|
||||
wc-utils/timestamp-expired?)
|
||||
version-supported? (wc-utils/version-supported? version)]
|
||||
(if (or (not valid-wc-uri?)
|
||||
(not version-supported?)
|
||||
(= network-status :offline)
|
||||
expired?)
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :negative
|
||||
:theme :dark
|
||||
:text (cond (= network-status :offline)
|
||||
(i18n/label :t/wallet-connect-no-internet-warning)
|
||||
|
||||
(not valid-wc-uri?)
|
||||
(i18n/label :t/wallet-connect-wrong-qr)
|
||||
|
||||
expired?
|
||||
(i18n/label :t/wallet-connect-qr-expired)
|
||||
|
||||
(not version-supported?)
|
||||
(i18n/label :t/wallet-connect-version-not-supported
|
||||
{:version version})
|
||||
|
||||
:else
|
||||
(i18n/label :t/something-went-wrong))}]]]}
|
||||
{:fx [[:dispatch [:wallet-connect/pair scanned-text]]]}))))
|
||||
|
||||
;; 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
|
||||
wallet-connect-core/filter-operable-accounts
|
||||
(map :address))
|
||||
sessions (->> (js->clj sessions :keywordize-keys true)
|
||||
vals
|
||||
(map wallet-connect-core/sdk-session->db-session)
|
||||
(wallet-connect-core/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-session topic]])
|
||||
expired-sessions)
|
||||
:db (assoc db :wallet-connect/sessions sessions)})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/fetch-active-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 %])}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/fetch-persisted-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')})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/fetch-persisted-sessions-fail
|
||||
(fn [_ [error]]
|
||||
(log/info "Wallet Connect fetch persisted sessions failed" error)
|
||||
{:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]]}))
|
||||
|
||||
(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]}]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/persist-session
|
||||
(fn [_ [session-info]]
|
||||
(let [redirect-url (-> session-info
|
||||
(js->clj :keywordize-keys true)
|
||||
(wallet-connect-core/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-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" %)}]]]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/no-internet-toast
|
||||
(fn [{:keys [db]}]
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :negative
|
||||
:theme (:theme db)
|
||||
:text (i18n/label :t/wallet-connect-no-internet-warning)}]]]}))
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.core
|
||||
(:require [re-frame.core :as rf]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[status-im.constants :as constants]
|
||||
status-im.contexts.wallet.wallet-connect.events.effects
|
||||
status-im.contexts.wallet.wallet-connect.events.session-proposals
|
||||
status-im.contexts.wallet.wallet-connect.events.session-requests
|
||||
status-im.contexts.wallet.wallet-connect.events.session-responses
|
||||
status-im.contexts.wallet.wallet-connect.events.sessions
|
||||
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/init
|
||||
(fn [{:keys [db]}]
|
||||
(let [network-status (:network/status db)
|
||||
web3-wallet-missing? (-> db :wallet-connect/web3-wallet boolean not)]
|
||||
(if (and (= network-status :online) web3-wallet-missing?)
|
||||
(do (log/info "Initialising WalletConnect SDK")
|
||||
{: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]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-init-success
|
||||
(fn [{:keys [db]} [web3-wallet]]
|
||||
(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]]]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/reload-on-network-change
|
||||
(fn [{:keys [db]} [is-connected?]]
|
||||
(let [logged-in? (-> db :profile/profile boolean)]
|
||||
(when (and is-connected? logged-in?)
|
||||
(log/info "Re-Initialising WalletConnect SDK due to network change")
|
||||
{:fx [[:dispatch [:wallet-connect/init]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/register-event-listeners
|
||||
(fn [{:keys [db]}]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)]
|
||||
{:fx [[:effects.wallet-connect/register-event-listener
|
||||
[web3-wallet
|
||||
constants/wallet-connect-session-proposal-event
|
||||
#(rf/dispatch [:wallet-connect/on-session-proposal %])]]
|
||||
[:effects.wallet-connect/register-event-listener
|
||||
[web3-wallet
|
||||
constants/wallet-connect-session-request-event
|
||||
#(rf/dispatch [:wallet-connect/on-session-request %])]]
|
||||
[:effects.wallet-connect/register-event-listener
|
||||
[web3-wallet
|
||||
constants/wallet-connect-session-delete-event
|
||||
#(rf/dispatch [:wallet-connect/on-session-delete %])]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-init-fail
|
||||
(fn [_ [error]]
|
||||
(log/error "Failed to initialize Wallet Connect"
|
||||
{:error error
|
||||
:event :wallet-connect/on-init-fail})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-session-request
|
||||
(fn [{:keys [db]} [event]]
|
||||
(if (networks/event-should-be-handled? db event)
|
||||
{:fx [[:dispatch [:wallet-connect/process-session-request event]]]}
|
||||
{:fx [[:dispatch
|
||||
[:wallet-connect/show-session-networks-unsupported-toast event]]
|
||||
[:dispatch
|
||||
[:wallet-connect/send-response
|
||||
{:request event
|
||||
:error (wallet-connect/get-sdk-error
|
||||
constants/wallet-connect-user-rejected-chains-error-key)}]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/reset-current-request
|
||||
(fn [{:keys [db]}]
|
||||
{:db (dissoc db :wallet-connect/current-request)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/no-internet-toast
|
||||
(fn [{:keys [db]}]
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :negative
|
||||
:theme (:theme db)
|
||||
:text (i18n/label :t/wallet-connect-no-internet-warning)}]]]}))
|
||||
@@ -1,12 +1,12 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.effects
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.effects
|
||||
(:require
|
||||
[promesa.core :as promesa]
|
||||
[re-frame.core :as rf]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[status-im.config :as config]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.wallet-connect.signing :as signing]
|
||||
[status-im.contexts.wallet.wallet-connect.transactions :as transactions]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.signing :as signing]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.session-proposals
|
||||
(:require [re-frame.core :as rf]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[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]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.uri :as uri]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/pair
|
||||
(fn [{:keys [db]} [url]]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)]
|
||||
{:fx [[:effects.wallet-connect/pair
|
||||
{:web3-wallet web3-wallet
|
||||
:url url
|
||||
:on-fail #(log/error "Failed to pair with dApp" {:error %})
|
||||
:on-success #(log/info "dApp paired successfully")}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-scan-connection
|
||||
(fn [{:keys [db]} [scanned-text]]
|
||||
(let [network-status (:network/status db)
|
||||
parsed-uri (wallet-connect/parse-uri scanned-text)
|
||||
version (:version parsed-uri)
|
||||
valid-wc-uri? (uri/valid-wc-uri? parsed-uri)
|
||||
expired? (-> parsed-uri
|
||||
:expiryTimestamp
|
||||
uri/timestamp-expired?)
|
||||
version-supported? (uri/version-supported? version)]
|
||||
(if (or (not valid-wc-uri?)
|
||||
(not version-supported?)
|
||||
(= network-status :offline)
|
||||
expired?)
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :negative
|
||||
:theme :dark
|
||||
:text (cond (= network-status :offline)
|
||||
(i18n/label :t/wallet-connect-no-internet-warning)
|
||||
|
||||
(not valid-wc-uri?)
|
||||
(i18n/label :t/wallet-connect-wrong-qr)
|
||||
|
||||
expired?
|
||||
(i18n/label :t/wallet-connect-qr-expired)
|
||||
|
||||
(not version-supported?)
|
||||
(i18n/label :t/wallet-connect-version-not-supported
|
||||
{:version version})
|
||||
|
||||
:else
|
||||
(i18n/label :t/something-went-wrong))}]]]}
|
||||
{:fx [[:dispatch [:wallet-connect/pair scanned-text]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/on-session-proposal
|
||||
(fn [{:keys [db]} [proposal]]
|
||||
(log/info "Received Wallet Connect session proposal: " proposal)
|
||||
(let [accounts (get-in db [:wallet :accounts])
|
||||
current-viewing-address (get-in db [:wallet :current-viewing-account-address])
|
||||
available-accounts (sessions/filter-operable-accounts (vals accounts))
|
||||
networks (networks/get-networks-by-mode db)
|
||||
session-networks (networks/proposal-networks-intersection proposal networks)
|
||||
required-networks-supported? (networks/required-networks-supported? proposal networks)]
|
||||
(if (and (not-empty session-networks) required-networks-supported?)
|
||||
{:db (update db
|
||||
:wallet-connect/current-proposal assoc
|
||||
:request proposal
|
||||
:session-networks session-networks
|
||||
:address (or current-viewing-address
|
||||
(-> available-accounts
|
||||
first
|
||||
:address)))
|
||||
:fx [[:dispatch [:open-modal :screen/wallet.wallet-connect-session-proposal]]]}
|
||||
{:fx [[:dispatch [:wallet-connect/show-session-networks-unsupported-toast proposal]]
|
||||
[:dispatch [:wallet-connect/reject-session-proposal proposal]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/show-session-networks-unsupported-toast
|
||||
(fn [{:keys [db]} [proposal]]
|
||||
(let [{:keys [name url]} (data-store/get-session-dapp-metadata proposal)]
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:type :negative
|
||||
:theme (:theme db)
|
||||
:text (i18n/label :t/wallet-connect-networks-not-supported
|
||||
{:dapp (data-store/compute-dapp-name name url)})}]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/reset-current-session-proposal
|
||||
(fn [{:keys [db]}]
|
||||
{:db (dissoc db :wallet-connect/current-proposal)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/set-current-proposal-address
|
||||
(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]}]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)
|
||||
current-proposal (get-in db [:wallet-connect/current-proposal :request])
|
||||
session-networks (->> (get-in db [:wallet-connect/current-proposal :session-networks])
|
||||
(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])]
|
||||
(if (= network-status :online)
|
||||
{:db (assoc-in db [:wallet-connect/current-proposal :response-sent?] true)
|
||||
:fx [(if (uri/timestamp-expired? expiry)
|
||||
[: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]))}])
|
||||
[:dispatch [:dismiss-modal :screen/wallet.wallet-connect-session-proposal]]]}
|
||||
{:fx [[:dispatch [:wallet-connect/no-internet-toast]]]}))))
|
||||
@@ -1,12 +1,14 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.processing-events
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.session-requests
|
||||
(: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.core :as wallet-connect-core]
|
||||
[status-im.contexts.wallet.wallet-connect.signing :as signing]
|
||||
[status-im.contexts.wallet.wallet-connect.transactions :as transactions]
|
||||
[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]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.transforms :as transforms]))
|
||||
@@ -15,8 +17,8 @@
|
||||
:wallet-connect/show-request-modal
|
||||
(fn [{:keys [db]}]
|
||||
(let [event (get-in db [:wallet-connect/current-request :event])
|
||||
method (wallet-connect-core/get-request-method event)
|
||||
screen (wallet-connect-core/method-to-screen method)]
|
||||
method (data-store/get-request-method event)
|
||||
screen (data-store/method-to-screen method)]
|
||||
(if screen
|
||||
{:fx [[:dispatch [:open-modal screen]]]}
|
||||
(log/error "Didn't find screen for Wallet Connect method"
|
||||
@@ -25,7 +27,7 @@
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-session-request
|
||||
(fn [{:keys [db]} [event]]
|
||||
(let [method (wallet-connect-core/get-request-method event)
|
||||
(let [method (data-store/get-request-method event)
|
||||
existing-event (get-in db [:wallet-connect/current-request :event])]
|
||||
(log/info "Processing Wallet Connect session request" method)
|
||||
;; NOTE: make sure we don't show two requests at the same time
|
||||
@@ -55,7 +57,7 @@
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-personal-sign
|
||||
(fn [{:keys [db]}]
|
||||
(let [[raw-data address] (wallet-connect-core/get-db-current-request-params db)
|
||||
(let [[raw-data address] (data-store/get-db-current-request-params db)
|
||||
parsed-data (native-module/hex-to-utf8 raw-data)]
|
||||
{:db (update-in db
|
||||
[:wallet-connect/current-request]
|
||||
@@ -68,7 +70,7 @@
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-eth-sign
|
||||
(fn [{:keys [db]}]
|
||||
(let [[address raw-data] (wallet-connect-core/get-db-current-request-params db)
|
||||
(let [[address raw-data] (data-store/get-db-current-request-params db)
|
||||
parsed-data (native-module/hex-to-utf8 raw-data)]
|
||||
{:db (update-in db
|
||||
[:wallet-connect/current-request]
|
||||
@@ -98,11 +100,11 @@
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-eth-send-transaction
|
||||
(fn [{:keys [db]}]
|
||||
(let [event (wallet-connect-core/get-db-current-request-event db)
|
||||
tx (-> event wallet-connect-core/get-request-params first)
|
||||
(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])
|
||||
wallet-connect-core/eip155->chain-id)]
|
||||
networks/eip155->chain-id)]
|
||||
{:fx [[:effects.wallet-connect/prepare-transaction
|
||||
{:tx tx
|
||||
:chain-id chain-id
|
||||
@@ -112,11 +114,11 @@
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/process-eth-sign-transaction
|
||||
(fn [{:keys [db]}]
|
||||
(let [event (wallet-connect-core/get-db-current-request-event db)
|
||||
tx (-> event wallet-connect-core/get-request-params first)
|
||||
(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])
|
||||
wallet-connect-core/eip155->chain-id)]
|
||||
networks/eip155->chain-id)]
|
||||
{:fx [[:effects.wallet-connect/prepare-transaction
|
||||
{:tx tx
|
||||
:chain-id chain-id
|
||||
@@ -127,11 +129,11 @@
|
||||
:wallet-connect/process-sign-typed
|
||||
(fn [{:keys [db]}]
|
||||
(try
|
||||
(let [[address raw-data] (wallet-connect-core/get-db-current-request-params db)
|
||||
(let [[address raw-data] (data-store/get-db-current-request-params db)
|
||||
parsed-raw-data (transforms/js-parse raw-data)
|
||||
session-chain-id (-> (wallet-connect-core/get-db-current-request-event db)
|
||||
session-chain-id (-> (data-store/get-db-current-request-event db)
|
||||
(get-in [:params :chainId])
|
||||
wallet-connect-core/eip155->chain-id)
|
||||
networks/eip155->chain-id)
|
||||
data-chain-id (-> parsed-raw-data
|
||||
transforms/js->clj
|
||||
signing/typed-data-chain-id)
|
||||
@@ -156,20 +158,20 @@
|
||||
[:wallet-connect/on-processing-error
|
||||
(ex-info "Failed to parse JSON typed data"
|
||||
{:error err
|
||||
:data (wallet-connect-core/get-db-current-request-params db)})]]]}))))
|
||||
:data (data-store/get-db-current-request-params db)})]]]}))))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/wrong-typed-data-chain-id
|
||||
(fn [_ [{:keys [expected-chain-id wrong-chain-id]}]]
|
||||
(let [wrong-network-name (-> wrong-chain-id
|
||||
wallet-connect-core/chain-id->network-details
|
||||
networks/chain-id->network-details
|
||||
:full-name)
|
||||
expected-network-name (-> expected-chain-id
|
||||
wallet-connect-core/chain-id->network-details
|
||||
networks/chain-id->network-details
|
||||
:full-name)
|
||||
toast-message (i18n/label :t/wallet-connect-typed-data-wrong-chain-id-warning
|
||||
{:wrong-chain (or wrong-network-name
|
||||
(wallet-connect-core/chain-id->eip155
|
||||
(networks/chain-id->eip155
|
||||
wrong-chain-id))
|
||||
:expected-chain expected-network-name})]
|
||||
{:fx [[:dispatch
|
||||
@@ -186,7 +188,7 @@
|
||||
:wallet-connect/on-processing-error
|
||||
(fn [{:keys [db]} [error]]
|
||||
(let [{:keys [address event]} (get db :wallet-connect/current-request)
|
||||
method (wallet-connect-core/get-request-method event)]
|
||||
method (data-store/get-request-method event)]
|
||||
(log/error "Failed to process Wallet Connect request"
|
||||
{:error error
|
||||
:address address
|
||||
@@ -1,9 +1,10 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.responding-events
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.session-responses
|
||||
(:require [re-frame.core :as rf]
|
||||
[react-native.wallet-connect :as wallet-connect]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core]
|
||||
[status-im.contexts.wallet.wallet-connect.utils :as wc-utils]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.uri :as uri]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.transforms :as transforms]))
|
||||
@@ -12,10 +13,10 @@
|
||||
:wallet-connect/respond-current-session
|
||||
(fn [{:keys [db]} [password]]
|
||||
(let [event (get-in db [:wallet-connect/current-request :event])
|
||||
method (wallet-connect-core/get-request-method event)
|
||||
screen (wallet-connect-core/method-to-screen method)
|
||||
method (data-store/get-request-method event)
|
||||
screen (data-store/method-to-screen method)
|
||||
expiry (get-in event [:params :request :expiryTimestamp])]
|
||||
(if (wc-utils/timestamp-expired? expiry)
|
||||
(if (uri/timestamp-expired? expiry)
|
||||
{:fx [[:dispatch
|
||||
[:toasts/upsert
|
||||
{:id :new-wallet-account-created
|
||||
@@ -99,7 +100,7 @@
|
||||
:wallet-connect/on-sign-error
|
||||
(fn [{:keys [db]} [error]]
|
||||
(let [{:keys [raw-data address event]} (get db :wallet-connect/current-request)
|
||||
method (wallet-connect-core/get-request-method event)]
|
||||
method (data-store/get-request-method event)]
|
||||
(log/error "Failed to sign Wallet Connect request"
|
||||
{:error error
|
||||
:address address
|
||||
@@ -115,9 +116,10 @@
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/send-response
|
||||
(fn [{:keys [db]} [{:keys [result error]}]]
|
||||
(when-let [{:keys [id topic] :as event} (get-in db [:wallet-connect/current-request :event])]
|
||||
(let [method (wallet-connect-core/get-request-method event)
|
||||
(fn [{:keys [db]} [{:keys [request result error]}]]
|
||||
(when-let [{:keys [id topic] :as event} (or request
|
||||
(get-in db [:wallet-connect/current-request :event]))]
|
||||
(let [method (data-store/get-request-method event)
|
||||
web3-wallet (get db :wallet-connect/web3-wallet)]
|
||||
{:db (assoc-in db [:wallet-connect/current-request :response-sent?] true)
|
||||
:fx [[:effects.wallet-connect/respond-session-request
|
||||
@@ -141,11 +143,11 @@
|
||||
(fn [{:keys [db]} [url]]
|
||||
(let [redirect-url (or url
|
||||
(->> (get db :wallet-connect/current-request)
|
||||
(wallet-connect-core/get-current-request-dapp
|
||||
(data-store/get-current-request-dapp
|
||||
(get db :wallet-connect/sessions))
|
||||
:sessionJson
|
||||
transforms/json->clj
|
||||
wallet-connect-core/get-dapp-redirect-url))]
|
||||
data-store/get-dapp-redirect-url))]
|
||||
{:fx [[:open-url redirect-url]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
@@ -153,17 +155,8 @@
|
||||
(fn [{:keys [db]} _]
|
||||
(let [screen (-> db
|
||||
(get-in [:wallet-connect/current-request :event])
|
||||
wallet-connect-core/get-request-method
|
||||
wallet-connect-core/method-to-screen)]
|
||||
{:fx [[:dispatch [:dismiss-modal screen]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/dismiss-request-modal
|
||||
(fn [{:keys [db]} _]
|
||||
(let [screen (-> db
|
||||
(get-in [:wallet-connect/current-request :event])
|
||||
wallet-connect-core/get-request-method
|
||||
wallet-connect-core/method-to-screen)]
|
||||
data-store/get-request-method
|
||||
data-store/method-to-screen)]
|
||||
{:fx [[:dispatch [:dismiss-modal screen]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
@@ -1,10 +1,10 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.responding-events-test
|
||||
(ns status-im.contexts.wallet.wallet-connect.events.session-responses-test
|
||||
(:require
|
||||
[cljs.test :refer-macros [is]]
|
||||
matcher-combinators.test
|
||||
[re-frame.db :as rf-db]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core]
|
||||
status-im.contexts.wallet.wallet-connect.responding-events
|
||||
status-im.contexts.wallet.wallet-connect.events.session-responses
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store]
|
||||
[test-helpers.unit :as h]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
:sessionJson session-json}]]
|
||||
(reset! rf-db/app-db {:wallet-connect {:current-request current-request
|
||||
:sessions sessions}})
|
||||
(with-redefs [wallet-connect-core/get-current-request-dapp
|
||||
(with-redefs [data-store/get-current-request-dapp
|
||||
(fn [_ _] (first sessions))
|
||||
transforms/json->clj
|
||||
(fn [json] (js/JSON.parse json))
|
||||
wallet-connect-core/get-dapp-redirect-url
|
||||
data-store/get-dapp-redirect-url
|
||||
(fn [_] "native://redirect-url")]
|
||||
|
||||
(is (match? {:fx [[:open-url "native://redirect-url"]]}
|
||||
@@ -0,0 +1,141 @@
|
||||
(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]))
|
||||
|
||||
(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]]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/disconnect-dapp
|
||||
(fn [{:keys [db]} [{:keys [topic on-success on-fail]}]]
|
||||
(let [web3-wallet (get db :wallet-connect/web3-wallet)
|
||||
network-status (:network/status db)]
|
||||
(log/info "Disconnecting dApp session" topic)
|
||||
(if (= network-status :online)
|
||||
{: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])
|
||||
(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
|
||||
(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 %])}]]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/fetch-persisted-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')})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet-connect/fetch-persisted-sessions-fail
|
||||
(fn [_ [error]]
|
||||
(log/info "Wallet Connect fetch persisted sessions failed" error)
|
||||
{:fx [[:dispatch [:wallet-connect/fetch-active-sessions]]]}))
|
||||
|
||||
(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]}]]]})))
|
||||
|
||||
(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
|
||||
(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" %)}]]]}))
|
||||
@@ -2,14 +2,15 @@
|
||||
(:require [clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as core]
|
||||
[status-im.contexts.wallet.wallet-connect.modals.common.header.style :as style]))
|
||||
[status-im.contexts.wallet.wallet-connect.modals.common.header.style :as style]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]))
|
||||
|
||||
(defn view
|
||||
[{:keys [label dapp account]}]
|
||||
[rn/view {:style style/header-container}
|
||||
(let [{:keys [name iconUrl url]} dapp
|
||||
image-source (core/compute-dapp-icon-path iconUrl url)]
|
||||
image-source (data-store/compute-dapp-icon-path iconUrl url)]
|
||||
[rn/view {:style style/dapp-container}
|
||||
[quo/summary-tag
|
||||
{:type :dapp
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.session-proposal.style
|
||||
(ns status-im.contexts.wallet.wallet-connect.modals.session-proposal.style
|
||||
(:require [quo.foundations.colors :as colors]))
|
||||
|
||||
(def dapp-avatar
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.session-proposal.view
|
||||
(ns status-im.contexts.wallet.wallet-connect.modals.session-proposal.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
@@ -6,8 +6,8 @@
|
||||
[quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[status-im.common.floating-button-page.view :as floating-button-page]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as wallet-connect-core]
|
||||
[status-im.contexts.wallet.wallet-connect.session-proposal.style :as style]
|
||||
[status-im.contexts.wallet.wallet-connect.modals.session-proposal.style :as style]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as data-store]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.string]))
|
||||
@@ -17,8 +17,8 @@
|
||||
(let [proposer (rf/sub [:wallet-connect/session-proposer])
|
||||
{:keys [icons name url]} (:metadata proposer)
|
||||
first-icon (first icons)
|
||||
dapp-name (wallet-connect-core/compute-dapp-name name url)
|
||||
profile-picture (wallet-connect-core/compute-dapp-icon-path first-icon url)]
|
||||
dapp-name (data-store/compute-dapp-name name url)
|
||||
profile-picture (data-store/compute-dapp-icon-path first-icon url)]
|
||||
[:<>
|
||||
[rn/view {:style style/dapp-avatar}
|
||||
[quo/user-avatar
|
||||
@@ -0,0 +1,75 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.data-store
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[status-im.constants :as constants]
|
||||
utils.string
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn compute-dapp-name
|
||||
"Sometimes dapps have no name or an empty name. Return url as name in that case"
|
||||
[name url]
|
||||
(if (seq name)
|
||||
name
|
||||
(when (seq url)
|
||||
(-> url
|
||||
utils.string/remove-trailing-slash
|
||||
utils.string/remove-http-prefix
|
||||
string/capitalize))))
|
||||
|
||||
(defn compute-dapp-icon-path
|
||||
"Some dapps have icons with relative paths, make paths absolute in those cases, send nil if icon is missing"
|
||||
[icon-path url]
|
||||
(when (and (seq icon-path)
|
||||
(seq url))
|
||||
(if (string/starts-with? icon-path "http")
|
||||
icon-path
|
||||
(str (utils.string/remove-trailing-slash url) icon-path))))
|
||||
|
||||
(def method-to-screen
|
||||
{constants/wallet-connect-personal-sign-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-typed-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-sign-typed-v4-method :screen/wallet-connect.sign-message
|
||||
constants/wallet-connect-eth-send-transaction-method :screen/wallet-connect.send-transaction
|
||||
constants/wallet-connect-eth-sign-transaction-method :screen/wallet-connect.sign-transaction})
|
||||
|
||||
(defn extract-native-call-signature
|
||||
[data]
|
||||
(-> data transforms/json->clj :result))
|
||||
|
||||
(defn get-request-method
|
||||
[event]
|
||||
(get-in event [:params :request :method]))
|
||||
|
||||
(defn get-request-params
|
||||
[event]
|
||||
(get-in event [:params :request :params]))
|
||||
|
||||
(defn get-db-current-request-event
|
||||
[db]
|
||||
(get-in db [:wallet-connect/current-request :event]))
|
||||
|
||||
(defn get-session-dapp-metadata
|
||||
[proposal]
|
||||
(let [metadata (get-in proposal [:params :proposer :metadata])
|
||||
origin (get-in proposal [:verifyContext :verified :origin])]
|
||||
(or metadata {:url origin})))
|
||||
|
||||
(defn get-current-request-dapp
|
||||
[request sessions]
|
||||
(let [dapp-url (get-in request [:event :verifyContext :verified :origin])]
|
||||
(->> sessions
|
||||
(filter (fn [session]
|
||||
(= (utils.string/remove-trailing-slash dapp-url)
|
||||
(utils.string/remove-trailing-slash (get session :url)))))
|
||||
first)))
|
||||
|
||||
(defn get-dapp-redirect-url
|
||||
[session]
|
||||
(get-in session [:peer :metadata :redirect :native]))
|
||||
|
||||
(defn get-db-current-request-params
|
||||
[db]
|
||||
(-> db
|
||||
get-db-current-request-event
|
||||
get-request-params))
|
||||
@@ -0,0 +1,27 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.data-store-test
|
||||
(:require
|
||||
[cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as sut]))
|
||||
|
||||
(deftest get-current-request-dapp-test
|
||||
(testing "returns the correct dapp based on the request's origin"
|
||||
(let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}}
|
||||
sessions [{:url "https://dapp.com"}
|
||||
{:url "https://anotherdapp.com"}]]
|
||||
(is (= {:url "https://dapp.com"}
|
||||
(sut/get-current-request-dapp request sessions)))))
|
||||
|
||||
(testing "returns nil if no matching dapp is found"
|
||||
(let [request {:event {:verifyContext {:verified {:origin "https://dapp.com"}}}}
|
||||
sessions [{:url "https://anotherdapp.com"}]]
|
||||
(is (nil? (sut/get-current-request-dapp request sessions))))))
|
||||
|
||||
(deftest get-dapp-redirect-url-test
|
||||
(testing "returns the native redirect URL if it exists"
|
||||
(let [session {:peer {:metadata {:redirect {:native "native://redirect-url"}}}}]
|
||||
(is (= "native://redirect-url"
|
||||
(sut/get-dapp-redirect-url session)))))
|
||||
|
||||
(testing "returns nil if no redirect URL is found"
|
||||
(let [session {:peer {:metadata {}}}]
|
||||
(is (nil? (sut/get-dapp-redirect-url session))))))
|
||||
@@ -0,0 +1,92 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.networks
|
||||
(:require [clojure.edn :as edn]
|
||||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.utils.networks :as networks]
|
||||
[utils.string]))
|
||||
|
||||
(defn chain-id->eip155
|
||||
[chain-id]
|
||||
(str "eip155:" chain-id))
|
||||
|
||||
(defn eip155->chain-id
|
||||
[chain-id-str]
|
||||
(-> chain-id-str
|
||||
(string/split #":")
|
||||
last
|
||||
edn/read-string))
|
||||
|
||||
(defn format-eip155-address
|
||||
[address chain-id]
|
||||
(str chain-id ":" address))
|
||||
|
||||
(defn- add-full-testnet-name
|
||||
"Updates the `:full-name` key with the full testnet name if using testnet `:chain-id`.\n
|
||||
e.g. `{:full-name \"Mainnet\"}` -> `{:full-name \"Mainnet Sepolia\"`}`"
|
||||
[network]
|
||||
(let [add-testnet-name (fn [testnet-name]
|
||||
(update network :full-name #(str % " " testnet-name)))]
|
||||
(condp #(contains? %1 %2) (:chain-id network)
|
||||
constants/sepolia-chain-ids (add-testnet-name constants/sepolia-full-name)
|
||||
constants/goerli-chain-ids (add-testnet-name constants/goerli-full-name)
|
||||
network)))
|
||||
|
||||
(defn chain-id->network-details
|
||||
[chain-id]
|
||||
(-> chain-id
|
||||
(networks/get-network-details)
|
||||
(add-full-testnet-name)))
|
||||
|
||||
(defn session-networks-allowed?
|
||||
[testnet-mode? {:keys [chains]}]
|
||||
(let [chain-ids (set (map (fn [chain]
|
||||
(-> chain
|
||||
(string/split ":")
|
||||
second
|
||||
js/parseInt))
|
||||
chains))]
|
||||
(if testnet-mode?
|
||||
(set/subset? chain-ids (set/union constants/sepolia-chain-ids constants/goerli-chain-ids))
|
||||
(set/subset? chain-ids constants/mainnet-chain-ids))))
|
||||
|
||||
(defn get-proposal-networks
|
||||
[proposal]
|
||||
(let [required-namespaces (get-in proposal [:params :requiredNamespaces])
|
||||
optional-namespaces (get-in proposal [:params :optionalNamespaces])]
|
||||
(->> [required-namespaces optional-namespaces]
|
||||
(map #(get-in % [:eip155 :chains]))
|
||||
(apply concat)
|
||||
(into #{}))))
|
||||
|
||||
(defn proposal-networks-intersection
|
||||
[proposal supported-networks]
|
||||
(let [proposed-networks (get-proposal-networks proposal)]
|
||||
(->> supported-networks
|
||||
(filter #(->> %
|
||||
chain-id->eip155
|
||||
(contains? proposed-networks))))))
|
||||
|
||||
(defn required-networks-supported?
|
||||
[proposal supported-networks]
|
||||
(let [supported-namespaces #{:eip155}
|
||||
required-namespaces (get-in proposal [:params :requiredNamespaces])]
|
||||
(when (every? #(contains? supported-namespaces %)
|
||||
(keys required-namespaces))
|
||||
(let [required-networks (get-in required-namespaces [:eip155 :chains])
|
||||
supported-eip155 (set (map chain-id->eip155 supported-networks))]
|
||||
(every? #(contains? supported-eip155 %)
|
||||
required-networks)))))
|
||||
|
||||
(defn get-networks-by-mode
|
||||
[db]
|
||||
(let [test-mode? (get-in db [:profile/profile :test-networks-enabled?])
|
||||
networks (get-in db [:wallet :networks (if test-mode? :test :prod)])]
|
||||
(mapv #(-> % :chain-id) networks)))
|
||||
|
||||
(defn event-should-be-handled?
|
||||
[db {:keys [topic]}]
|
||||
(let [testnet-mode? (get-in db [:profile/profile :test-networks-enabled?])]
|
||||
(some #(and (= (:topic %) topic)
|
||||
(session-networks-allowed? testnet-mode? %))
|
||||
(:wallet-connect/sessions db))))
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.rpc
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.rpc
|
||||
(:require [oops.core :as oops]
|
||||
[promesa.core :as promesa]
|
||||
[status-im.common.json-rpc.events :as rpc-events]
|
||||
@@ -0,0 +1,33 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.sessions
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn sdk-session->db-session
|
||||
[{:keys [topic expiry pairingTopic] :as session}]
|
||||
{:topic topic
|
||||
:expiry expiry
|
||||
:sessionJson (transforms/clj->json session)
|
||||
:pairingTopic pairingTopic
|
||||
:name (get-in session [:peer :metadata :name])
|
||||
:iconUrl (get-in session [:peer :metadata :icons 0])
|
||||
:url (get-in session [:peer :metadata :url])
|
||||
:accounts (get-in session [:namespaces :eip155 :accounts])
|
||||
:chains (get-in session [:namespaces :eip155 :chains])
|
||||
:disconnected false})
|
||||
|
||||
(defn filter-operable-accounts
|
||||
[accounts]
|
||||
(filter #(and (:operable? %)
|
||||
(not (:watch-only? %)))
|
||||
accounts))
|
||||
|
||||
(defn filter-sessions-for-account-addresses
|
||||
[account-addresses sessions]
|
||||
(filter (fn [{:keys [accounts]}]
|
||||
(some (fn [account]
|
||||
(some (fn [account-address]
|
||||
(string/includes? account account-address))
|
||||
account-addresses))
|
||||
accounts))
|
||||
sessions))
|
||||
@@ -1,8 +1,10 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.signing
|
||||
(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.core :as core]
|
||||
[status-im.contexts.wallet.wallet-connect.rpc :as rpc]
|
||||
[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]))
|
||||
@@ -29,7 +31,7 @@
|
||||
:password password}
|
||||
transforms/clj->json
|
||||
native-module/sign-message
|
||||
(promesa/then core/extract-native-call-signature)))
|
||||
(promesa/then data-store/extract-native-call-signature)))
|
||||
|
||||
(defn personal-sign
|
||||
[password address data]
|
||||
@@ -40,7 +42,7 @@
|
||||
(defn eth-sign-typed-data
|
||||
[password address data chain-id-eip155 version]
|
||||
(let [legacy? (= version :v1)
|
||||
chain-id (core/eip155->chain-id chain-id-eip155)]
|
||||
chain-id (networks/eip155->chain-id chain-id-eip155)]
|
||||
(rpc/wallet-safe-sign-typed-data data
|
||||
address
|
||||
password
|
||||
@@ -1,17 +1,18 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.transactions
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.transactions
|
||||
(:require [cljs-bean.core :as bean]
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[promesa.core :as promesa]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.wallet-connect.core :as core]
|
||||
[status-im.contexts.wallet.wallet-connect.rpc :as rpc]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.rpc :as rpc]
|
||||
[utils.money :as money]
|
||||
[utils.transforms :as transforms]))
|
||||
|
||||
(defn transaction-request?
|
||||
[event]
|
||||
(->> (core/get-request-method event)
|
||||
(->> (data-store/get-request-method event)
|
||||
(contains? #{constants/wallet-connect-eth-send-transaction-method
|
||||
constants/wallet-connect-eth-sign-transaction-method})))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils
|
||||
(ns status-im.contexts.wallet.wallet-connect.utils.uri
|
||||
(:require [react-native.wallet-connect :as wallet-connect]))
|
||||
|
||||
(defn version-supported?
|
||||
@@ -48,7 +48,7 @@
|
||||
status-im.contexts.wallet.send.events
|
||||
status-im.contexts.wallet.signals
|
||||
status-im.contexts.wallet.swap.events
|
||||
status-im.contexts.wallet.wallet-connect.events
|
||||
status-im.contexts.wallet.wallet-connect.events.core
|
||||
[status-im.db :as db]
|
||||
status-im.navigation.effects
|
||||
status-im.navigation.events
|
||||
|
||||
@@ -127,10 +127,11 @@
|
||||
[status-im.contexts.wallet.swap.swap-proposal.view :as wallet-swap-propasal]
|
||||
[status-im.contexts.wallet.wallet-connect.modals.send-transaction.view :as
|
||||
wallet-connect-send-transaction]
|
||||
[status-im.contexts.wallet.wallet-connect.modals.session-proposal.view :as
|
||||
wallet-connect-session-proposal]
|
||||
[status-im.contexts.wallet.wallet-connect.modals.sign-message.view :as wallet-connect-sign-message]
|
||||
[status-im.contexts.wallet.wallet-connect.modals.sign-transaction.view :as
|
||||
wallet-connect-sign-transaction]
|
||||
[status-im.contexts.wallet.wallet-connect.session-proposal.view :as wallet-connect-session-proposal]
|
||||
[status-im.navigation.options :as options]
|
||||
[status-im.navigation.transitions :as transitions]))
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
(ns status-im.subs.bottom-sheet
|
||||
(:require
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:bottom-sheet-sheets
|
||||
:<- [:bottom-sheet]
|
||||
:-> :sheets)
|
||||
@@ -228,6 +228,7 @@
|
||||
community? (some? current-community)
|
||||
group-admin? (contains? admins current-public-key)
|
||||
community-admin? (get current-community :admin false)
|
||||
community-member? (get current-community :is-member? false)
|
||||
|
||||
message-pin-enabled
|
||||
(cond public? false
|
||||
@@ -247,6 +248,7 @@
|
||||
:chat-id chat-id
|
||||
:in-pinned-view? (boolean in-pinned-view?)
|
||||
:able-to-send-message? able-to-send-message?
|
||||
:community-member? community-member?
|
||||
:message-pin-enabled message-pin-enabled
|
||||
:can-delete-message-for-everyone? can-delete-message-for-everyone?})))
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
status-im.subs.activity-center
|
||||
status-im.subs.alert-banner
|
||||
status-im.subs.biometrics
|
||||
status-im.subs.bottom-sheet
|
||||
status-im.subs.chats
|
||||
status-im.subs.communities
|
||||
status-im.subs.community.account-selection
|
||||
@@ -19,12 +20,12 @@
|
||||
status-im.subs.wallet.activities
|
||||
status-im.subs.wallet.buy
|
||||
status-im.subs.wallet.collectibles
|
||||
status-im.subs.wallet.dapps.core
|
||||
status-im.subs.wallet.networks
|
||||
status-im.subs.wallet.saved-addresses
|
||||
status-im.subs.wallet.send
|
||||
status-im.subs.wallet.swap
|
||||
status-im.subs.wallet.wallet
|
||||
status-im.subs.wallet.wallet-connect))
|
||||
status-im.subs.wallet.wallet))
|
||||
|
||||
(defn reg-root-key-sub
|
||||
[sub-name db-key]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
(ns status-im.subs.wallet.activities
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[quo.foundations.resources :as quo.resources]
|
||||
[quo.foundations.resources]
|
||||
@@ -33,12 +32,6 @@
|
||||
(native-module/hex-to-number)
|
||||
(str)))
|
||||
|
||||
(defn- normalize-nft-name
|
||||
[token-id nft-name]
|
||||
(if (and (some? token-id) (string/blank? nft-name))
|
||||
"Unknown"
|
||||
nft-name))
|
||||
|
||||
(defn- get-token-amount
|
||||
[token amount]
|
||||
(let [token-type (:token-type token)]
|
||||
@@ -77,7 +70,7 @@
|
||||
(defn- process-activity-by-type
|
||||
[chain-id->network-name
|
||||
{:keys [activity-type activity-status timestamp sender recipient token-in token-out
|
||||
chain-id-in chain-id-out nft-name]
|
||||
chain-id-in chain-id-out]
|
||||
:as data}]
|
||||
(let [network-name (chain-id->network-name (or chain-id-in chain-id-out))
|
||||
token-id (some-> (or token-in token-out)
|
||||
@@ -91,8 +84,7 @@
|
||||
:network-name network-name
|
||||
:token-id token-id
|
||||
:status (constants/wallet-activity-status->name activity-status)
|
||||
:network-logo (quo.resources/get-network network-name)
|
||||
:nft-name (normalize-nft-name token-id nft-name))]
|
||||
:network-logo (quo.resources/get-network network-name))]
|
||||
(condp = activity-type
|
||||
constants/wallet-activity-type-send
|
||||
(process-send-activity activity)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
(ns status-im.subs.wallet.dapps.core
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.contexts.wallet.common.utils :as wallet-utils]
|
||||
status-im.subs.wallet.dapps.proposals
|
||||
status-im.subs.wallet.dapps.requests
|
||||
status-im.subs.wallet.dapps.sessions
|
||||
status-im.subs.wallet.dapps.transactions
|
||||
[utils.string]))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/account-details-by-address
|
||||
:<- [:wallet/accounts-without-watched-accounts]
|
||||
(fn [accounts [_ address]]
|
||||
(let [{:keys [customization-color name emoji]} (wallet-utils/get-account-by-address accounts address)]
|
||||
{:customization-color customization-color
|
||||
:name name
|
||||
:emoji emoji})))
|
||||
@@ -0,0 +1,45 @@
|
||||
(ns status-im.subs.wallet.dapps.proposals
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[utils.string]))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-proposal-request
|
||||
:<- [:wallet-connect/current-proposal]
|
||||
:-> :request)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/session-proposal-networks
|
||||
:<- [:wallet-connect/current-proposal]
|
||||
:-> :session-networks)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/session-proposer
|
||||
:<- [:wallet-connect/current-proposal-request]
|
||||
(fn [proposal]
|
||||
(-> proposal :params :proposer)))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/session-proposer-name
|
||||
:<- [:wallet-connect/session-proposer]
|
||||
(fn [proposer]
|
||||
(let [{:keys [name url]} (-> proposer :metadata)]
|
||||
(data-store/compute-dapp-name name url))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/session-proposal-network-details
|
||||
:<- [:wallet-connect/session-proposal-networks]
|
||||
:<- [:wallet/network-details]
|
||||
(fn [[session-networks network-details]]
|
||||
(let [supported-networks (map :chain-id network-details)
|
||||
session-networks (filterv #(contains? (set session-networks) (:chain-id %))
|
||||
network-details)
|
||||
all-networks-in-session? (= (count supported-networks) (count session-networks))]
|
||||
{:session-networks session-networks
|
||||
:all-networks-in-session? all-networks-in-session?})))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-proposal-address
|
||||
(fn [db]
|
||||
(get-in db [:wallet-connect/current-proposal :address])))
|
||||
@@ -1,9 +1,9 @@
|
||||
(ns status-im.subs.wallet.wallet-connect-test
|
||||
(ns status-im.subs.wallet.dapps.proposals-test
|
||||
(:require
|
||||
[cljs.test :refer [is testing]]
|
||||
[re-frame.db :as rf-db]
|
||||
status-im.subs.root
|
||||
status-im.subs.wallet.wallet-connect
|
||||
status-im.subs.wallet.dapps.core
|
||||
[test-helpers.unit :as h]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
(ns status-im.subs.wallet.dapps.requests
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.contexts.wallet.common.utils :as wallet-utils]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.data-store :as
|
||||
data-store]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
||||
[utils.string]))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-request-address
|
||||
:<- [:wallet-connect/current-request]
|
||||
:-> :address)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-request-display-data
|
||||
:<- [:wallet-connect/current-request]
|
||||
:-> :display-data)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-request-account-details
|
||||
:<- [:wallet-connect/current-request-address]
|
||||
:<- [:wallet/accounts-without-watched-accounts]
|
||||
(fn [[address accounts]]
|
||||
(let [{:keys [customization-color name emoji]} (wallet-utils/get-account-by-address accounts address)]
|
||||
{:customization-color customization-color
|
||||
:name name
|
||||
:emoji emoji})))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-request-dapp
|
||||
:<- [:wallet-connect/current-request]
|
||||
:<- [:wallet-connect/sessions]
|
||||
(fn [[request sessions]]
|
||||
(data-store/get-current-request-dapp request sessions)))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/chain-id
|
||||
:<- [:wallet-connect/current-request]
|
||||
(fn [request]
|
||||
(-> request
|
||||
(get-in [:event :params :chainId])
|
||||
(networks/eip155->chain-id))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-request-network
|
||||
:<- [:wallet-connect/chain-id]
|
||||
networks/chain-id->network-details)
|
||||
@@ -0,0 +1,25 @@
|
||||
(ns status-im.subs.wallet.dapps.sessions
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as rf]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.networks :as networks]
|
||||
[utils.string]))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/sessions-for-current-account
|
||||
:<- [:wallet-connect/sessions]
|
||||
:<- [:wallet/current-viewing-account-address]
|
||||
(fn [[sessions address]]
|
||||
(filter
|
||||
(fn [{:keys [accounts]}]
|
||||
(some #(string/includes? % address) accounts))
|
||||
sessions)))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/sessions-for-current-account-and-networks
|
||||
:<- [:wallet-connect/sessions-for-current-account]
|
||||
:<- [:profile/test-networks-enabled?]
|
||||
(fn [[sessions testnet-mode?]]
|
||||
(filter
|
||||
(partial networks/session-networks-allowed? testnet-mode?)
|
||||
sessions)))
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
(ns status-im.subs.wallet.dapps.transactions
|
||||
(:require [re-frame.core :as rf]
|
||||
[status-im.contexts.wallet.common.utils :as wallet-utils]
|
||||
[status-im.contexts.wallet.wallet-connect.utils.transactions :as transactions]
|
||||
[utils.money :as money]
|
||||
[utils.string]))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/transaction-args
|
||||
:<- [:wallet-connect/current-request]
|
||||
(fn [{:keys [event transaction]}]
|
||||
(when (transactions/transaction-request? event)
|
||||
transaction)))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/transaction-suggested-fees
|
||||
:<- [:wallet-connect/current-request]
|
||||
(fn [{:keys [event raw-data]}]
|
||||
(when (transactions/transaction-request? event)
|
||||
(:suggested-fees raw-data))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/transaction-max-fees-wei
|
||||
:<- [:wallet-connect/transaction-args]
|
||||
:<- [:wallet-connect/transaction-suggested-fees]
|
||||
(fn [[transaction suggested-fees]]
|
||||
(when transaction
|
||||
(let [{:keys [gasPrice gas gasLimit maxFeePerGas]} transaction
|
||||
eip-1559-chain? (:eip1559Enabled suggested-fees)
|
||||
gas-limit (or gasLimit gas)
|
||||
max-gas-fee (if eip-1559-chain? maxFeePerGas gasPrice)]
|
||||
(money/bignumber (* max-gas-fee gas-limit))))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/account-eth-token
|
||||
:<- [:wallet-connect/current-request-address]
|
||||
:<- [:wallet/accounts]
|
||||
(fn [[address accounts]]
|
||||
(let [fee-token "ETH"
|
||||
find-account #(when (= (:address %) address) %)
|
||||
find-token #(when (= (:symbol %) fee-token) %)]
|
||||
(->> accounts
|
||||
(some find-account)
|
||||
:tokens
|
||||
(some find-token)))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet-connect/current-request-transaction-information
|
||||
:<- [:wallet-connect/chain-id]
|
||||
:<- [:wallet-connect/transaction-max-fees-wei]
|
||||
:<- [:wallet-connect/transaction-args]
|
||||
:<- [:wallet-connect/account-eth-token]
|
||||
:<- [:profile/currency]
|
||||
:<- [: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)]
|
||||
{:total-transaction-value total-transaction-value
|
||||
:balance balance
|
||||
:max-fees max-fees-wei
|
||||
:max-fees-fiat-value max-fees-fiat
|
||||
:max-fees-fiat-formatted max-fees-fiat-formatted
|
||||
:error-state (cond
|
||||
(not (money/sufficient-funds? tx-value balance))
|
||||
:not-enough-assets
|
||||
|
||||
(not (money/sufficient-funds? total-transaction-value
|
||||
balance))
|
||||
:not-enough-assets-to-pay-gas-fees)}))))
|
||||
@@ -2,6 +2,7 @@
|
||||
(:require
|
||||
[re-frame.core :as rf]
|
||||
[status-im.contexts.wallet.common.activity-tab.constants :as constants]
|
||||
[status-im.contexts.wallet.send.utils :as send-utils]
|
||||
[utils.number]))
|
||||
|
||||
(rf/reg-sub
|
||||
@@ -16,7 +17,7 @@
|
||||
:-> :send)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-send-recipient
|
||||
:wallet/send-recipient
|
||||
:<- [:wallet/wallet-send]
|
||||
:-> :recipient)
|
||||
|
||||
@@ -31,10 +32,20 @@
|
||||
:-> :just-completed-transaction?)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-send-amount
|
||||
:wallet/send-amount
|
||||
:<- [:wallet/wallet-send]
|
||||
:-> :amount)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/send-tx-type
|
||||
:<- [:wallet/wallet-send]
|
||||
:-> :tx-type)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/sending-collectible?
|
||||
:<- [:wallet/send-tx-type]
|
||||
#(send-utils/tx-type-collectible? %))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/send-transaction-progress
|
||||
:<- [:wallet/send-transaction-ids]
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
(ns status-im.subs.wallet.swap
|
||||
(:require [re-frame.core :as rf]
|
||||
(:require [clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
[re-frame.core :as rf]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.utils :as utils]
|
||||
[utils.money :as money]))
|
||||
[status-im.contexts.wallet.send.utils :as send-utils]
|
||||
[utils.hex :as hex]
|
||||
[utils.money :as money]
|
||||
[utils.number :as number]))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap
|
||||
@@ -19,6 +24,16 @@
|
||||
:<- [:wallet/swap]
|
||||
:-> :asset-to-receive)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-network
|
||||
:<- [:wallet/swap]
|
||||
:-> :network)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-error-response
|
||||
:<- [:wallet/swap]
|
||||
:-> :error-response)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-asset-to-pay-token-symbol
|
||||
:<- [:wallet/swap-asset-to-pay]
|
||||
@@ -62,3 +77,123 @@
|
||||
:wallet/swap-max-slippage
|
||||
:<- [:wallet/swap]
|
||||
:-> :max-slippage)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-approval-transaction-id
|
||||
:<- [:wallet/swap]
|
||||
:-> :approval-transaction-id)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-approval-transaction-status
|
||||
:<- [:wallet/transactions]
|
||||
:<- [:wallet/swap-approval-transaction-id]
|
||||
(fn [[transactions approval-transaction-id]]
|
||||
(get-in transactions [approval-transaction-id :status])))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-proposal
|
||||
:<- [:wallet/swap]
|
||||
:-> :swap-proposal)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-loading-swap-proposal?
|
||||
:<- [:wallet/swap]
|
||||
:-> :loading-swap-proposal?)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-proposal-amount-out
|
||||
:<- [:wallet/swap-proposal]
|
||||
:-> :amount-out)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-proposal-amount-in
|
||||
:<- [:wallet/swap-proposal]
|
||||
:-> :amount-in)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-receive-amount
|
||||
:<- [:wallet/swap-proposal-amount-out]
|
||||
:<- [:wallet/swap-asset-to-receive]
|
||||
(fn [[amount-out asset-to-receive]]
|
||||
(let [receive-token-decimals (:decimals asset-to-receive)
|
||||
receive-amount (when amount-out
|
||||
(number/convert-to-whole-number
|
||||
(native-module/hex-to-number
|
||||
(hex/normalize-hex
|
||||
amount-out))
|
||||
receive-token-decimals))
|
||||
receive-amount (when amount-out
|
||||
(number/remove-trailing-zeroes
|
||||
(.toFixed receive-amount
|
||||
(min receive-token-decimals
|
||||
constants/min-token-decimals-to-display))))]
|
||||
(or receive-amount 0))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-pay-amount
|
||||
:<- [:wallet/swap-proposal-amount-in]
|
||||
:<- [:wallet/swap-asset-to-pay]
|
||||
(fn [[amount-in asset-to-pay]]
|
||||
(let [pay-token-decimals (:decimals asset-to-pay)
|
||||
pay-amount (when amount-in
|
||||
(number/convert-to-whole-number
|
||||
(native-module/hex-to-number
|
||||
(hex/normalize-hex
|
||||
amount-in))
|
||||
pay-token-decimals))
|
||||
pay-amount (when amount-in
|
||||
(number/remove-trailing-zeroes
|
||||
(.toFixed pay-amount
|
||||
(min pay-token-decimals
|
||||
constants/min-token-decimals-to-display))))]
|
||||
(or pay-amount 0))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-proposal-provider
|
||||
:<- [:wallet/swap-proposal]
|
||||
(fn [swap-proposal]
|
||||
(when swap-proposal
|
||||
(let [bridge-name (:bridge-name swap-proposal)
|
||||
provider-key (keyword (string/lower-case bridge-name))]
|
||||
(get constants/swap-providers provider-key)))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-proposal-approval-required
|
||||
:<- [:wallet/swap-proposal]
|
||||
:-> :approval-required)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-proposal-approval-amount-required
|
||||
:<- [:wallet/swap-proposal]
|
||||
:-> :approval-amount-required)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/swap-proposal-estimated-time
|
||||
:<- [:wallet/swap-proposal]
|
||||
:-> :estimated-time)
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/wallet-swap-proposal-fee-fiat-formatted
|
||||
:<- [:wallet/current-viewing-account]
|
||||
:<- [:wallet/swap-proposal]
|
||||
:<- [:profile/currency]
|
||||
:<- [:profile/currency-symbol]
|
||||
(fn [[account swap-proposal currency currency-symbol] [_ token-symbol-for-fees]]
|
||||
(when token-symbol-for-fees
|
||||
(let [tokens (:tokens account)
|
||||
token-for-fees (first (filter #(= (string/lower-case (:symbol %))
|
||||
(string/lower-case token-symbol-for-fees))
|
||||
tokens))
|
||||
fee-in-native-token (send-utils/calculate-full-route-gas-fee [swap-proposal])
|
||||
fee-in-crypto-formatted (utils/get-standard-crypto-format
|
||||
token-for-fees
|
||||
fee-in-native-token)
|
||||
fee-in-fiat (utils/calculate-token-fiat-value
|
||||
{:currency currency
|
||||
:balance fee-in-native-token
|
||||
:token token-for-fees})
|
||||
fee-formatted (utils/get-standard-fiat-format
|
||||
fee-in-crypto-formatted
|
||||
currency-symbol
|
||||
fee-in-fiat)]
|
||||
fee-formatted))))
|
||||
|
||||
@@ -16,6 +16,27 @@
|
||||
:popular? true
|
||||
:token? false}})
|
||||
|
||||
(def ^:private accounts-with-tokens
|
||||
{:0x1 {:tokens [{:symbol "ETH"
|
||||
:balances-per-chain {1 {:raw-balance "100"}}
|
||||
:market-values-per-currency {:usd {:price 10000}}}
|
||||
{:symbol "SNT"
|
||||
:balances-per-chain {1 {:raw-balance "100"}}
|
||||
:market-values-per-currency {:usd {:price 10000}}}]
|
||||
:network-preferences-names #{}
|
||||
:customization-color nil
|
||||
:operable? true
|
||||
:operable :fully
|
||||
:address "0x1"}
|
||||
:0x2 {:tokens [{:symbol "SNT"
|
||||
:balances-per-chain {1 {:raw-balance "200"}}
|
||||
:market-values-per-currency {:usd {:price 10000}}}]
|
||||
:network-preferences-names #{}
|
||||
:customization-color nil
|
||||
:operable? true
|
||||
:operable :partially
|
||||
:address "0x2"}})
|
||||
|
||||
(def networks
|
||||
{:mainnet-network
|
||||
{:full-name "Mainnet"
|
||||
@@ -110,7 +131,18 @@
|
||||
:token-list-id ""
|
||||
:built-on "ETH"
|
||||
:verified true}
|
||||
:network nil})
|
||||
:network (networks :mainnet-network)
|
||||
:swap-proposal {:amount-out "0x10000"
|
||||
:amount-in "0x10000"
|
||||
:approval-required true
|
||||
:approval-amount-required "0x10000"
|
||||
:gas-amount "25000"
|
||||
:gas-fees {:max-fee-per-gas-medium "4"
|
||||
:eip-1559-enabled true
|
||||
:l-1-gas-fee "0"}}
|
||||
:error-response "Error"
|
||||
:loading-swap-proposal? false
|
||||
:max-slippage 0.5})
|
||||
|
||||
(h/deftest-sub :wallet/swap
|
||||
[sub-name]
|
||||
@@ -160,3 +192,83 @@
|
||||
(assoc :currencies currencies)
|
||||
(assoc-in [:wallet :ui :swap] swap-data)))
|
||||
(is (match? {:crypto "1 SNT" :fiat "$0.03"} (rf/sub [sub-name 1])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-network
|
||||
[sub-name]
|
||||
(testing "Return the current swap network"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (match? (swap-data :network) (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-error-response
|
||||
[sub-name]
|
||||
(testing "Return the swap error response"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (match? (swap-data :error-response) (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-max-slippage
|
||||
[sub-name]
|
||||
(testing "Return the max slippage for the swap"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (match? 0.5 (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-loading-swap-proposal?
|
||||
[sub-name]
|
||||
(testing "Return if swap is loading the swap proposal"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (false? (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-proposal
|
||||
[sub-name]
|
||||
(testing "Return the swap proposal"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (match? (swap-data :swap-proposal) (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-proposal-amount-out
|
||||
[sub-name]
|
||||
(testing "Return the amount out in the swap proposal"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (match? "0x10000" (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-proposal-approval-required
|
||||
[sub-name]
|
||||
(testing "Return if approval is required in the swap proposal"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (true? (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/swap-proposal-approval-amount-required
|
||||
[sub-name]
|
||||
(testing "Return the approval amount required in the swap proposal"
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:wallet :ui :swap]
|
||||
swap-data)
|
||||
(is (match? "0x10000" (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/wallet-swap-proposal-fee-fiat-formatted
|
||||
[sub-name]
|
||||
(testing "wallet send fee calculated and formatted in fiat"
|
||||
(swap! rf-db/app-db
|
||||
#(-> %
|
||||
(assoc-in [:wallet :accounts] accounts-with-tokens)
|
||||
(assoc-in [:wallet :current-viewing-account-address] "0x1")
|
||||
(assoc-in [:wallet :ui :swap] swap-data)
|
||||
(assoc-in [:currencies] currencies)
|
||||
(assoc-in [:profile/profile :currency] :usd)
|
||||
(assoc-in [:profile/profile :currency-symbol] "$")))
|
||||
|
||||
(let [token-symbol-for-fees "ETH"
|
||||
result (rf/sub [sub-name token-symbol-for-fees])]
|
||||
(is (match? result "$1.00")))))
|
||||
|
||||