Compare commits

..
Author SHA1 Message Date
frank f9fdd5c671 fix_: random empty account 2024-09-11 19:31:03 +08:00
John Ngei 462101dbf5 chore: Compress syncing images (#20907) 2024-09-10 20:06:46 -03:00
Parvesh Monu 00f1463e94 Change order of bottom tabs and select wallet-stack as default tab (#21240)
* Changed order of bottom tabs
* Changed default bottom to wallet-stack
2024-09-10 19:41:34 +05:30
Lungu CristianandMohsen 9135e915ab Restructure wallet-connect namespaces (#21167)
Co-authored-by: Mohsen <maxghafori@gmail.com>
2024-09-10 15:59:18 +03:00
John NgeiandUlises M d7242f1c70 fix(chats): Last message overlapped by the composer when a message is pinned (#20606)
* fixed overlapping last message by the composer when there is a pinned message

* prevent a user who is not a member of a community from pinning or unpinning channel messages

---------

Co-authored-by: Ulises M <ulises.ssb@hotmail.com>
2024-09-09 17:19:10 -06:00
Ulises Manuel fa4ea0640d fix(onboarding): Can't create profile (#21170)
* Remove unnecessary React.Fragment and reagent functional components notation

* Fix continue button not responding in the Create Profile screen
2024-09-09 14:43:36 -06:00
Ulises Manuel 8f94332d59 feat(wallet): Hide account switcher in send flow (#20892)
* Fix input amount step skipped while sending a token

* Rename events to remove the `wallet` unnecessary suffix

* Code style fixes

* Fix React warnings:
    1. About unique key in `select-address.tabs.view` along with a refactor
    2. The deprecated `:keyboard-should-persist-taps` as `true` in `send.select-address.view`

* Hide account switcher while sending a collectible

* Fix tests
2024-09-09 14:24:00 -06:00
Brian Sztamfater 337ec963b7 feat: autorefresh swap proposal (#21143)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-09 01:27:31 -03:00
Ulises Manuel cf5a395b29 feat(wallet): Show collectibles in activity tab (#21109)
* Add UNKNOWN collectible variant for context tags

* Show fallbacks for collectibles in the activity tab
2024-09-06 15:44:12 -06:00
Brian Sztamfater e2d597401c feat(swap): trigger swap transactions (#21134)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-06 11:30:15 -03:00
Mohsen 3ae500ff68 [#20909] fix: dapp wrong network refusal (#21123) 2024-09-06 15:56:04 +03:00
Parvesh Monu 779eadd787 Update Sign in flow on Profiles list screen (#21160) 2024-09-06 15:46:15 +05:30
Siddarth Kumar 110475a973 fixes for building universal APK (#21175)
In my previous PR -> https://github.com/status-im/status-mobile/pull/21163 I had attempted to build universalAPK only for release builds but had missed out on these changes.

This commit ensures `universalApk` value of `gradle` config is properly set.
This commit also points to my branch in `status-jenkins-lib` which modifies the APK size restrictions.
Its not `110 MB` for Universal APKs and `70 MB` for Individual Architecture APKs
2024-09-05 14:31:03 +05:30
Ulises Manuel 74469c2b61 feat(Activity Center): Add support for preview images in chat mentions (#21150)
* fix `clojure.core/type` shadowed

* Remove comment about rn/delay-render
2024-09-04 12:25:14 -06:00
Brian Sztamfater d5238ac82f feat: approve token transactions on swap (#21076)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-04 13:53:45 -03:00
Brian Sztamfater c861d95d69 feat(swap): fetch swap proposals (#21040)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-09-04 13:40:06 -03:00
108 changed files with 2749 additions and 1699 deletions
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.9'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.9'
pipeline {
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.9'
pipeline {
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.8'
library 'status-jenkins-lib@v1.9.9'
pipeline {
agent {
+2
View File
@@ -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";
Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

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]
+94 -77
View File
@@ -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?]
+11 -5
View File
@@ -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?}]))
+14 -1
View File
@@ -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}]))
+15 -9
View File
@@ -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
+3 -2
View File
@@ -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]]
+1 -1
View File
@@ -60,7 +60,7 @@
:size 32
:default-active default-active
:data data
:on-change #(on-change %)}])
:on-change on-change}])
(defn view
[]
+60 -145
View File
@@ -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])
+28 -3
View File
@@ -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
+325 -19
View File
@@ -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?
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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]))
+8
View File
@@ -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)
+2
View File
@@ -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?})))
+3 -2
View File
@@ -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]
+2 -10
View File
@@ -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)
+17
View File
@@ -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)}))))
+13 -2
View File
@@ -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]
+137 -2
View File
@@ -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))))
+113 -1
View File
@@ -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")))))

Some files were not shown because too many files have changed in this diff Show More