wallet refactoring

Signed-off-by: Goran Jovic <goranjovic@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2018-08-10 20:15:16 +03:00 committed by Goran Jovic
parent 2b6d6722b7
commit 3e7c059b59
No known key found for this signature in database
GPG Key ID: D429D1A9B2EB8A8E
31 changed files with 731 additions and 925 deletions

View File

@ -265,7 +265,7 @@
view)
(views/defview main-screen-modal-view [current-view & components]
(views/letsubs [signing? [:get-in [:wallet :send-transaction :signing?]]]
(views/letsubs [signing? [:get-in [:wallet :send-transaction :show-password-input?]]]
(let [main-screen-view (create-main-screen-view current-view)]
[main-screen-view styles/flex
[keyboard-avoiding-view {:flex 1 :flex-direction :column}

View File

@ -16,7 +16,7 @@ dependencies {
implementation 'com.instabug.library:instabug:3+'
implementation 'status-im:function:0.0.1'
String statusGoVersion = 'develop-ga6d69eba'
String statusGoVersion = 'tags-v0.11.0^0-g4afd9e6c-302'
final String statusGoGroup = 'status-im', statusGoName = 'status-go'
// Check if the local status-go jar exists, and compile against that if it does

View File

@ -504,8 +504,8 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
}
@ReactMethod
public void approveSignRequest(final String id, final String password, final Callback callback) {
Log.d(TAG, "approveSignRequest");
public void sendTransaction(final String txArgsJSON, final String password, final Callback callback) {
Log.d(TAG, "sendTransaction");
if (!checkAvailability()) {
callback.invoke(false);
return;
@ -514,7 +514,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
Runnable r = new Runnable() {
@Override
public void run() {
String res = Statusgo.ApproveSignRequest(id, password);
String res = Statusgo.SendTransaction(txArgsJSON, password);
callback.invoke(res);
}
};
@ -523,8 +523,8 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
}
@ReactMethod
public void approveSignRequestWithArgs(final String id, final String password, final String gas, final String gasPrice, final Callback callback) {
Log.d(TAG, "approveSignRequestWithArgs");
public void signMessage(final String rpcParams, final Callback callback) {
Log.d(TAG, "signMessage");
if (!checkAvailability()) {
callback.invoke(false);
return;
@ -533,7 +533,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
Runnable r = new Runnable() {
@Override
public void run() {
String res = Statusgo.ApproveSignRequestWithArgs(id, password, Long.parseLong(gas), Long.parseLong(gasPrice));
String res = Statusgo.SignMessage(rpcParams);
callback.invoke(res);
}
};
@ -541,24 +541,6 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void discardSignRequest(final String id) {
Log.d(TAG, "discardSignRequest");
if (!checkAvailability()) {
return;
}
Runnable r = new Runnable() {
@Override
public void run() {
Statusgo.DiscardSignRequest(id);
}
};
StatusThreadPoolExecutor.getInstance().execute(r);
}
@ReactMethod
public void setAdjustResize() {
Log.d(TAG, "setAdjustResize");
@ -667,7 +649,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
}
@ReactMethod
public void sendWeb3Request(final String payload, final Callback callback) {
public void callRPC(final String payload, final Callback callback) {
Runnable r = new Runnable() {
@Override
public void run() {
@ -680,7 +662,7 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
}
@ReactMethod
public void sendWeb3PrivateRequest(final String payload, final Callback callback) {
public void callPrivateRPC(final String payload, final Callback callback) {
Runnable r = new Runnable() {
@Override
public void run() {

View File

@ -251,43 +251,30 @@ RCT_EXPORT_METHOD(login:(NSString *)address
}
////////////////////////////////////////////////////////////////////
#pragma mark - Approve Sign Request
//////////////////////////////////////////////////////////////////// approveSignRequests
RCT_EXPORT_METHOD(approveSignRequest:(NSString *)id
#pragma mark - SendTransaction
//////////////////////////////////////////////////////////////////// sendTransaction
RCT_EXPORT_METHOD(sendTransaction:(NSString *)txArgsJSON
password:(NSString *)password
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"ApproveSignRequest() method called");
NSLog(@"SendTransaction() method called");
#endif
char * result = ApproveSignRequest((char *) [id UTF8String], (char *) [password UTF8String]);
char * result = SendTransaction((char *) [txArgsJSON UTF8String], (char *) [password UTF8String]);
callback(@[[NSString stringWithUTF8String: result]]);
}
////////////////////////////////////////////////////////////////////
#pragma mark - Approve Sign Request With Args
//////////////////////////////////////////////////////////////////// approveSignRequestWithArgs
RCT_EXPORT_METHOD(approveSignRequestWithArgs:(NSString *)id
password:(NSString *)password
gas:(NSString *)gas
gasPrice:(NSString *)gasPrice
#pragma mark - SignMessage
//////////////////////////////////////////////////////////////////// signMessage
RCT_EXPORT_METHOD(signMessage:(NSString *)message
callback:(RCTResponseSenderBlock)callback) {
#if DEBUG
NSLog(@"ApproveSignRequestWithArgs() method called");
NSLog(@"SignMessage() method called");
#endif
char * result = ApproveSignRequestWithArgs((char *) [id UTF8String], (char *) [password UTF8String], (long long) [gas longLongValue], (long long) [gasPrice longLongValue]);
char * result = SignMessage((char *) [message UTF8String]);
callback(@[[NSString stringWithUTF8String: result]]);
}
////////////////////////////////////////////////////////////////////
#pragma mark - Discard Sign Request
//////////////////////////////////////////////////////////////////// discardSignRequest
RCT_EXPORT_METHOD(discardSignRequest:(NSString *)id) {
#if DEBUG
NSLog(@"DiscardSignRequest() method called");
#endif
DiscardSignRequest((char *) [id UTF8String]);
}
////////////////////////////////////////////////////////////////////
#pragma mark - only android methods
////////////////////////////////////////////////////////////////////
@ -329,7 +316,7 @@ RCT_EXPORT_METHOD(clearStorageAPIs) {
}
}
RCT_EXPORT_METHOD(sendWeb3Request:(NSString *)payload
RCT_EXPORT_METHOD(callRPC:(NSString *)payload
callback:(RCTResponseSenderBlock)callback) {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
char * result = CallRPC((char *) [payload UTF8String]);
@ -339,7 +326,7 @@ RCT_EXPORT_METHOD(sendWeb3Request:(NSString *)payload
});
}
RCT_EXPORT_METHOD(sendWeb3PrivateRequest:(NSString *)payload
RCT_EXPORT_METHOD(callPrivateRPC:(NSString *)payload
callback:(RCTResponseSenderBlock)callback) {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
char * result = CallPrivateRPC((char *) [payload UTF8String]);

View File

@ -25,7 +25,7 @@
<artifactItem>
<groupId>status-im</groupId>
<artifactId>status-go-ios-simulator</artifactId>
<version>develop-ga6d69eba</version>
<version>tags-v0.11.0^0-g4afd9e6c-302</version>
<type>zip</type>
<overWrite>true</overWrite>
<outputDirectory>./</outputDirectory>

File diff suppressed because one or more lines are too long

View File

@ -2,27 +2,31 @@ if(typeof StatusHttpProvider === "undefined"){
var callbackId = 0;
var callbacks = {};
function httpCallback(id, data) {
var result = data;
var error = null;
WebViewBridge.onMessage = function (message) {
data = JSON.parse(message);
try {
result = JSON.parse(data);
} catch (e) {
error = {message: "InvalidResponse"};
if (data.type === "navigate-to-blank")
window.location.href = "about:blank";
else if (data.type === "status-api-success")
{
window.STATUS_API = data.data;
window.postMessage({ type: 'STATUS_API_SUCCESS', permissions: data.keys }, "*");
}
if (callbacks[id]) {
callbacks[id](error, result);
else if (data.type === "web3-send-async-callback")
{
var id = data.messageId;
if (callbacks[id]) {
callbacks[id](data.error, data.result);
}
}
}
var StatusHttpProvider = function (host, timeout) {
this.host = host || 'http://localhost:8545';
this.timeout = timeout || 0;
};
var StatusHttpProvider = function () {};
StatusHttpProvider.prototype.isStatus = true;
StatusHttpProvider.prototype.isConnected = function () { return true; };
function web3Response (payload, result){
return {id: payload.id,
@ -66,57 +70,19 @@ StatusHttpProvider.prototype.sendAsync = function (payload, callback) {
else {
var messageId = callbackId++;
callbacks[messageId] = callback;
if (typeof StatusBridge == "undefined") {
var data = {
payload: JSON.stringify(payload),
callbackId: JSON.stringify(messageId),
host: this.host
};
webkit.messageHandlers.sendRequest.postMessage(JSON.stringify(data));
} else {
StatusBridge.sendRequest(this.host, JSON.stringify(messageId), JSON.stringify(payload));
}
}
};
StatusHttpProvider.prototype.prepareRequest = function () {
var request = new XMLHttpRequest();
request.open('POST', this.host, false);
request.setRequestHeader('Content-Type', 'application/json');
return request;
};
/**
* Synchronously tries to make Http request
*
* @method isConnected
* @return {Boolean} returns true if request haven't failed. Otherwise false
*/
StatusHttpProvider.prototype.isConnected = function () {
try {
this.sendAsync({
id: 9999999999,
jsonrpc: '2.0',
method: 'net_listening',
params: []
}, function () {
});
return true;
} catch (e) {
return false;
WebViewBridge.send(JSON.stringify({type: 'web3-send-async',
messageId: messageId,
payload: payload}));
}
};
}
var protocol = window.location.protocol
var address = providerAddress || "http://localhost:8545";
console.log(protocol);
if (typeof web3 === "undefined") {
//why do we need this condition?
if (protocol == "https:" || protocol == "http:") {
console.log("StatusHttpProvider");
web3 = new Web3(new StatusHttpProvider(address));
web3.eth.defaultAccount = currentAccountAddress;
web3 = new Web3(new StatusHttpProvider());
web3.eth.defaultAccount = currentAccountAddress; // currentAccountAddress - injected from status-react
}
}

View File

@ -25,16 +25,4 @@
});
}
});
WebViewBridge.onMessage = function (message) {
data = JSON.parse(message);
if (data.type === "navigate-to-blank")
window.location.href = "about:blank";
else if (data.type === "status-api-success")
window.STATUS_API = data.data;
window.postMessage({ type: 'STATUS_API_SUCCESS', permissions: data.keys }, "*");
};
}());

View File

@ -37,9 +37,7 @@
{:type [{:id :inbound :label (i18n/label :t/incoming) :checked? true}
{:id :outbound :label (i18n/label :t/outgoing) :checked? true}
{:id :pending :label (i18n/label :t/pending) :checked? true}
{:id :failed :label (i18n/label :t/failed) :checked? true}
;; TODO(jeluard) Restore once we support postponing transaction
#_{:id :postponed :label (i18n/label :t/postponed) :checked? true}]}})
{:id :failed :label (i18n/label :t/failed) :checked? true}]}})
(def mainnet-networks
{"mainnet" {:id "mainnet",
@ -250,11 +248,11 @@
;; Used to generate topic for contact discoveries
(def contact-discovery "contact-discovery")
(def ^:const send-transaction-no-error-code "0")
(def ^:const send-transaction-default-error-code "1")
(def ^:const send-transaction-password-error-code "2")
(def ^:const send-transaction-timeout-error-code "3")
(def ^:const send-transaction-discarded-error-code "4")
(def ^:const send-transaction-failed-parse-response 1)
(def ^:const send-transaction-failed-parse-params 2)
(def ^:const send-transaction-no-account-selected 3)
(def ^:const send-transaction-invalid-tx-sender 4)
(def ^:const send-transaction-err-decrypt 5)
(def ^:const web3-send-transaction "eth_sendTransaction")
(def ^:const web3-personal-sign "personal_sign")
@ -268,4 +266,6 @@
(def ^:const dapp-permission-contact-code "CONTACT_CODE")
(def ^:const status-api-success "status-api-success")
(def ^:const status-api-request "status-api-request")
(def ^:const history-state-changed "history-state-changed")
(def ^:const history-state-changed "history-state-changed")
(def ^:const web3-send-async "web3-send-async")
(def ^:const web3-send-async-callback "web3-send-async-callback")

View File

@ -4,7 +4,8 @@
[status-im.i18n :as i18n]
[status-im.constants :as constants]
[status-im.ui.screens.browser.default-dapps :as default-dapps]
[status-im.utils.http :as http]))
[status-im.utils.http :as http]
[re-frame.core :as re-frame]))
(defn get-current-url [{:keys [history history-index]}]
(when (and history-index history)
@ -17,8 +18,8 @@
(< history-index (dec (count history))))
(defn check-if-dapp-in-list [{:keys [history history-index] :as browser}]
(let [history-host (http/url-host (try (nth history history-index) (catch js/Error _)))
dapp (first (filter #(= history-host (http/url-host (:dapp-url %))) (apply concat (mapv :data default-dapps/all))))]
(let [history-host (http/url-host (try (nth history history-index) (catch js/Error _)))
dapp (first (filter #(= history-host (http/url-host (:dapp-url %))) (apply concat (mapv :data default-dapps/all))))]
(if dapp
(assoc browser :dapp? true :name (:name dapp))
(assoc browser :dapp? false :name (i18n/label :t/browser)))))
@ -72,7 +73,10 @@
(assoc (update-dapp-permissions-fx cofx {:dapp dapp-name
:permissions (vec (set (concat (keys permissions-allowed)
user-permissions)))})
:send-to-bridge-fx [permissions-allowed webview])))
:send-to-bridge-fx [{:type constants/status-api-success
:data permissions-allowed
:keys (keys permissions-allowed)}
webview])))
(defn next-permission [cofx params & [permission permissions-data]]
(request-permission
@ -82,4 +86,16 @@
(update :index inc)
(and permission permissions-data)
(assoc-in [:permissions-allowed permission] (get permissions-data permission)))))
(assoc-in [:permissions-allowed permission] (get permissions-data permission)))))
(defn web3-send-async [{:keys [db]} {:keys [method] :as payload} message-id]
(if (or (= method constants/web3-send-transaction)
(= method constants/web3-personal-sign))
{:db (update-in db [:wallet :transactions-queue] conj {:message-id message-id :payload payload})
:dispatch [:check-dapps-transactions-queue]}
{:call-rpc [payload
#(re-frame/dispatch [:send-to-bridge
{:type constants/web3-send-async-callback
:messageId message-id
:error %1
:result %2}])]}))

View File

@ -1,5 +1,12 @@
(ns status-im.models.wallet
(:require [status-im.utils.money :as money]))
(:require [status-im.utils.money :as money]
[status-im.i18n :as i18n]
[status-im.utils.ethereum.core :as ethereum]
[status-im.constants :as constants]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.hex :as utils.hex]
[status-im.transport.utils :as transport.utils]))
(def min-gas-price-wei (money/bignumber 1))
@ -22,7 +29,7 @@
"0"))
(defn- edit-max-fee [edit]
(let [gas (get-in edit [:gas-price :value-number])
(let [gas (get-in edit [:gas-price :value-number])
gas-price (get-in edit [:gas :value-number])]
(assoc edit :max-fee (calculate-max-fee gas gas-price))))
@ -51,3 +58,114 @@
(defn edit-value
[key value {:keys [db]}]
{:db (update-in db [:wallet :edit] build-edit key value)})
;; DAPP TRANSACTION -> SEND TRANSACTION
(defn prepare-dapp-transaction [{{:keys [id method params]} :payload :as queued-transaction} contacts]
(let [{:keys [to value data gas gasPrice nonce]} (first params)
contact (get contacts (utils.hex/normalize-hex to))]
(cond-> {:id (str id)
:to-name (or (when (nil? to)
(i18n/label :t/new-contract))
contact)
:symbol :ETH
:method method
:dapp-transaction queued-transaction
:to to
:amount (money/bignumber (or value 0))
:gas (cond
gas
(money/bignumber gas)
value
(money/bignumber 21000))
:gas-price (when gasPrice
(money/bignumber gasPrice))
:data data}
nonce
(assoc :nonce nonce))))
;; SEND TRANSACTION -> RPC TRANSACTION
(defn prepare-send-transaction [from {:keys [amount to gas gas-price data nonce]}]
(cond-> {:from (ethereum/normalized-address from)
:to (ethereum/normalized-address to)
:value (ethereum/int->hex amount)
:gas (ethereum/int->hex gas)
:gas-price (ethereum/int->hex gas-price)}
data
(assoc :data data)
nonce
(assoc :nonce nonce)))
;; NOTE (andrey) we need this function, because params may be mixed up, so we need to figure out which one is address
;; and which message
(defn normalize-sign-message-params [params]
(let [first_param (first params)
second_param (second params)
first-param-address? (ethereum/address? first_param)
second-param-address? (ethereum/address? second_param)]
(when (or first-param-address? second-param-address?)
(if first-param-address?
[first_param second_param]
[second_param first_param]))))
(defn web3-error-callback [fx {:keys [webview-bridge]} {:keys [message-id]} message]
(assoc fx :send-to-bridge-fx [{:type constants/web3-send-async-callback
:messageId message-id
:error message}
webview-bridge]))
(defn dapp-complete-transaction [id result method message-id webview]
(cond-> {:send-to-bridge-fx [{:type constants/web3-send-async-callback
:messageId message-id
:result {:jsonrpc "2.0"
:id (int id)
:result result}}
webview]
:dispatch [:navigate-back]}
(= method constants/web3-send-transaction)
(assoc :dispatch-later [{:ms 400 :dispatch [:navigate-to-modal :wallet-transaction-sent-modal]}])))
(defn discard-transaction
[{:keys [db]}]
(let [{:keys [dapp-transaction]} (get-in db [:wallet :send-transaction])]
(cond-> {:db (assoc-in db [:wallet :send-transaction] {})}
dapp-transaction
(web3-error-callback db dapp-transaction "discarded"))))
(defn prepare-unconfirmed-transaction [db now hash]
(let [transaction (get-in db [:wallet :send-transaction])]
(let [chain (:chain db)
token (tokens/symbol->token (keyword chain) (:symbol transaction))]
(-> transaction
(assoc :confirmations "0"
:timestamp (str now)
:type :outbound
:hash hash)
(update :gas-price str)
(assoc :value (:amount transaction))
(assoc :token token)
(update :gas str)
(dissoc :message-id :id)))))
(defn handle-transaction-error [db {:keys [code message]}]
(let [{:keys [dapp-transaction]} (get-in db [:wallet :send-transaction])]
(case code
;;WRONG PASSWORD
constants/send-transaction-err-decrypt
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] true))}
(cond-> {:db (-> db
navigation/navigate-back
(assoc-in [:wallet :transactions-queue] nil)
(assoc-in [:wallet :send-transaction] {}))
:wallet/show-transaction-error message}
dapp-transaction
(web3-error-callback db dapp-transaction message)))))
(defn transform-data-for-message [{:keys [method] :as transaction}]
(cond-> transaction
(= method constants/web3-personal-sign)
(update :data transport.utils/to-utf8)))

View File

@ -3,9 +3,6 @@
(def adjust-resize 16)
(defn move-to-internal-storage [callback]
(native-module/move-to-internal-storage callback))
(defn start-node [config]
(native-module/start-node config))
@ -21,33 +18,27 @@
(defn login [address password callback]
(native-module/login address password callback))
(defn approve-sign-request [id password callback]
(native-module/approve-sign-request id password callback))
(defn approve-sign-request-with-args [id password gas gas-price callback]
(native-module/approve-sign-request-with-args id password gas gas-price callback))
(defn discard-sign-request [id]
(native-module/discard-sign-request id))
(defn set-soft-input-mode [mode]
(native-module/set-soft-input-mode mode))
(defn clear-web-data []
(native-module/clear-web-data))
(defn call-web3 [payload callback]
(native-module/call-web3 payload callback))
(defn call-rpc [payload callback]
(native-module/call-rpc payload callback))
(defn call-web3-private [payload callback]
(native-module/call-web3-private payload callback))
(defn call-private-rpc [payload callback]
(native-module/call-private-rpc payload callback))
(defn sign-message [rpcParams callback]
(native-module/sign-message rpcParams callback))
(defn send-transaction [rpcParams password callback]
(native-module/send-transaction rpcParams password callback))
(defn module-initialized! []
(native-module/module-initialized!))
(defn should-move-to-internal-storage? [callback]
(native-module/should-move-to-internal-storage? callback))
(defn notify-users [m callback]
(native-module/notify-users m callback))

View File

@ -54,14 +54,6 @@
(.addListener r/device-event-emitter "gethEvent"
#(re-frame/dispatch [:signal-event (.-jsonEvent %)])))
(defn should-move-to-internal-storage? [on-result]
(when status
(call-module #(.shouldMoveToInternalStorage status on-result))))
(defn move-to-internal-storage [on-result]
(when status
(call-module #(.moveToInternalStorage status on-result))))
(defn stop-node []
(when status
(call-module #(.stopNode status))))
@ -101,27 +93,6 @@
(when status
(call-module #(.login status address password on-result))))
(defn approve-sign-request
[id password callback]
(log/debug :approve-sign-request (boolean status) id)
(when status
(call-module #(.approveSignRequest status id password callback))))
(defn approve-sign-request-with-args
[id password gas gas-price callback]
(log/debug :approve-sign-request-with-args (boolean status) id gas gas-price)
(when status
(call-module #(.approveSignRequestWithArgs status id password gas gas-price callback))))
(defn discard-sign-request
[id]
(log/debug :discard-sign-request id)
(when status
(call-module #(.discardSignRequest status id))))
(defn- append-catalog-init [js]
(str js "\n" "var catalog = JSON.stringify(_status_catalog); catalog;"))
(defn set-soft-input-mode [mode]
(when status
(call-module #(.setSoftInputMode status mode))))
@ -131,13 +102,21 @@
(call-module #(.clearCookies status))
(call-module #(.clearStorageAPIs status))))
(defn call-web3 [payload callback]
(defn call-rpc [payload callback]
(when status
(call-module #(.sendWeb3Request status payload callback))))
(call-module #(.callRPC status payload callback))))
(defn call-web3-private [payload callback]
(defn call-private-rpc [payload callback]
(when status
(call-module #(.sendWeb3PrivateRequest status payload callback))))
(call-module #(.callPrivateRPC status payload callback))))
(defn sign-message [rpcParams callback]
(when status
(call-module #(.signMessage status rpcParams callback))))
(defn send-transaction [rpcParams password callback]
(when status
(call-module #(.sendTransaction status rpcParams password callback))))
(defn close-application []
(.closeApplication status))

View File

@ -11,7 +11,10 @@
[status-im.models.browser :as model]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils]
[status-im.constants :as constants]))
[status-im.constants :as constants]
[status-im.native-module.core :as status]
[taoensso.timbre :as log]
[status-im.utils.types :as types]))
(re-frame/reg-fx
:browse
@ -20,12 +23,22 @@
(utils.universal-links/open! link)
(list-selection/browse link))))
(re-frame/reg-fx
:call-rpc
(fn [[payload callback]]
(status/call-rpc
(types/clj->json payload)
(fn [response]
(if (= "" response)
(do
(log/warn :web3-response-error)
(callback "web3-response-error" nil))
(callback nil (.parse js/JSON response)))))))
(re-frame/reg-fx
:send-to-bridge-fx
(fn [[permissions-allowed webview]]
(.sendToBridge @webview (.stringify js/JSON (clj->js {:type constants/status-api-success
:data permissions-allowed
:keys (keys permissions-allowed)})))))
(fn [[message webview]]
(.sendToBridge webview (types/clj->json message))))
(re-frame/reg-fx
:show-dapp-permission-confirmation-fx
@ -67,6 +80,12 @@
:history-index 0
:history [normalized-url]}))))
(handlers/register-handler-fx
:send-to-bridge
[re-frame/trim-v]
(fn [cofx [message]]
{:send-to-bridge-fx [message (get-in cofx [:db :webview-bridge])]}))
(handlers/register-handler-fx
:open-browser
[re-frame/trim-v]
@ -112,24 +131,32 @@
(handlers/register-handler-fx
:on-bridge-message
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [{{:keys [url]} :navState :keys [type host permissions]} browser webview]]
(cond
(fn [{:keys [db] :as cofx} [message]]
(let [{:browser/keys [options browsers] :keys [webview-bridge]} db
{:keys [browser-id]} options
browser (get browsers browser-id)
data (types/json->clj message)
{{:keys [url]} :navState :keys [type host permissions payload messageId]} data]
(cond
(and (= type constants/history-state-changed) platform/ios? (not= "about:blank" url))
(model/update-browser-history-fx cofx browser url false)
(and (= type constants/history-state-changed) platform/ios? (not= "about:blank" url))
(model/update-browser-history-fx cofx browser url false)
(= type constants/status-api-request)
(let [{:account/keys [account]} db
{:keys [dapp? name]} browser
dapp-name (if dapp? name host)]
(model/request-permission
cofx
{:dapp-name dapp-name
:webview webview
:index 0
:user-permissions (get-in db [:dapps/permissions dapp-name :permissions])
:requested-permissions permissions
:permissions-data {constants/dapp-permission-contact-code (:public-key account)}})))))
(= type constants/web3-send-async)
(model/web3-send-async cofx payload messageId)
(= type constants/status-api-request)
(let [{:account/keys [account]} db
{:keys [dapp? name]} browser
dapp-name (if dapp? name host)]
(model/request-permission
cofx
{:dapp-name dapp-name
:webview webview-bridge
:index 0
:user-permissions (get-in db [:dapps/permissions dapp-name :permissions])
:requested-permissions permissions
:permissions-data {constants/dapp-permission-contact-code (:public-key account)}}))))))
(handlers/register-handler-fx
:next-dapp-permission

View File

@ -103,18 +103,16 @@
[components.webview-bridge/webview-bridge
{:dapp? dapp?
:dapp-name name
:ref #(reset! webview %)
:ref #(do
(reset! webview %)
(re-frame/dispatch [:set :webview-bridge %]))
:source {:uri url}
:java-script-enabled true
:bounces false
:local-storage-enabled true
:render-error web-view-error
:on-navigation-state-change #(on-navigation-change % browser)
:on-bridge-message #(re-frame/dispatch [:on-bridge-message
(js->clj (.parse js/JSON %)
:keywordize-keys true)
browser
webview])
:on-bridge-message #(re-frame/dispatch [:on-bridge-message %])
:on-load #(re-frame/dispatch [:update-browser-options {:error? false}])
:on-error #(re-frame/dispatch [:update-browser-options {:error? true
:loading? false}])

View File

@ -364,11 +364,6 @@
(re-frame/dispatch [:fetch-web3-node-version-callback resp])))))
nil))
(handlers/register-handler-fx
:webview-geo-permissions-granted
(fn [{{:keys [webview-bridge]} :db} _]
(.geoPermissionsGranted webview-bridge)))
(handlers/register-handler-fx
:get-fcm-token
(fn [_ _]
@ -393,8 +388,6 @@
(instabug/log (str "Signal event: " event-str))
(let [{:keys [type event]} (types/json->clj event-str)
to-dispatch (case type
"sign-request.queued" [:sign-request-queued event]
"sign-request.failed" [:sign-request-failed event]
"node.started" [:status-node-started]
"node.stopped" [:status-node-stopped]
"module.initialized" [:status-module-initialized]

View File

@ -28,11 +28,6 @@
(fn [db [_ path]]
(get-in db path)))
(reg-sub :signed-up?
:<- [:get-current-account]
(fn [current-account]
(:signed-up? current-account)))
(reg-sub :network
:<- [:get-current-account]
(fn [current-account]

View File

@ -32,10 +32,10 @@
[status-im.ui.screens.wallet.request.views :refer [request-transaction send-transaction-request]]
[status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.onboarding.setup.views :as wallet.onboarding.setup]
[status-im.ui.screens.wallet.send.views :as wallet.send]
[status-im.ui.screens.wallet.transaction-fee.views :as wallet.transaction-fee]
[status-im.ui.screens.wallet.settings.views :as wallet-settings]
[status-im.ui.screens.wallet.transactions.views :as wallet-transactions]
[status-im.ui.screens.wallet.send.transaction-sent.views :refer [transaction-sent transaction-sent-modal]]
[status-im.ui.screens.wallet.transaction-sent.views :refer [transaction-sent transaction-sent-modal]]
[status-im.ui.screens.wallet.components.views :refer [contact-code recent-recipients recipient-qr-code]]
[status-im.ui.screens.network-settings.views :refer [network-settings]]
[status-im.ui.screens.network-settings.network-details.views :refer [network-details]]
@ -110,7 +110,7 @@
:wallet-send-transaction-modal send-transaction-modal
:wallet-transaction-sent-modal transaction-sent-modal
:wallet-sign-message-modal sign-message-modal
:wallet-transaction-fee wallet.send/transaction-fee
:wallet-transaction-fee wallet.transaction-fee/transaction-fee
:wallet-onboarding-setup-modal wallet.onboarding.setup/modal
[react/view [react/text (str "Unknown modal view: " modal-view)]]))
@ -135,8 +135,7 @@
[component]])]])))
(defview main []
(letsubs [signed-up? [:signed-up?]
view-id [:get :view-id]]
(letsubs [view-id [:get :view-id]]
{:component-did-mount utils.universal-links/initialize
:component-will-unmount utils.universal-links/finalize
:component-will-update (fn [] (react/dismiss-keyboard!))}

View File

@ -1,4 +1,5 @@
(ns status-im.ui.screens.wallet.db
(:require-macros [status-im.utils.db :refer [allowed-keys]])
(:require [cljs.spec.alpha :as spec]
[status-im.i18n :as i18n]
status-im.ui.screens.wallet.request.db
@ -7,14 +8,36 @@
(spec/def :wallet.send/recipient string?)
(spec/def :wallet/send (spec/keys :req-un [:wallet.send/recipient]))
(spec/def :wallet/send (allowed-keys :req-un [:wallet.send/recipient]))
(spec/def :wallet/wallet (spec/keys :opt-un [:wallet/send-transaction :wallet/request-transaction
:wallet/transactions-queue]))
;; Placeholder namespace for wallet specs, which are a WIP depending on data
;; model we decide on for balances, prices, etc.
(spec/def :wallet/balance-loading? (spec/nilable boolean?))
(spec/def :wallet/transactions-loading? (spec/nilable boolean?))
(spec/def :wallet/transactions-sync-started? (spec/nilable boolean?))
(spec/def :wallet/errors (spec/nilable any?))
(spec/def :wallet/transactions-last-updated-at (spec/nilable any?))
(spec/def :wallet/chat-transactions (spec/nilable any?))
(spec/def :wallet/transactions (spec/nilable any?))
(spec/def :wallet/transactions-queue (spec/nilable any?))
(spec/def :wallet/edit (spec/nilable any?))
(spec/def :wallet/current-tab (spec/nilable any?))
(spec/def :wallet/current-transaction (spec/nilable any?))
(spec/def :wallet/modal-history? (spec/nilable any?))
(spec/def :wallet/visible-tokens (spec/nilable any?))
(spec/def :wallet/currency (spec/nilable any?))
(spec/def :wallet/balance (spec/nilable any?))
(spec/def :wallet/wallet (allowed-keys :opt-un [:wallet/send-transaction :wallet/request-transaction
:wallet/transactions-queue
:wallet/balance-loading? :wallet/errors :wallet/transactions-loading?
:wallet/transactions-last-updated-at :wallet/chat-transactions
:wallet/transactions-sync-started? :wallet/transactions
:wallet/edit
:wallet/current-tab
:wallet/current-transaction
:wallet/modal-history?
:wallet/visible-tokens
:wallet/currency
:wallet/balance]))
(defn- too-precise-amount?
"Checks if number has any extra digit beyond the allowed number of decimals.

View File

@ -1,7 +1,8 @@
(ns status-im.ui.screens.wallet.navigation
(:require [re-frame.core :as re-frame]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.core :as ethereum]))
[status-im.utils.ethereum.core :as ethereum]
[status-im.constants :as constants]))
(defmethod navigation/preload-data! :wallet
[db _]
@ -22,6 +23,7 @@
(def transaction-send-default
(let [symbol :ETH]
{:gas (ethereum/estimate-gas symbol)
:method constants/web3-send-transaction
:symbol symbol}))
(def transaction-request-default

View File

@ -4,8 +4,16 @@
[status-im.utils.money :as money]
[status-im.utils.security :as security]))
(spec/def ::amount (spec/nilable money/valid?))
; transaction
(spec/def ::from (spec/nilable string?))
(spec/def ::to (spec/nilable string?))
(spec/def ::amount (spec/nilable money/valid?))
(spec/def ::gas (spec/nilable money/valid?))
(spec/def ::gas-price (spec/nilable money/valid?))
; dapp transaction
(spec/def ::data (spec/nilable string?))
(spec/def ::nonce (spec/nilable money/valid?))
(spec/def ::to-name (spec/nilable string?))
(spec/def ::amount-error (spec/nilable string?))
(spec/def ::asset-error (spec/nilable string?))
@ -13,25 +21,22 @@
(spec/def ::password (spec/nilable #(instance? security/MaskedData %)))
(spec/def ::wrong-password? (spec/nilable boolean?))
(spec/def ::id (spec/nilable string?))
(spec/def ::waiting-signal? (spec/nilable boolean?))
(spec/def ::signing? (spec/nilable boolean?))
(spec/def ::later? (spec/nilable boolean?))
(spec/def ::show-password-input? (spec/nilable boolean?))
(spec/def ::height double?)
(spec/def ::width double?)
(spec/def ::camera-flashlight #{:on :off})
(spec/def ::in-progress? boolean?)
(spec/def ::from-chat? (spec/nilable boolean?))
(spec/def ::symbol (spec/nilable keyword?))
(spec/def ::gas (spec/nilable money/valid?))
(spec/def ::gas-price (spec/nilable money/valid?))
(spec/def ::advanced? boolean?)
(spec/def ::whisper-identity (spec/nilable string?))
(spec/def ::method (spec/nilable string?))
(spec/def ::tx-hash (spec/nilable string?))
(spec/def ::dapp-transaction (spec/nilable any?))
(spec/def :wallet/send-transaction (allowed-keys
:opt-un [::amount ::to ::to-name ::amount-error ::asset-error ::amount-text ::password
::waiting-signal? ::signing? ::id ::later?
::camera-flashlight ::in-progress?
:opt-un [::amount ::to ::to-name ::amount-error ::asset-error ::amount-text
::password ::show-password-input? ::id ::from ::data
::camera-flashlight ::in-progress? ::dapp-transaction
::wrong-password? ::from-chat? ::symbol ::advanced?
::gas ::gas-price ::whisper-identity ::method ::tx-hash]))

View File

@ -1,16 +1,13 @@
(ns status-im.ui.screens.wallet.send.events
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.native-module.core :as status]
[status-im.ui.screens.db :as db]
[status-im.ui.screens.wallet.db :as wallet.db]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.erc20 :as erc20]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.hex :as utils.hex]
[status-im.utils.money :as money]
[status-im.utils.security :as security]
[status-im.utils.types :as types]
@ -19,71 +16,154 @@
[status-im.chat.models.message :as models.message]
[status-im.chat.commands.sending :as commands-sending]
[status-im.constants :as constants]
[status-im.transport.utils :as transport.utils]
[taoensso.timbre :as log]
[status-im.ui.screens.navigation :as navigation]
[status-im.wallet.transactions :as wallet.transactions]))
;;;; FX
(re-frame/reg-fx
::accept-transaction
(fn [{:keys [masked-password id on-completed]}]
;; unmasking the password as late as possible to avoid being exposed from app-db
(status/approve-sign-request id
(security/unmask masked-password)
on-completed)))
(defn- send-ethers [params on-completed masked-password]
(status/send-transaction (types/clj->json params)
(security/unmask masked-password)
on-completed))
(re-frame/reg-fx
::accept-transaction-with-changed-gas
(fn [{:keys [masked-password id on-completed gas gas-price default-gas-price]}]
;; unmasking the password as late as possible to avoid being exposed from app-db
(if gas
(status/approve-sign-request-with-args id
(security/unmask masked-password)
(money/to-fixed gas)
(money/to-fixed (or gas-price default-gas-price))
on-completed)
(status/approve-sign-request id
(security/unmask masked-password)
on-completed))))
(defn- send-ethers [{:keys [web3 from to value gas gas-price]}]
(.sendTransaction (.-eth web3)
(clj->js {:from from :to to :value value :gas gas :gasPrice gas-price})
#()))
(defn- send-tokens [{:keys [web3 from to value gas gas-price symbol chain]}]
(defn- send-tokens [symbol chain {:keys [from to value gas gas-price]} on-completed masked-password]
(let [contract (:address (tokens/symbol->token (keyword chain) symbol))]
(erc20/transfer web3 contract from to value {:gas gas :gasPrice gas-price} #())))
(erc20/transfer contract from to value gas gas-price masked-password on-completed)))
(re-frame/reg-fx
::send-transaction
(fn [{:keys [symbol] :as params}]
(fn [[params symbol chain on-completed masked-password]]
(case symbol
:ETH (send-ethers params)
(send-tokens params))))
:ETH (send-ethers params on-completed masked-password)
(send-tokens symbol chain params on-completed masked-password))))
(re-frame/reg-fx
::show-transaction-error
::sign-message
(fn [{:keys [params on-completed]}]
(status/sign-message (types/clj->json params)
on-completed)))
(re-frame/reg-fx
:wallet/show-transaction-error
(fn [message]
;; (andrey) we need this timeout because modal window conflicts with alert
;; (andrey) we need this timeout because modal window conflicts with alert
(utils/set-timeout #(utils/show-popup (i18n/label :t/transaction-failed) message) 1000)))
(re-frame/reg-fx
:discard-transaction
(fn [id]
(status/discard-sign-request id)))
;;Helper functions
(defn transaction-valid? [{{:keys [to data]} :args}]
(or (and to (utils.hex/valid-hex? to)) (and data (not= data "0x"))))
(defn dispatch-transaction-completed [result & [modal?]]
(re-frame/dispatch [::transaction-completed {:id (:id result) :response result} modal?]))
;;;; Handlers
;; SEND TRANSACTION
(handlers/register-handler-fx
:wallet/send-transaction
(fn [{{:keys [chain] :as db} :db} _]
(let [{:keys [password symbol in-progress?] :as transaction} (get-in db [:wallet :send-transaction])
from (get-in db [:account/account :address])]
(when-not in-progress?
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] false)
(assoc-in [:wallet :send-transaction :in-progress?] true))
::send-transaction [(models.wallet/prepare-send-transaction from transaction)
symbol
chain
#(re-frame/dispatch [::transaction-completed (types/json->clj %)])
password]}))))
;; SIGN MESSAGE
(handlers/register-handler-fx
:wallet/sign-message
(fn [{db :db} _]
(let [{:keys [data from password]} (get-in db [:wallet :send-transaction])]
{:db (assoc-in db [:wallet :send-transaction :in-progress?] true)
::sign-message {:params {:data data
:password (security/unmask password)
:account from}
:on-completed #(re-frame/dispatch [::transaction-completed (types/json->clj %)])}})))
;; SEND TRANSACTION (SIGN MESSAGE) CALLBACK
(handlers/register-handler-fx
::transaction-completed
(fn [{:keys [db now]} [_ {:keys [result error]}]]
(let [{:keys [id method whisper-identity to symbol amount-text dapp-transaction]} (get-in db [:wallet :send-transaction])
db' (assoc-in db [:wallet :send-transaction :in-progress?] false)]
(if error
;; ERROR
(models.wallet/handle-transaction-error db' error)
;; RESULT
(merge
{:db (cond-> (assoc-in db' [:wallet :send-transaction] {})
(not= method constants/web3-personal-sign)
(assoc-in [:wallet :transactions result]
(models.wallet/prepare-unconfirmed-transaction db now result)))}
(if dapp-transaction
(let [{:keys [message-id]} dapp-transaction
webview (:webview-bridge db)]
(models.wallet/dapp-complete-transaction (int id) result method message-id webview))
{:dispatch [:send-transaction-message whisper-identity {:address to
:asset (name symbol)
:amount amount-text
:tx-hash result}]}))))))
;; DISCARD TRANSACTION
(handlers/register-handler-fx
:wallet/discard-transaction
(fn [cofx _]
(models.wallet/discard-transaction cofx)))
;; DAPP TRANSACTIONS QUEUE
;; NOTE(andrey) We need this queue because dapp can send several transactions in a row, this is bad behaviour
;; but we need to support it
(handlers/register-handler-fx
:check-dapps-transactions-queue
(fn [{:keys [db]} _]
(let [{:keys [send-transaction transactions-queue]} (:wallet db)
{:keys [payload message-id] :as queued-transaction} (last transactions-queue)
{:keys [method params]} payload
db' (update-in db [:wallet :transactions-queue] drop-last)]
(when (and (not (:id send-transaction)) queued-transaction)
(cond
;;SEND TRANSACTION
(= method constants/web3-send-transaction)
(let [{:keys [gas gasPrice] :as transaction} (models.wallet/prepare-dapp-transaction
queued-transaction (:contacts/contacts db))
{:keys [wallet-set-up-passed?]} (:account/account db)]
{:db (assoc-in db' [:wallet :send-transaction] transaction)
:dispatch-n [[:update-wallet]
(when-not gas
[:wallet/update-estimated-gas (first params)])
(when-not gasPrice
[:wallet/update-gas-price])
[:navigate-to-modal (if wallet-set-up-passed?
:wallet-send-transaction-modal
:wallet-onboarding-setup-modal)]]})
;;SIGN MESSAGE
(= method constants/web3-personal-sign)
(let [[address data] (models.wallet/normalize-sign-message-params params)]
(if (and address data)
{:db (assoc-in db' [:wallet :send-transaction] {:id (str message-id)
:from address
:data data
:dapp-transaction queued-transaction
:method method})
:dispatch [:navigate-to-modal :wallet-sign-message-modal]}
{:db db'})))))))
(handlers/register-handler-fx
:send-transaction-message
(concat models.message/send-interceptors
navigation/navigation-interceptors)
(fn [{:keys [db] :as cofx} [chat-id params]]
;;NOTE(goranjovic): we want to send the payment message only when we have a whisper id
;; for the recipient, we always redirect to `:wallet-transaction-sent` even when we don't
(if-let [send-command (and chat-id (get-in db [:id->command ["send" #{:personal-chats}]]))]
(handlers-macro/merge-fx cofx
(commands-sending/send chat-id send-command params)
(navigation/replace-view :wallet-transaction-sent))
(handlers-macro/merge-fx cofx
(navigation/replace-view :wallet-transaction-sent)))))
(defn set-and-validate-amount-db [db amount symbol decimals]
(let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
(-> db
@ -96,6 +176,13 @@
(fn [{:keys [db]} [_ amount symbol decimals]]
{:db (set-and-validate-amount-db db amount symbol decimals)}))
(handlers/register-handler-fx
:wallet/discard-transaction-navigate-back
(fn [cofx _]
(-> cofx
models.wallet/discard-transaction
(assoc :dispatch [:navigate-back]))))
(defn update-gas-price
([db edit? success-event]
{:update-gas-price {:web3 (:web3 db)
@ -130,305 +217,11 @@
(fn [{:keys [db]} [_ advanced?]]
{:db (assoc-in db [:wallet :send-transaction :advanced?] advanced?)}))
(def ^:private clear-send-properties {:id nil
:signing? false
:wrong-password? false
:waiting-signal? false
:from-chat? false
:in-progress? false
:password nil})
;; TODO(goranjovic) - this is a temporary workaround because in regular `clear-send-properties` we need `:from-chat?`
;; to be false to avoid the wallet onboarding crash if the user accessed transactions from dapps first.
;; On the other hand, if we reset the same flag to false every time we clear the current transaction that would also
;; happen when user clicks Cancel in chat initiated transaction and then all fields would become editable, which is
;; another bug.
;; This temporary workaround resets all fields except the flag if we are in a chat /send transaction.
;; A permanent solution would be to add onboarding check to dapp transaction and/or review the workflow.
(def ^:private partial-clear-send-properties {:id nil
:signing? false
:wrong-password? false
:waiting-signal? false
:in-progress? false
:password nil})
(defn on-transactions-completed [raw-results]
(let [result (types/json->clj raw-results)]
(dispatch-transaction-completed result)))
(defn prepare-transaction [{:keys [id message_id method args]} now]
;;NOTE(goranjovic): the transactions started from chat using /send command
;; are only in ether, so this parameter defaults to ETH
(let [{:keys [from to value symbol data gas gasPrice] :or {symbol :ETH}} args]
{:id id
:from from
:to to
:to-name (when (nil? to)
(i18n/label :t/new-contract))
:symbol symbol
:method method
:value (money/bignumber (or value 0))
:data data
:gas (when (seq gas)
(money/bignumber (money/to-decimal gas)))
:gas-price (when (seq gasPrice)
(money/bignumber (money/to-decimal gasPrice)))
:timestamp now
:message-id message_id}))
;;TRANSACTION QUEUED signal from status-go
(handlers/register-handler-fx
:sign-request-queued
(fn [{:keys [db]} [_ transaction]]
{:db (update-in db [:wallet :transactions-queue] conj transaction)
:dispatch [:check-transactions-queue]}))
(handlers/register-handler-fx
:check-transactions-queue
(fn [{:keys [db now]} _]
(let [{:keys [send-transaction transactions-queue]} (:wallet db)
{:keys [id method args] :as queued-transaction} (last transactions-queue)
db' (update-in db [:wallet :transactions-queue] drop-last)]
(when (and (not (:id send-transaction)) queued-transaction)
(cond
;;SEND TRANSACTION
(= method constants/web3-send-transaction)
(let [{:keys [gas gasPrice]} args
transaction (prepare-transaction queued-transaction now)
sending-from-dapp? (not (get-in db [:wallet :send-transaction :waiting-signal?]))
new-db (assoc-in db' [:wallet :transactions-unsigned id] transaction)
sender-account (:account/account db)
sending-db {:id id
:method method
:from-chat? (or sending-from-dapp? ;;TODO(goranjovic): figure out why we need to
;; have from-chat? flag for dapp txs and get rid of this
(get-in db [:wallet :send-transaction :from-chat?]))}]
(if sending-from-dapp?
;;SENDING FROM DAPP
{:db (assoc-in new-db [:wallet :send-transaction] sending-db) ; we need to completely reset sending state here
:dispatch-n [[:update-wallet]
[:navigate-to-modal (if (:wallet-set-up-passed? sender-account)
:wallet-send-transaction-modal
:wallet-onboarding-setup-modal)]
(when-not (seq gas)
[:wallet/update-estimated-gas transaction])
(when-not (seq gasPrice)
[:wallet/update-gas-price])]}
;;WALLET SEND SCREEN WAITING SIGNAL
(let [{:keys [password]} (get-in db [:wallet :send-transaction])
new-db' (update-in new-db [:wallet :send-transaction] merge sending-db)] ; just update sending state as we are in wallet flow
{:db new-db'
::accept-transaction {:id id
:masked-password password
:on-completed on-transactions-completed}})))
;;SIGN MESSAGE
(= method constants/web3-personal-sign)
(let [{:keys [data]} args
data' (transport.utils/to-utf8 data)]
(if data'
{:db (-> db'
(assoc-in [:wallet :transactions-unsigned id] {:data data' :id id})
(assoc-in [:wallet :send-transaction] {:id id :method method}))
:dispatch [:navigate-to-modal :wallet-sign-message-modal]}
{:db db'})))))))
(defn this-transaction-signing? [id signing-id view-id modal]
(and (= signing-id id)
(or (= view-id :wallet-send-transaction)
(= view-id :wallet-send-transaction-chat)
(= modal :wallet-send-transaction-modal)
(= modal :wallet-sign-message-modal))))
(defn handle-failed-tx [cofx error_message]
(-> cofx
(assoc ::show-transaction-error error_message)
(update-in [:db :wallet] dissoc :send-transaction)))
;;TRANSACTION FAILED signal from status-go
(handlers/register-handler-fx
:sign-request-failed
(fn [{{:keys [view-id modal] :as db} :db} [_ {:keys [id method error_code error_message]}]]
(let [send-transaction (get-in db [:wallet :send-transaction])]
(case error_code
;;WRONG PASSWORD
constants/send-transaction-password-error-code
{:db (-> db
(assoc-in [:wallet :send-transaction :wrong-password?] true)
(assoc-in [:wallet :send-transaction :waiting-signal?] false))}
;;NO ERROR, DISCARDED, TIMEOUT or DEFAULT ERROR
(if (this-transaction-signing? id (:id send-transaction) view-id modal)
(cond-> {:db (-> db
navigation/navigate-back
(assoc-in [:wallet :transactions-queue] nil)
(update-in [:wallet :transactions-unsigned] dissoc id)
(update-in [:wallet :send-transaction] merge clear-send-properties))}
(= method constants/web3-send-transaction)
(handle-failed-tx error_message))
{:db (update-in db [:wallet :transactions-unsigned] dissoc id)})))))
(defn prepare-unconfirmed-dapp-transaction [now hash transaction]
(-> transaction
(assoc :confirmations "0"
:timestamp (str now)
:type :outbound
:hash hash)
(update :gas-price str)
(update :value str)
(update :gas str)
(dissoc :message-id :id)))
(defn prepare-unconfirmed-status-transaction [db now hash transaction]
(let [chain (:chain db)
token (tokens/symbol->token (keyword chain) (:symbol transaction))]
(-> transaction
(assoc :confirmations "0"
:timestamp (str now)
:type :outbound
:hash hash)
(update :gas-price str)
(assoc :value (:amount transaction))
(assoc :token token)
(update :gas str)
(dissoc :message-id :id))))
(defn prepare-unconfirmed-transaction [db now hash id]
(let [unsigned-transaction (get-in db [:wallet :transactions-unsigned id])
send-transaction (get-in db [:wallet :send-transaction])]
;;TODO(goranjovic) - unify `send-transaction` with transactions-unsigned`
;; currently the latter is only used for transactions initiated from dapps
(if-not (:symbol send-transaction)
(prepare-unconfirmed-dapp-transaction now hash unsigned-transaction)
(prepare-unconfirmed-status-transaction db now hash send-transaction))))
(handlers/register-handler-fx
:send-transaction-message
(concat models.message/send-interceptors
navigation/navigation-interceptors)
(fn [{:keys [db] :as cofx} [chat-id params]]
;;NOTE(goranjovic): we want to send the payment message only when we have a whisper id
;; for the recipient, we always redirect to `:wallet-transaction-sent` even when we don't
(if-let [send-command (and chat-id (get-in db [:id->command ["send" #{:personal-chats}]]))]
(handlers-macro/merge-fx cofx
(commands-sending/send chat-id send-command params)
(navigation/replace-view :wallet-transaction-sent))
(handlers-macro/merge-fx cofx
(navigation/replace-view :wallet-transaction-sent)))))
(handlers/register-handler-fx
::transaction-completed
(fn [{db :db now :now} [_ {:keys [id response] :as params} modal?]]
(let [{:keys [hash error]} response
{:keys [method whisper-identity to symbol amount-text]} (get-in db [:wallet :send-transaction])
db' (assoc-in db [:wallet :send-transaction :in-progress?] false)]
(if (and error (string? error) (not (string/blank? error))) ;; ignore error here, error will be handled in :transaction-failed
{:db db'}
(merge
{:db (cond-> db'
(= method constants/web3-send-transaction)
(assoc-in [:wallet :transactions hash] (prepare-unconfirmed-transaction db now hash id))
true
(update-in [:wallet :transactions-unsigned] dissoc id)
true
(update-in [:wallet :send-transaction] merge clear-send-properties {:tx-hash hash}))}
(if modal?
(cond-> {:dispatch [:navigate-back]}
(= method constants/web3-send-transaction)
(assoc :dispatch-later [{:ms 400 :dispatch [:navigate-to-modal :wallet-transaction-sent-modal]}]))
{:dispatch [:send-transaction-message whisper-identity {:address to
:asset (name symbol)
:amount amount-text
:tx-hash hash}]}))))))
(defn on-transactions-modal-completed [raw-results]
(let [result (types/json->clj raw-results)]
(dispatch-transaction-completed result true)))
(handlers/register-handler-fx
:wallet/sign-transaction
(fn [{{:keys [web3 chain] :as db} :db} [_ later?]]
(let [db' (assoc-in db [:wallet :send-transaction :wrong-password?] false)
{:keys [amount id password to symbol method gas gas-price]} (get-in db [:wallet :send-transaction])]
(if id
{::accept-transaction {:id id
:masked-password password
:on-completed on-transactions-completed}
:db (assoc-in db' [:wallet :send-transaction :in-progress?] true)}
{:db (update-in db' [:wallet :send-transaction] assoc
:waiting-signal? true
:later? later?
:in-progress? true)
::send-transaction {:web3 web3
:from (get-in db [:account/account :address])
:to to
:value amount
:gas gas
:gas-price gas-price
:symbol symbol
:method method
:chain chain}}))))
(handlers/register-handler-fx
:wallet/sign-message-modal
(fn [{db :db} _]
(let [{:keys [id password]} (get-in db [:wallet :send-transaction])]
{:db (assoc-in db [:wallet :send-transaction :in-progress?] true)
::accept-transaction {:id id
:masked-password password
:on-completed on-transactions-modal-completed}})))
(defn sign-transaction-modal [{:keys [db]} default-gas-price]
;;TODO(goranjovic) - unify send-transaction and unsigned-transaction
(let [{:keys [id password] :as send-transaction} (get-in db [:wallet :send-transaction])
{:keys [gas gas-price]} [:wallet.send/unsigned-transaction]]
{:db (assoc-in db [:wallet :send-transaction :in-progress?] true)
::accept-transaction-with-changed-gas {:id id
:masked-password password
:gas (or gas (:gas send-transaction))
:gas-price (or gas-price (:gas-price send-transaction))
:default-gas-price default-gas-price
:on-completed on-transactions-modal-completed}}))
(handlers/register-handler-fx
:wallet/sign-transaction-modal-update-gas-success
(fn [cofx [_ default-gas-price]]
(sign-transaction-modal cofx default-gas-price)))
(handlers/register-handler-fx
:wallet/sign-transaction-modal
(fn [{:keys [db]} _]
(update-gas-price db false :wallet/sign-transaction-modal-update-gas-success)))
(defn discard-transaction
[{:keys [db]}]
(let [{:keys [id from-chat?]} (get-in db [:wallet :send-transaction])
clear-fields (if from-chat? partial-clear-send-properties clear-send-properties)]
(merge {:db (update-in db [:wallet :send-transaction] merge clear-fields)}
(when id
{:discard-transaction id}))))
(handlers/register-handler-fx
:wallet/discard-transaction
(fn [cofx _]
(discard-transaction cofx)))
(handlers/register-handler-fx
:wallet/discard-transaction-navigate-back
(fn [cofx _]
(-> cofx
discard-transaction
(assoc :dispatch [:navigate-back]))))
(handlers/register-handler-fx
:wallet/cancel-signing-modal
:wallet/cancel-entering-password
(fn [{:keys [db]} _]
{:db (update-in db [:wallet :send-transaction] assoc
:signing? false
:show-password-input? false
:wrong-password? false
:password nil)}))
@ -437,11 +230,6 @@
(fn [{:keys [db]} [_ masked-password]]
{:db (assoc-in db [:wallet :send-transaction :password] masked-password)}))
(handlers/register-handler-fx
:wallet.send/set-signing?
(fn [{:keys [db]} [_ signing?]]
{:db (assoc-in db [:wallet :send-transaction :signing?] signing?)}))
(handlers/register-handler-fx
:wallet.send/edit-value
(fn [cofx [_ key value]]
@ -475,7 +263,7 @@
:close-transaction-sent-screen
(fn [{:keys [db]} [_ chat-id]]
{:dispatch [:navigate-back]
:dispatch-later [{:ms 400 :dispatch [:check-transactions-queue]}]}))
:dispatch-later [{:ms 400 :dispatch [:check-dapps-transactions-queue]}]}))
(handlers/register-handler-fx
:sync-wallet-transactions

View File

@ -1,54 +1,43 @@
(ns status-im.ui.screens.wallet.send.subs
(:require [re-frame.core :as re-frame]
[status-im.utils.money :as money]
[status-im.models.wallet :as models.wallet]
[status-im.utils.hex :as utils.hex]))
[status-im.models.wallet :as models.wallet]))
(re-frame/reg-sub ::send-transaction
:<- [:wallet]
(fn [wallet]
(:send-transaction wallet)))
(re-frame/reg-sub
::send-transaction
:<- [:wallet]
(fn [wallet]
(:send-transaction wallet)))
(re-frame/reg-sub :wallet.send/symbol
:<- [::send-transaction]
(fn [send-transaction]
(:symbol send-transaction)))
(re-frame/reg-sub
:wallet.send/symbol
:<- [::send-transaction]
(fn [send-transaction]
(:symbol send-transaction)))
(re-frame/reg-sub :wallet.send/advanced?
:<- [::send-transaction]
(fn [send-transaction]
(:advanced? send-transaction)))
(re-frame/reg-sub
:wallet.send/advanced?
:<- [::send-transaction]
(fn [send-transaction]
(:advanced? send-transaction)))
(re-frame/reg-sub :wallet.send/camera-flashlight
:<- [::send-transaction]
(fn [send-transaction]
(:camera-flashlight send-transaction)))
(re-frame/reg-sub
:wallet.send/camera-flashlight
:<- [::send-transaction]
(fn [send-transaction]
(:camera-flashlight send-transaction)))
(re-frame/reg-sub :wallet.send/wrong-password?
:<- [::send-transaction]
(fn [send-transaction]
(:wrong-password? send-transaction)))
(re-frame/reg-sub
:wallet.send/wrong-password?
:<- [::send-transaction]
(fn [send-transaction]
(:wrong-password? send-transaction)))
(re-frame/reg-sub :wallet.send/sign-password-enabled?
:<- [::send-transaction]
(fn [{:keys [password]}]
(and (not (nil? password)) (not= password ""))))
(re-frame/reg-sub ::unsigned-transactions
:<- [:wallet]
(fn [wallet]
(:transactions-unsigned wallet)))
(re-frame/reg-sub ::unsigned-transaction
:<- [::send-transaction]
:<- [::unsigned-transactions]
(fn [[send-transaction unsigned-transactions]]
(when-let [unsigned-transaction (get unsigned-transactions
(:id send-transaction))]
(merge send-transaction
unsigned-transaction
{:gas (or (:gas send-transaction) (:gas unsigned-transaction))
:gas-price (or (:gas-price send-transaction) (:gas-price unsigned-transaction))}))))
(re-frame/reg-sub
:wallet.send/sign-password-enabled?
:<- [::send-transaction]
(fn [{:keys [password]}]
(and (not (nil? password)) (not= password ""))))
(defn edit-or-transaction-data
"Set up edit data structure, defaulting to transaction when not available"
@ -64,16 +53,14 @@
:gas
(money/to-fixed (:gas transaction)))))
(re-frame/reg-sub :wallet/edit
:<- [::send-transaction]
:<- [::unsigned-transaction]
:<- [:wallet]
(fn [[send-transaction unsigned-transaction {:keys [edit]}]]
(edit-or-transaction-data
(if (:id send-transaction)
unsigned-transaction
send-transaction)
edit)))
(re-frame/reg-sub
:wallet/edit
:<- [::send-transaction]
:<- [:wallet]
(fn [[send-transaction {:keys [edit]}]]
(edit-or-transaction-data
send-transaction
edit)))
(defn check-sufficient-funds [transaction balance symbol amount]
(assoc transaction :sufficient-funds?
@ -93,25 +80,13 @@
(money/formatted->internal :ETH 18))
(money/bignumber available-for-gas))))))
(re-frame/reg-sub :wallet.send/transaction
:<- [::send-transaction]
:<- [:balance]
(fn [[{:keys [amount symbol] :as transaction} balance]]
(-> transaction
(models.wallet/add-max-fee)
(check-sufficient-funds balance symbol amount)
(check-sufficient-gas balance symbol amount))))
(re-frame/reg-sub :wallet.send/unsigned-transaction
:<- [::unsigned-transaction]
:<- [:get-contacts-by-address]
:<- [:balance]
(fn [[{:keys [value to symbol] :as transaction} contacts balance]]
(when transaction
(let [contact (contacts (utils.hex/normalize-hex to))]
(-> transaction
(assoc :amount value
:to-name (:name contact))
(models.wallet/add-max-fee)
(check-sufficient-funds balance symbol value)
(check-sufficient-gas balance symbol value))))))
(re-frame/reg-sub
:wallet.send/transaction
:<- [::send-transaction]
:<- [:balance]
(fn [[{:keys [amount symbol] :as transaction} balance]]
(-> transaction
(models.wallet/transform-data-for-message)
(models.wallet/add-max-fee)
(check-sufficient-funds balance symbol amount)
(check-sufficient-gas balance symbol amount))))

View File

@ -23,9 +23,45 @@
[status-im.utils.security :as security]
[status-im.utils.utils :as utils]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.ethereum.core :as ethereum]))
[status-im.utils.ethereum.core :as ethereum]
[status-im.transport.utils :as transport.utils]))
(defview sign-panel [message-label spinning?]
(defn- toolbar [modal? title]
(let [action (if modal? act/close-white act/back-white)]
[toolbar/toolbar {:style wallet.styles/toolbar}
[toolbar/nav-button (action (if modal?
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back])
#(act/default-handler)))]
[toolbar/content-title {:color :white} title]]))
(defn- advanced-cartouche [{:keys [max-fee gas gas-price]}]
[react/view
[wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas])
(re-frame/dispatch [:navigate-to-modal :wallet-transaction-fee]))}
(i18n/label :t/wallet-transaction-fee)
[react/view {:style styles/advanced-options-text-wrapper
:accessibility-label :transaction-fee-button}
[react/text {:style styles/advanced-fees-text}
(str max-fee " " (i18n/label :t/eth))]
[react/text {:style styles/advanced-fees-details-text}
(str (money/to-fixed gas) " * " (money/to-fixed (money/wei-> :gwei gas-price)) (i18n/label :t/gwei))]]]])
(defn- advanced-options [advanced? transaction scroll]
[react/view {:style styles/advanced-wrapper}
[react/touchable-highlight {:on-press (fn []
(re-frame/dispatch [:wallet.send/toggle-advanced (not advanced?)])
(when (and scroll @scroll) (utils/set-timeout #(.scrollToEnd @scroll) 350)))}
[react/view {:style styles/advanced-button-wrapper}
[react/view {:style styles/advanced-button
:accessibility-label :advanced-button}
[react/i18n-text {:style (merge wallet.components.styles/label
styles/advanced-label)
:key :wallet-advanced}]
[vector-icons/icon (if advanced? :icons/up :icons/down) {:color :white}]]]]
(when advanced?
[advanced-cartouche transaction])])
(defview password-input-panel [message-label spinning?]
(letsubs [account [:get-current-account]
wrong-password? [:wallet.send/wrong-password?]
signing-phrase (:signing-phrase @account)
@ -37,8 +73,6 @@
[tooltip/tooltip (i18n/label :t/wrong-password) styles/password-error-tooltip])
[react/animated-view {:style (styles/sign-panel opacity-value)}
[react/view styles/spinner-container
;;NOTE(goranjovic) - android build doesn't seem to react on change in `:animating` property, so
;;we have this workaround of just using `when` around the whole element.
(when spinning?
[react/activity-indicator {:animating true
:size :large}])]
@ -58,8 +92,8 @@
:accessibility-label :enter-password-input
:auto-capitalize :none}]]]]))
;; "Cancel" and "Sign Transaction >" buttons, signing with password
(defview signing-buttons [spinning? cancel-handler sign-handler sign-label]
;; "Cancel" and "Sign Transaction >" or "Sign >" buttons, signing with password
(defview enter-password-buttons [spinning? cancel-handler sign-handler sign-label]
(letsubs [sign-enabled? [:wallet.send/sign-password-enabled?]]
[bottom-buttons/bottom-buttons
styles/sign-buttons
@ -74,156 +108,34 @@
(i18n/label sign-label)
[vector-icons/icon :icons/forward {:color :white}]]]))
(defn- sign-enabled? [amount-error to amount modal?]
(and
(nil? amount-error)
(or modal? (not (empty? to))) ;;NOTE(goranjovic) - contract creation will have empty `to`
(not (nil? amount))))
;; "Sign Later" and "Sign Transaction >" buttons
(defn- sign-button [amount-error to amount sufficient-funds? sufficient-gas? modal?]
(let [sign-enabled? (sign-enabled? amount-error to amount modal?)
immediate-sign-enabled? (and sign-enabled? sufficient-funds? sufficient-gas?)]
;; "Sign Transaction >" button
(defn- sign-transaction-button [amount-error to amount sufficient-funds? sufficient-gas? modal?]
(let [sign-enabled? (and (nil? amount-error)
(or modal? (not (empty? to))) ;;NOTE(goranjovic) - contract creation will have empty `to`
(not (nil? amount))
sufficient-funds?
sufficient-gas?)]
[bottom-buttons/bottom-buttons
styles/sign-buttons
[react/view]
[button/button {:style components.styles/flex
:disabled? (not immediate-sign-enabled?)
:on-press #(re-frame/dispatch [:wallet.send/set-signing? true])
:disabled? (not sign-enabled?)
:on-press #(re-frame/dispatch [:set-in
[:wallet :send-transaction :show-password-input?]
true])
:text-style {:color :white}
:accessibility-label :sign-transaction-button}
(i18n/label :t/transactions-sign-transaction)
[vector-icons/icon :icons/forward {:color (if immediate-sign-enabled? :white :gray)}]]]))
[vector-icons/icon :icons/forward {:color (if sign-enabled? :white :gray)}]]]))
(defn return-to-transaction [dapp-transaction?]
(if dapp-transaction?
(re-frame/dispatch [:navigate-to-modal :wallet-send-transaction-modal])
(act/default-handler)))
(defn handler [discard? dapp-transaction?]
(if discard?
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back])
#(return-to-transaction dapp-transaction?)))
(defn- toolbar [discard? dapp-transaction? action title]
[toolbar/toolbar {:style wallet.styles/toolbar}
[toolbar/nav-button (action (handler discard? dapp-transaction?))]
[toolbar/content-title {:color :white} title]])
(defview transaction-fee []
(letsubs [send-transaction [:wallet.send/transaction]
unsigned-transaction [:wallet.send/unsigned-transaction]
network [:get-current-account-network]
{gas-edit :gas
max-fee :max-fee
gas-price-edit :gas-price} [:wallet/edit]]
(let [modal? (:id send-transaction)
;;TODO(goranjovic) - unify unsigned and regular transaction subs
{:keys [amount symbol] :as transaction} (if modal? unsigned-transaction send-transaction)
gas (:value gas-edit)
gas-price (:value gas-price-edit)
{:keys [decimals]} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)]
[wallet.components/simple-screen {:status-bar-type :modal-wallet}
[toolbar false modal? act/close-white
(i18n/label :t/wallet-transaction-fee)]
[react/view components.styles/flex
[react/view {:flex-direction :row}
[react/view styles/gas-container-wrapper
[wallet.components/cartouche {}
(i18n/label :t/gas-limit)
[react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas %])
:default-value gas
:accessibility-label :gas-limit-input})]]]
(when (:invalid? gas-edit)
[tooltip/tooltip (i18n/label :t/invalid-number)])]
[react/view styles/gas-container-wrapper
[wallet.components/cartouche {}
(i18n/label :t/gas-price)
[react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas-price %])
:default-value gas-price
:accessibility-label :gas-price-input})]
[wallet.components/cartouche-secondary-text
(i18n/label :t/gwei)]]]
(when (:invalid? gas-price-edit)
[tooltip/tooltip (i18n/label (if (= :invalid-number (:invalid? gas-price-edit))
:t/invalid-number
:t/wallet-send-min-wei))])]]
[react/view styles/transaction-fee-info
[react/view styles/transaction-fee-info-icon
[react/text {:style styles/transaction-fee-info-icon-text} "?"]]
[react/view styles/transaction-fee-info-text-wrapper
[react/i18n-text {:style styles/advanced-fees-text
:key :wallet-transaction-fee-details}]]]
[components/separator]
[react/view styles/transaction-fee-block-wrapper
[wallet.components/cartouche {:disabled? true}
(i18n/label :t/amount)
[react/view {:accessibility-label :amount-input}
[wallet.components/cartouche-text-content
(str (money/to-fixed (money/internal->formatted amount symbol decimals)))
(name symbol)]]]
[wallet.components/cartouche {:disabled? true}
(i18n/label :t/wallet-transaction-total-fee)
[react/view {:accessibility-label :total-fee-input}
[wallet.components/cartouche-text-content
(str max-fee " " (i18n/label :t/eth))]]]]
[bottom-buttons/bottom-buttons styles/fee-buttons
[button/button {:on-press #(re-frame/dispatch [:wallet.send/reset-gas-default])
:accessibility-label :reset-to-default-button}
(i18n/label :t/reset-default)]
[button/button {:on-press #(do (re-frame/dispatch [:wallet.send/set-gas-details
(:value-number gas-edit)
(:value-number gas-price-edit)])
(return-to-transaction modal?))
:accessibility-label :done-button
:disabled? (or (:invalid? gas-edit)
(:invalid? gas-price-edit))}
(i18n/label :t/done)]]]])))
(defn- advanced-cartouche [{:keys [max-fee gas gas-price]}]
[react/view
[wallet.components/cartouche {:on-press #(do (re-frame/dispatch [:wallet.send/clear-gas])
(re-frame/dispatch [:navigate-to-modal :wallet-transaction-fee]))}
(i18n/label :t/wallet-transaction-fee)
[react/view {:style styles/advanced-options-text-wrapper
:accessibility-label :transaction-fee-button}
[react/text {:style styles/advanced-fees-text}
(str max-fee " " (i18n/label :t/eth))]
[react/text {:style styles/advanced-fees-details-text}
(str (money/to-fixed gas) " * " (money/to-fixed (money/wei-> :gwei gas-price)) (i18n/label :t/gwei))]]]])
(defn- advanced-options [advanced? transaction modal? scroll]
[react/view {:style styles/advanced-wrapper}
[react/touchable-highlight {:on-press (fn []
(re-frame/dispatch [:wallet.send/toggle-advanced (not advanced?)])
(when (and scroll @scroll) (utils/set-timeout #(.scrollToEnd @scroll) 350)))}
[react/view {:style styles/advanced-button-wrapper}
[react/view {:style styles/advanced-button
:accessibility-label :advanced-button}
[react/i18n-text {:style (merge wallet.components.styles/label
styles/advanced-label)
:key :wallet-advanced}]
[vector-icons/icon (if advanced? :icons/up :icons/down) {:color :white}]]]]
(when advanced?
[advanced-cartouche transaction])])
(defn- send-transaction-panel [{:keys [modal? transaction scroll advanced? network]}]
(let [{:keys [amount amount-text amount-error asset-error signing? to to-name sufficient-funds? sufficient-gas?
in-progress? from-chat? symbol]} transaction
{:keys [decimals] :as token} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)
timeout (atom nil)]
;; MAIN SEND TRANSACTION VIEW
(defn- send-transaction-view [{:keys [modal? transaction scroll advanced? network]}]
(let [{:keys [amount amount-text amount-error asset-error show-password-input? to to-name sufficient-funds?
sufficient-gas? in-progress? from-chat? symbol]} transaction
{:keys [decimals] :as token} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)]
[wallet.components/simple-screen {:avoid-keyboard? (not modal?)
:status-bar-type (if modal? :modal-wallet :wallet)}
[toolbar from-chat? false (if modal? act/close-white act/back-white)
(i18n/label :t/send-transaction)]
[toolbar modal? (i18n/label :t/send-transaction)]
[react/view components.styles/flex
[common/network-info {:text-color :white}]
[react/scroll-view {:keyboard-should-persist-taps :always
@ -247,46 +159,47 @@
:amount-text amount-text
:input-options {:on-focus (fn [] (when (and scroll @scroll) (utils/set-timeout #(.scrollToEnd @scroll) 100)))
:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount % symbol decimals])}} token]
[advanced-options advanced? transaction modal? scroll]]]
(if signing?
[signing-buttons in-progress?
#(re-frame/dispatch (if modal? [:wallet/cancel-signing-modal] [:wallet/discard-transaction]))
#(re-frame/dispatch (if modal? [:wallet/sign-transaction-modal] [:wallet/sign-transaction]))
[advanced-options advanced? transaction scroll]]]
(if show-password-input?
[enter-password-buttons in-progress?
#(re-frame/dispatch [:wallet/cancel-entering-password])
#(re-frame/dispatch [:wallet/send-transaction])
:t/transactions-sign-transaction]
[sign-button amount-error to amount sufficient-funds? sufficient-gas? modal?])
(when signing?
[sign-panel :t/signing-phrase-description in-progress?])
[sign-transaction-button amount-error to amount sufficient-funds? sufficient-gas? modal?])
(when show-password-input?
[password-input-panel :t/signing-phrase-description in-progress?])
(when in-progress? [react/view styles/processing-view])]]))
;; SEND TRANSACTION FROM WALLET (CHAT)
(defview send-transaction []
(letsubs [transaction [:wallet.send/transaction]
advanced? [:wallet.send/advanced?]
network [:get-current-account-network]
scroll (atom nil)]
[send-transaction-panel {:modal? false :transaction transaction :scroll scroll :advanced? advanced?
:network network}]))
[send-transaction-view {:modal? false :transaction transaction :scroll scroll :advanced? advanced?
:network network}]))
;; SEND TRANSACTION FROM DAPP
(defview send-transaction-modal []
(letsubs [transaction [:wallet.send/unsigned-transaction]
(letsubs [transaction [:wallet.send/transaction]
advanced? [:wallet.send/advanced?]
network [:get-current-account-network]
scroll (atom nil)]
(if transaction
[send-transaction-panel {:modal? true :transaction transaction :scroll scroll :advanced? advanced?
:network network}]
[send-transaction-view {:modal? true :transaction transaction :scroll scroll :advanced? advanced?
:network network}]
[react/view wallet.styles/wallet-modal-container
[react/view components.styles/flex
[status-bar/status-bar {:type :modal-wallet}]
[toolbar false false act/close-white
(i18n/label :t/send-transaction)]
[toolbar true (i18n/label :t/send-transaction)]
[react/i18n-text {:style styles/empty-text
:key :unsigned-transaction-expired}]]])))
;; SIGN MESSAGE FROM DAPP
(defview sign-message-modal []
(letsubs [{:keys [data in-progress?]} [:wallet.send/unsigned-transaction]]
(letsubs [{:keys [data in-progress?]} [:wallet.send/transaction]]
[wallet.components/simple-screen {:status-bar-type :modal-wallet}
[toolbar true false act/close-white
(i18n/label :t/sign-message)]
[toolbar true (i18n/label :t/sign-message)]
[react/view components.styles/flex
[react/scroll-view
[react/view styles/send-transaction-form
@ -297,10 +210,10 @@
:input-options {:multiline true}
:amount-text data}
nil]]]]
[signing-buttons false
[enter-password-buttons false
#(re-frame/dispatch [:wallet/discard-transaction-navigate-back])
#(re-frame/dispatch [:wallet/sign-message-modal])
#(re-frame/dispatch [:wallet/sign-message])
:t/transactions-sign]
[sign-panel :t/signing-message-phrase-description false]
[password-input-panel :t/signing-message-phrase-description false]
(when in-progress?
[react/view styles/processing-view])]]))

View File

@ -0,0 +1,103 @@
(ns status-im.ui.screens.wallet.transaction-fee.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.bottom-buttons.view :as bottom-buttons]
[status-im.ui.components.button.view :as button]
[status-im.ui.components.react :as react]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.actions :as act]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.ui.screens.wallet.components.views :as components]
[status-im.ui.screens.wallet.send.styles :as styles]
[status-im.ui.screens.wallet.styles :as wallet.styles]
[status-im.utils.money :as money]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.ethereum.core :as ethereum]))
(defn return-to-transaction [modal?]
(if modal?
;;TODO(andrey) artificial navigation stack for modals (should be reworked)
(re-frame/dispatch [:navigate-to-modal :wallet-send-transaction-modal])
(act/default-handler)))
(defn- toolbar [modal? title]
[toolbar/toolbar {:style wallet.styles/toolbar}
[toolbar/nav-button (act/close-white #(return-to-transaction modal?))]
[toolbar/content-title {:color :white} title]])
(defview transaction-fee []
(letsubs [send-transaction [:wallet.send/transaction]
network [:get-current-account-network]
{gas-edit :gas
max-fee :max-fee
gas-price-edit :gas-price} [:wallet/edit]]
(let [modal? (:id send-transaction)
{:keys [amount symbol]} send-transaction
gas (:value gas-edit)
gas-price (:value gas-price-edit)
{:keys [decimals]} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)]
[components/simple-screen {:status-bar-type :modal-wallet}
[toolbar modal? (i18n/label :t/wallet-transaction-fee)]
[react/view components.styles/flex
[react/view {:flex-direction :row}
[react/view styles/gas-container-wrapper
[components/cartouche {}
(i18n/label :t/gas-limit)
[react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas %])
:default-value gas
:accessibility-label :gas-limit-input})]]]
(when (:invalid? gas-edit)
[tooltip/tooltip (i18n/label :t/invalid-number)])]
[react/view styles/gas-container-wrapper
[components/cartouche {}
(i18n/label :t/gas-price)
[react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas-price %])
:default-value gas-price
:accessibility-label :gas-price-input})]
[components/cartouche-secondary-text
(i18n/label :t/gwei)]]]
(when (:invalid? gas-price-edit)
[tooltip/tooltip (i18n/label (if (= :invalid-number (:invalid? gas-price-edit))
:t/invalid-number
:t/wallet-send-min-wei))])]]
[react/view styles/transaction-fee-info
[react/view styles/transaction-fee-info-icon
[react/text {:style styles/transaction-fee-info-icon-text} "?"]]
[react/view styles/transaction-fee-info-text-wrapper
[react/i18n-text {:style styles/advanced-fees-text
:key :wallet-transaction-fee-details}]]]
[components/separator]
[react/view styles/transaction-fee-block-wrapper
[components/cartouche {:disabled? true}
(i18n/label :t/amount)
[react/view {:accessibility-label :amount-input}
[components/cartouche-text-content
(str (money/to-fixed (money/internal->formatted amount symbol decimals)))
(name symbol)]]]
[components/cartouche {:disabled? true}
(i18n/label :t/wallet-transaction-total-fee)
[react/view {:accessibility-label :total-fee-input}
[components/cartouche-text-content
(str max-fee " " (i18n/label :t/eth))]]]]
[bottom-buttons/bottom-buttons styles/fee-buttons
[button/button {:on-press #(re-frame/dispatch [:wallet.send/reset-gas-default])
:accessibility-label :reset-to-default-button}
(i18n/label :t/reset-default)]
[button/button {:on-press #(do (re-frame/dispatch [:wallet.send/set-gas-details
(:value-number gas-edit)
(:value-number gas-price-edit)])
(return-to-transaction modal?))
:accessibility-label :done-button
:disabled? (or (:invalid? gas-edit)
(:invalid? gas-price-edit))}
(i18n/label :t/done)]]]])))

View File

@ -1,4 +1,4 @@
(ns status-im.ui.screens.wallet.send.transaction-sent.styles
(ns status-im.ui.screens.wallet.transaction-sent.styles
(:require-macros [status-im.utils.styles :refer [defnstyle defstyle]])
(:require [status-im.ui.components.colors :as colors]))

View File

@ -1,10 +1,10 @@
(ns status-im.ui.screens.wallet.send.transaction-sent.views
(ns status-im.ui.screens.wallet.transaction-sent.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.icons.vector-icons :as vi]
[status-im.ui.screens.wallet.styles :as wallet.styles]
[status-im.ui.screens.wallet.send.transaction-sent.styles :as styles]
[status-im.ui.screens.wallet.transaction-sent.styles :as styles]
[status-im.ui.components.styles :as components.styles]
[re-frame.core :as re-frame]
[status-im.ui.screens.wallet.components.views :as components]

View File

@ -42,45 +42,6 @@
(fn [transactions]
(group-by :type (vals transactions))))
(defn- format-unsigned-transaction [{:keys [id] :as transaction}]
(assoc transaction
:type :unsigned
:confirmations 0
;; TODO (andrey) revisit this, we shouldn't set not hash value to the hash field
:hash id))
(reg-sub :wallet/unsigned-transactions
:<- [:wallet]
:<- [:get-contacts-by-address]
(fn [[wallet contacts]]
(map #(enrich-transaction % contacts) (vals (:transactions-unsigned wallet)))))
(reg-sub :wallet.transactions/unsigned-transactions
:<- [:wallet/unsigned-transactions]
(fn [transactions]
(reduce (fn [acc {:keys [id] :as transaction}]
(assoc acc id (format-unsigned-transaction transaction)))
{}
transactions)))
(reg-sub :wallet.transactions/unsigned-transactions-count
:<- [:wallet.transactions/unsigned-transactions]
(fn [unsigned-transactions]
(count unsigned-transactions)))
(reg-sub :wallet.transactions/unsigned-transactions-list
:<- [:wallet.transactions/unsigned-transactions]
(fn [unsigned-transactions]
(vals unsigned-transactions)))
(reg-sub :wallet.transactions/postponed-transactions-list
:<- [:wallet.transactions/grouped-transactions]
(fn [{:keys [postponed]}]
(when postponed
{:title "Postponed"
:key :postponed
:data postponed})))
(reg-sub :wallet.transactions/pending-transactions-list
:<- [:wallet.transactions/grouped-transactions]
(fn [{:keys [pending]}]
@ -113,12 +74,10 @@
(group-transactions-by-date (concat inbound outbound failed))))
(reg-sub :wallet.transactions/transactions-history-list
:<- [:wallet.transactions/postponed-transactions-list]
:<- [:wallet.transactions/pending-transactions-list]
:<- [:wallet.transactions/completed-transactions-list]
(fn [[postponed pending completed]]
(fn [[pending completed]]
(cond-> []
postponed (into postponed)
pending (into pending)
completed (into completed))))
@ -128,13 +87,11 @@
(:current-transaction wallet)))
(reg-sub :wallet.transactions/transaction-details
:<- [:wallet.transactions/unsigned-transactions]
:<- [:wallet.transactions/transactions]
:<- [:wallet.transactions/current-transaction]
:<- [:network]
(fn [[unsigned-transactions transactions current-transaction network]]
(let [transactions (merge transactions unsigned-transactions)
{:keys [gas-used gas-price hash timestamp type] :as transaction} (get transactions current-transaction)
(fn [[transactions current-transaction network]]
(let [{:keys [gas-used gas-price hash timestamp type] :as transaction} (get transactions current-transaction)
chain (ethereum/network->chain-keyword network)]
(when transaction
(merge transaction

View File

@ -20,7 +20,9 @@
[status-im.utils.ethereum.tokens :as tokens]
[status-im.constants :as constants]
[status-im.utils.datetime :as datetime]
[clojure.string :as string])
[clojure.string :as string]
[status-im.utils.security :as security]
[status-im.utils.types :as types])
(:refer-clojure :exclude [name symbol]))
(defn name [web3 contract cb]
@ -42,12 +44,14 @@
(ethereum/call-params contract "balanceOf(address)" (ethereum/normalized-address address))
#(cb %1 (ethereum/hex->bignumber %2))))
(defn transfer [web3 contract from address value params cb]
(ethereum/send-transaction web3
(merge (ethereum/call-params contract "transfer(address,uint256)" (ethereum/normalized-address address) (ethereum/int->hex value))
{:from from}
params)
#(cb %1 (ethereum/hex->boolean %2))))
(defn transfer [contract from to value gas gas-price masked-password on-completed]
(status/send-transaction (types/clj->json
(merge (ethereum/call-params contract "transfer(address,uint256)" to value)
{:from from
:gas gas
:gasPrice gas-price}))
(security/unmask masked-password)
on-completed))
(defn transfer-from [web3 contract from-address to-address value cb]
(ethereum/call web3
@ -168,8 +172,8 @@
(add-padding from)
(add-padding to)]}]}
payload (.stringify js/JSON (clj->js args))]
(status/call-web3-private payload
(response-handler web3 current-block-number chain direction ethereum/handle-error cb))))
(status/call-private-rpc payload
(response-handler web3 current-block-number chain direction ethereum/handle-error cb))))
(defn get-token-transactions
[web3 chain contracts direction address cb]

View File

@ -9,7 +9,7 @@
(defn make-internal-web3 []
(dependencies/Web3.
#js {:sendAsync (fn [payload callback]
(status/call-web3-private
(status/call-private-rpc
(.stringify js/JSON payload)
(fn [response]
(if (= "" response)

View File

@ -5,7 +5,8 @@
status-im.ui.screens.db
status-im.ui.screens.subs
[re-frame.core :as re-frame]
[status-im.models.browser :as model]))
[status-im.models.browser :as model]
[status-im.utils.types :as types]))
(defn test-fixtures []
@ -129,18 +130,18 @@
(is (zero? (count @dapps-permissions)))
(re-frame/dispatch [:on-bridge-message {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]}
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]})
nil nil])
(is (= {:dapp dapp-name
:permissions []}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE"]}
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE"]})
nil nil])
(is (= 1 (count @dapps-permissions)))
@ -149,9 +150,9 @@
:permissions ["CONTACT_CODE"]}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE" "FAKE_PERMISSION"]}
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE" "FAKE_PERMISSION"]})
nil nil])
(is (= 1 (count @dapps-permissions)))
@ -160,9 +161,9 @@
:permissions ["CONTACT_CODE"]}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]}
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]})
nil nil])
(is (= 1 (count @dapps-permissions)))
@ -171,9 +172,9 @@
:permissions ["CONTACT_CODE"]}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message {:type "status-api-request"
:host dapp-name2
:permissions ["CONTACT_CODE"]}
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name2
:permissions ["CONTACT_CODE"]})
nil nil])
(is (= 2 (count @dapps-permissions)))