Compare commits

...
Author SHA1 Message Date
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
51 changed files with 1657 additions and 767 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";
@@ -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
+13 -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,19 @@
(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)
@@ -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]
@@ -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})
+31 -143
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]))
@@ -518,23 +518,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 +582,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 +613,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
+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
+321 -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,10 +41,6 @@
: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
@@ -47,18 +54,313 @@
(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.swap/set-swap-proposal
(fn [{:keys [db]} [swap-proposal]]
{:db (assoc-in db [:wallet :ui :swap :swap-proposal] swap-proposal)}))
(rf/reg-event-fx :wallet.swap/set-provider
(fn [{:keys [db]}]
{:db (assoc-in db [:wallet :ui :swap :providers] [constants/swap-default-provider])}))
(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/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))]]
(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]]
(let [last-request-uuid (get-in db [:wallet :ui :swap :last-request-uuid])
request-uuid (:uuid swap-proposal)
best-routes (:best swap-proposal)
error-response (:error-response swap-proposal)]
(when (= request-uuid last-request-uuid)
{: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)}))))
(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 (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/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,100 @@
[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)]
[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)]
[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 +119,52 @@
: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)]
[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)]
[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 +180,52 @@
: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-fees? (rf/sub [:wallet/swap-loading-fees?])
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 max-fees
:loading? loading-fees?
:size :small}]
[data-item
{:title (i18n/label :t/est-time)
:subtitle (i18n/label :t/time-in-mins {:minutes (str estimated-time)})}]]]))
(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-fees? (rf/sub [:wallet/swap-loading-fees?])
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? loading-fees?
: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 +235,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,260 @@
: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-fees? (rf/sub [:wallet/swap-loading-fees?])
loading-swap-proposal? (rf/sub [:wallet/swap-loading-swap-proposal?])
loading? (or loading-fees? 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?
:size :small}]
[data-item
{:title (i18n/label :t/max-slippage)
:subtitle max-slippage
:subtitle-icon :i/edit
:loading? loading-fees?}]]))
:size :small
:loading? loading?}]]))
(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 pay-input-num-value
: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-fees? (rf/sub [:wallet/swap-loading-fees?])
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?
loading-fees?)
: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)
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])
pay-input-amount (controlled-input/input-value pay-input-state)
pay-token-decimals (:decimals asset-to-pay)
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)))))]
[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,59 @@
: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-fees? (rf/sub [:wallet/swap-loading-fees?])
max-slippage (rf/sub [:wallet/swap-max-slippage])]
[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)})}]
[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 "%")}]]]))
(defn- slide-button
[]
(let [loading-fees? (rf/sub [:wallet/swap-loading-fees?])
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? loading-fees?
: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 (:full-name provider)})]]]))
(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 +194,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]]]]))
@@ -50,7 +50,9 @@
(defn get-session-dapp-metadata
[proposal]
(get-in proposal [:params :proposer :metadata]))
(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]
@@ -109,9 +109,12 @@
(if (wallet-connect-core/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
{:error (wallet-connect/get-sdk-error
constants/wallet-connect-user-rejected-chains-error-key)}]]]})))
{:request event
:error (wallet-connect/get-sdk-error
constants/wallet-connect-user-rejected-chains-error-key)}]]]})))
(rf/reg-event-fx
:wallet-connect/on-session-delete
@@ -115,8 +115,9 @@
(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])]
(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 (wallet-connect-core/get-request-method event)
web3-wallet (get db :wallet-connect/web3-wallet)]
{:db (assoc-in db [:wallet-connect/current-request :response-sent?] true)
+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)
+141 -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,127 @@
:wallet/swap-max-slippage
:<- [:wallet/swap]
:-> :max-slippage)
(rf/reg-sub
:wallet/swap-loading-fees?
:<- [:wallet/swap]
:-> :loading-fees?)
(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]
(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))))
+122 -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,19 @@
: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-fees? false
:loading-swap-proposal? false
:max-slippage 0.5})
(h/deftest-sub :wallet/swap
[sub-name]
@@ -160,3 +193,91 @@
(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-fees?
[sub-name]
(testing "Return if swap is loading fees"
(swap! rf-db/app-db assoc-in
[:wallet :ui :swap]
swap-data)
(is (false? (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")))))
+6 -5
View File
@@ -202,11 +202,6 @@
[gas gas-price]
(.times ^js (bignumber gas) ^js (bignumber gas-price)))
(defn crypto->fiat
[crypto fiat-price]
(when-let [^js bn (bignumber crypto)]
(.times bn ^js (bignumber fiat-price))))
(defn percent-change
[from to]
(let [^js bnf (bignumber from)
@@ -221,6 +216,12 @@
(when-let [^js bn (bignumber n)]
(.round bn decimals)))
(defn crypto->fiat
[crypto fiat-price]
(when-let [^js bn (bignumber crypto)]
(-> (.times bn ^js (bignumber fiat-price))
(with-precision 2))))
(defn sufficient-funds?
[^js amount ^js balance]
(when (and amount balance)
+11
View File
@@ -15,6 +15,17 @@
(/ (Math/round (* n scale))
scale)))
(defn convert-to-whole-number
"Converts a fractional `amount` to its corresponding whole number representation
by dividing it by 10 raised to the power of `decimals`. This is often used in financial
calculations where amounts are stored in their smallest units (e.g., cents) and need
to be converted to their whole number equivalents (e.g., dollars).
Example usage:
(convert-to-whole-number 12345 2) ; => 123.45"
[amount decimals]
(/ amount (Math/pow 10 decimals)))
(defn parse-int
"Parses `n` as an integer. Defaults to zero or `default` instead of NaN."
([n]
+20
View File
@@ -3,6 +3,26 @@
[cljs.test :refer [deftest is testing]]
[utils.number]))
(deftest convert-to-whole-number-test
(testing "correctly converts fractional amounts to whole numbers"
(is (= 123.45 (utils.number/convert-to-whole-number 12345 2)))
(is (= 1.2345 (utils.number/convert-to-whole-number 12345 4)))
(is (= 12345.0 (utils.number/convert-to-whole-number 1234500 2)))
(is (= 0.123 (utils.number/convert-to-whole-number 123 3)))
(is (= 1000.0 (utils.number/convert-to-whole-number 1000000 3))))
(testing "handles zero decimals"
(is (= 12345 (utils.number/convert-to-whole-number 12345 0))))
(testing "handles negative amounts"
(is (= -123.45 (utils.number/convert-to-whole-number -12345 2)))
(is (= -1.2345 (utils.number/convert-to-whole-number -12345 4)))
(is (= -0.123 (utils.number/convert-to-whole-number -123 3))))
(testing "handles zero amount"
(is (= 0 (utils.number/convert-to-whole-number 0 2)))
(is (= 0 (utils.number/convert-to-whole-number 0 0)))))
(deftest parse-int-test
(testing "defaults to zero"
(is (= 0 (utils.number/parse-int nil))))
+6
View File
@@ -88,3 +88,9 @@
[url]
(when (string? url)
(string/replace url #"^https?://" "")))
(defn valid-amount-for-token-decimals?
[token-decimals amount-text]
(let [regex-pattern (str "^\\d*\\.?\\d{0," token-decimals "}$")
regex (re-pattern regex-pattern)]
(re-matches regex amount-text)))
+6 -1
View File
@@ -53,7 +53,6 @@
"add-bootnode": "Add bootnode",
"add-contact": "Add contact",
"add-custom-token": "Add custom token",
"add-existing-status-profile": "Add existing Status profile",
"add-favourite": "Add favourite",
"add-mailserver": "Add Status node",
"add-me-to-your-contacts": "Please add me to your contacts",
@@ -2302,6 +2301,7 @@
"slow": "Slow",
"something-about-you": "Something about you",
"something-went-wrong": "Something went wrong",
"something-went-wrong-please-try-again-later": "Something went wrong, please try again later",
"soon": "Soon",
"sort-communities": "Sort communities",
"special-characters": "Special characters",
@@ -2312,6 +2312,8 @@
"specify-symbol": "Specify a symbol",
"spender-contract": "Spender contract",
"spending-cap": "Spending cap",
"spending-cap-failed": "Spending cap failed: {{amount}} {{token-symbol}} for {{provider-name}} in {{account-name}}",
"spending-cap-set": "Spending cap set: {{amount}} {{token-symbol}} for {{provider-name}} in {{account-name}}",
"start-chat": "Start chat",
"start-conversation": "Start conversation",
"start-group-chat": "Start group chat",
@@ -2366,6 +2368,9 @@
"sun": "Sun",
"swap": "Swap",
"swap-details": "Swap details",
"swap-failed": "Swap failed",
"swapped-to": "Swapped {{pay-amount}} {{pay-token-symbol}} to {{receive-amount}} {{receive-token-symbol}}",
"swapping-to": "Swapping {{pay-amount}} {{pay-token-symbol}} to {{receive-amount}} {{receive-token-symbol}}",
"swaps-powered-by": "Swaps powered by {{provider}}",
"switch-to-simple-interface": "Switch to simple interface",
"symbol": "Symbol",
-1
View File
@@ -48,7 +48,6 @@
"add-bootnode": "Bootnodeを追加",
"add-contact": "連絡先を追加",
"add-custom-token": "カスタムトークンを追加",
"add-existing-status-profile": "既存のStatusプロフィールを追加する",
"add-favourite": "お気に入りに追加する",
"add-mailserver": "Statusノードを追加",
"add-me-to-your-contacts": "連絡先に追加してください",