diff --git a/components/src/status_im/ui/components/react.cljs b/components/src/status_im/ui/components/react.cljs index c37a75c983..8a0c75c932 100644 --- a/components/src/status_im/ui/components/react.cljs +++ b/components/src/status_im/ui/components/react.cljs @@ -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} diff --git a/modules/react-native-status/android/build.gradle b/modules/react-native-status/android/build.gradle index b213bc103c..b1ec39ad12 100644 --- a/modules/react-native-status/android/build.gradle +++ b/modules/react-native-status/android/build.gradle @@ -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 diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index 8854440da3..0faa563a9b 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -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() { diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index a1fe831368..c2da83fd2d 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -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]); diff --git a/modules/react-native-status/ios/RCTStatus/pom.xml b/modules/react-native-status/ios/RCTStatus/pom.xml index 4592d71280..905d40a6f8 100644 --- a/modules/react-native-status/ios/RCTStatus/pom.xml +++ b/modules/react-native-status/ios/RCTStatus/pom.xml @@ -25,7 +25,7 @@ status-im status-go-ios-simulator - develop-ga6d69eba + tags-v0.11.0^0-g4afd9e6c-302 zip true ./ diff --git a/resources/js/i18n.js b/resources/js/i18n.js deleted file mode 100644 index 842710a9b6..0000000000 --- a/resources/js/i18n.js +++ /dev/null @@ -1,4 +0,0 @@ -// i18n.js 3.0.0.rc14 -!function(a){if("undefined"!=typeof module&&module.exports)module.exports=a(this);else if("function"==typeof define&&define.amd){var b=this;define("i18n",function(){return a(b)})}else this.I18n=a(this)}(function(a){"use strict";var b=a&&a.I18n||{},c=Array.prototype.slice,d=function(a){return("0"+a.toString()).substr(-2)},e=function(a,b){return h("round",a,-b).toFixed(b)},f=function(a){var b=typeof a;return"function"===b||"object"===b&&!!a},g=function(a){return Array.isArray?Array.isArray(a):"[object Array]"===Object.prototype.toString.call(a)},h=function(a,b,c){return"undefined"==typeof c||0===+c?Math[a](b):(b=+b,c=+c,isNaN(b)||"number"!=typeof c||c%1!==0?NaN:(b=b.toString().split("e"),b=Math[a](+(b[0]+"e"+(b[1]?+b[1]-c:-c))),b=b.toString().split("e"),+(b[0]+"e"+(b[1]?+b[1]+c:c))))},i=function(a,b){var c,d;for(c in b)b.hasOwnProperty(c)&&(d=b[c],"[object String]"===Object.prototype.toString.call(d)?a[c]=d:(null==a[c]&&(a[c]={}),i(a[c],d)));return a},j={day_names:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbr_day_names:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],month_names:[null,"January","February","March","April","May","June","July","August","September","October","November","December"],abbr_month_names:[null,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],meridian:["AM","PM"]},k={precision:3,separator:".",delimiter:",",strip_insignificant_zeros:!1},l={unit:"$",precision:2,format:"%u%n",sign_first:!0,delimiter:",",separator:"."},m={unit:"%",precision:3,format:"%n%u",separator:".",delimiter:""},n=[null,"kb","mb","gb","tb"],o={defaultLocale:"en",locale:"en",defaultSeparator:".",placeholder:/(?:\{\{|%\{)(.*?)(?:\}\}?)/gm,fallbacks:!1,translations:{},missingBehaviour:"message",missingTranslationPrefix:""};return b.reset=function(){this.defaultLocale=o.defaultLocale,this.locale=o.locale,this.defaultSeparator=o.defaultSeparator,this.placeholder=o.placeholder,this.fallbacks=o.fallbacks,this.translations=o.translations,this.missingBehaviour=o.missingBehaviour,this.missingTranslationPrefix=o.missingTranslationPrefix},b.initializeOptions=function(){"undefined"==typeof this.defaultLocale&&null!==this.defaultLocale&&(this.defaultLocale=o.defaultLocale),"undefined"==typeof this.locale&&null!==this.locale&&(this.locale=o.locale),"undefined"==typeof this.defaultSeparator&&null!==this.defaultSeparator&&(this.defaultSeparator=o.defaultSeparator),"undefined"==typeof this.placeholder&&null!==this.placeholder&&(this.placeholder=o.placeholder),"undefined"==typeof this.fallbacks&&null!==this.fallbacks&&(this.fallbacks=o.fallbacks),"undefined"==typeof this.translations&&null!==this.translations&&(this.translations=o.translations),"undefined"==typeof this.missingBehaviour&&null!==this.missingBehaviour&&(this.missingBehaviour=o.missingBehaviour),"undefined"==typeof this.missingTranslationPrefix&&null!==this.missingTranslationPrefix&&(this.missingTranslationPrefix=o.missingTranslationPrefix)},b.initializeOptions(),b.locales={},b.locales.get=function(a){var c=this[a]||this[b.locale]||this.default;return"function"==typeof c&&(c=c(a)),g(c)===!1&&(c=[c]),c},b.locales.default=function(a){var e,c=[],d=[];return a&&c.push(a),!a&&b.locale&&c.push(b.locale),b.fallbacks&&b.defaultLocale&&c.push(b.defaultLocale),c.forEach(function(a){e=a.split("-")[0],~d.indexOf(a)||d.push(a),b.fallbacks&&e&&e!==a&&!~d.indexOf(e)&&d.push(e)}),c.length||c.push("en"),d},b.pluralization={},b.pluralization.get=function(a){return this[a]||this[b.locale]||this.default},b.pluralization.default=function(a){switch(a){case 0:return["zero","other"];case 1:return["one"];default:return["other"]}},b.currentLocale=function(){return this.locale||this.defaultLocale},b.isSet=function(a){return void 0!==a&&null!==a},b.lookup=function(a,b){b=this.prepareOptions(b);var e,f,g,c=this.locales.get(b.locale).slice();c[0];for(a=this.getFullScope(a,b);c.length;)if(e=c.shift(),f=a.split(this.defaultSeparator),g=this.translations[e]){for(;f.length&&(g=g[f.shift()],void 0!==g&&null!==g););if(void 0!==g&&null!==g)return g}if(this.isSet(b.defaultValue))return b.defaultValue},b.meridian=function(){var a=this.lookup("time"),b=this.lookup("date");return a&&a.am&&a.pm?[a.am,a.pm]:b&&b.meridian?b.meridian:j.meridian},b.prepareOptions=function(){for(var d,a=c.call(arguments),b={};a.length;)if(d=a.shift(),"object"==typeof d)for(var e in d)d.hasOwnProperty(e)&&(this.isSet(b[e])||(b[e]=d[e]));return b},b.createTranslationOptions=function(a,b){var c=[{scope:a}];return this.isSet(b.defaults)&&(c=c.concat(b.defaults)),this.isSet(b.defaultValue)&&(c.push({message:b.defaultValue}),delete b.defaultValue),c},b.translate=function(a,b){b=this.prepareOptions(b);var d,c=this.createTranslationOptions(a,b),e=c.some(function(a){if(this.isSet(a.scope)?d=this.lookup(a.scope,b):this.isSet(a.message)&&(d=a.message),void 0!==d&&null!==d)return!0},this);return e?("string"==typeof d?d=this.interpolate(d,b):f(d)&&this.isSet(b.count)&&(d=this.pluralize(b.count,d,b)),d):this.missingTranslation(a,b)},b.interpolate=function(a,b){b=this.prepareOptions(b);var d,e,f,g,c=a.match(this.placeholder);if(!c)return a;for(var e;c.length;)d=c.shift(),f=d.replace(this.placeholder,"$1"),e=this.isSet(b[f])?b[f].toString().replace(/\$/gm,"_#$#_"):f in b?this.nullPlaceholder(d,a,b):this.missingPlaceholder(d,a,b),g=new RegExp(d.replace(/\{/gm,"\\{").replace(/\}/gm,"\\}")),a=a.replace(g,e);return a.replace(/_#\$#_/g,"$")},b.pluralize=function(a,b,c){c=this.prepareOptions(c);var d,e,g,h,i;if(d=f(b)?b:this.lookup(b,c),!d)return this.missingTranslation(b,c);for(e=this.pluralization.get(c.locale),g=e(a);g.length;)if(h=g.shift(),this.isSet(d[h])){i=d[h];break}return c.count=String(a),this.interpolate(i,c)},b.missingTranslation=function(a,b){if("guess"==this.missingBehaviour){var c=a.split(".").slice(-1)[0];return(this.missingTranslationPrefix.length>0?this.missingTranslationPrefix:"")+c.replace("_"," ").replace(/([a-z])([A-Z])/g,function(a,b,c){return b+" "+c.toLowerCase()})}var d=null!=b&&null!=b.locale?b.locale:this.currentLocale(),e=this.getFullScope(a,b),f=[d,e].join(this.defaultSeparator);return'[missing "'+f+'" translation]'},b.missingPlaceholder=function(a,b,c){return"[missing "+a+" value]"},b.nullPlaceholder=function(){return b.missingPlaceholder.apply(b,arguments)},b.toNumber=function(a,b){b=this.prepareOptions(b,this.lookup("number.format"),k);var g,i,c=a<0,d=e(Math.abs(a),b.precision).toString(),f=d.split("."),h=[],j=b.format||"%n",l=c?"-":"";for(a=f[0],g=f[1];a.length>0;)h.unshift(a.substr(Math.max(0,a.length-3),3)),a=a.substr(0,a.length-3);return i=h.join(b.delimiter),b.strip_insignificant_zeros&&g&&(g=g.replace(/0+$/,"")),b.precision>0&&g&&(i+=b.separator+g),j=b.sign_first?"%s"+j:j.replace("%n","%s%n"),i=j.replace("%u",b.unit).replace("%n",i).replace("%s",l)},b.toCurrency=function(a,b){return b=this.prepareOptions(b,this.lookup("number.currency.format"),this.lookup("number.format"),l),this.toNumber(a,b)},b.localize=function(a,b,c){switch(c||(c={}),a){case"currency":return this.toCurrency(b);case"number":return a=this.lookup("number.format"),this.toNumber(b,a);case"percentage":return this.toPercentage(b);default:var d;return d=a.match(/^(date|time)/)?this.toTime(a,b):b.toString(),this.interpolate(d,c)}},b.parseDate=function(a){var b,c,d;if("object"==typeof a)return a;if(b=a.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/)){for(var e=1;e<=6;e++)b[e]=parseInt(b[e],10)||0;b[2]-=1,d=b[7]?1e3*("0"+b[7]):null,c=b[8]?new Date(Date.UTC(b[1],b[2],b[3],b[4],b[5],b[6],d)):new Date(b[1],b[2],b[3],b[4],b[5],b[6],d)}else"number"==typeof a?(c=new Date,c.setTime(a)):a.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)?(c=new Date,c.setTime(Date.parse([RegExp.$1,RegExp.$2,RegExp.$3,RegExp.$6,RegExp.$4,RegExp.$5].join(" ")))):a.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)?(c=new Date,c.setTime(Date.parse(a))):(c=new Date,c.setTime(Date.parse(a)));return c},b.strftime=function(a,c){var e=this.lookup("date"),f=b.meridian();if(e||(e={}),e=this.prepareOptions(e,j),isNaN(a.getTime()))throw new Error("I18n.strftime() requires a valid date object, but received an invalid date.");var g=a.getDay(),h=a.getDate(),i=a.getFullYear(),k=a.getMonth()+1,l=a.getHours(),m=l,n=l>11?1:0,o=a.getSeconds(),p=a.getMinutes(),q=a.getTimezoneOffset(),r=Math.floor(Math.abs(q/60)),s=Math.abs(q)-60*r,t=(q>0?"-":"+")+(r.toString().length<2?"0"+r:r)+(s.toString().length<2?"0"+s:s);return m>12?m-=12:0===m&&(m=12),c=c.replace("%a",e.abbr_day_names[g]),c=c.replace("%A",e.day_names[g]),c=c.replace("%b",e.abbr_month_names[k]),c=c.replace("%B",e.month_names[k]),c=c.replace("%d",d(h)),c=c.replace("%e",h),c=c.replace("%-d",h),c=c.replace("%H",d(l)),c=c.replace("%-H",l),c=c.replace("%I",d(m)),c=c.replace("%-I",m),c=c.replace("%m",d(k)),c=c.replace("%-m",k),c=c.replace("%M",d(p)),c=c.replace("%-M",p),c=c.replace("%p",f[n]),c=c.replace("%S",d(o)),c=c.replace("%-S",o),c=c.replace("%w",g),c=c.replace("%y",d(i)),c=c.replace("%-y",d(i).replace(/^0+/,"")),c=c.replace("%Y",i),c=c.replace("%z",t)},b.toTime=function(a,b){var c=this.parseDate(b),d=this.lookup(a);return c.toString().match(/invalid/i)?c.toString():d?this.strftime(c,d):c.toString()},b.toPercentage=function(a,b){return b=this.prepareOptions(b,this.lookup("number.percentage.format"),this.lookup("number.format"),m),this.toNumber(a,b)},b.toHumanSize=function(a,b){for(var f,g,c=1024,d=a,e=0;d>=c&&e<4;)d/=c,e+=1;return 0===e?(f=this.t("number.human.storage_units.units.byte",{count:d}),g=0):(f=this.t("number.human.storage_units.units."+n[e]),g=d-Math.floor(d)===0?0:1),b=this.prepareOptions(b,{unit:f,precision:g,format:"%n%u",delimiter:""}),this.toNumber(d,b)},b.getFullScope=function(a,b){return b=this.prepareOptions(b),a.constructor===Array&&(a=a.join(this.defaultSeparator)),b.scope&&(a=[b.scope,a].join(this.defaultSeparator)),a},b.extend=function(a,b){return"undefined"==typeof a&&"undefined"==typeof b?{}:i(a,b)},b.t=b.translate,b.l=b.localize,b.p=b.pluralize,b}); -I18n.defaultLocale = "en"; -I18n.fallbacks = true; diff --git a/resources/js/web3_init.js b/resources/js/web3_init.js index 6d1e298a75..ccd411dabd 100644 --- a/resources/js/web3_init.js +++ b/resources/js/web3_init.js @@ -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 } } diff --git a/resources/js/webview.js b/resources/js/webview.js index 1dfdeb464c..679cc2ca08 100644 --- a/resources/js/webview.js +++ b/resources/js/webview.js @@ -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 }, "*"); - }; }()); diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 7a76ce0701..4b17963454 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -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") \ No newline at end of file +(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") \ No newline at end of file diff --git a/src/status_im/models/browser.cljs b/src/status_im/models/browser.cljs index a39b5b4042..f857727528 100644 --- a/src/status_im/models/browser.cljs +++ b/src/status_im/models/browser.cljs @@ -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))))) \ No newline at end of file + (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}])]})) \ No newline at end of file diff --git a/src/status_im/models/wallet.cljs b/src/status_im/models/wallet.cljs index 34724c7ebc..74b7a3bc03 100644 --- a/src/status_im/models/wallet.cljs +++ b/src/status_im/models/wallet.cljs @@ -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))) diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 220db7e419..3bd69cffdd 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -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)) diff --git a/src/status_im/native_module/impl/module.cljs b/src/status_im/native_module/impl/module.cljs index 5ae460b80a..cd406ac0ed 100644 --- a/src/status_im/native_module/impl/module.cljs +++ b/src/status_im/native_module/impl/module.cljs @@ -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)) diff --git a/src/status_im/ui/screens/browser/events.cljs b/src/status_im/ui/screens/browser/events.cljs index 5419073235..636fe471bf 100644 --- a/src/status_im/ui/screens/browser/events.cljs +++ b/src/status_im/ui/screens/browser/events.cljs @@ -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 diff --git a/src/status_im/ui/screens/browser/views.cljs b/src/status_im/ui/screens/browser/views.cljs index 994c94cd76..c83515f9f7 100644 --- a/src/status_im/ui/screens/browser/views.cljs +++ b/src/status_im/ui/screens/browser/views.cljs @@ -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}]) diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index d041822840..d16a24516a 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -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] diff --git a/src/status_im/ui/screens/subs.cljs b/src/status_im/ui/screens/subs.cljs index 32f0d6a04f..2b850743ec 100644 --- a/src/status_im/ui/screens/subs.cljs +++ b/src/status_im/ui/screens/subs.cljs @@ -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] diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index b5bef3cf8d..907efb0664 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -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!))} diff --git a/src/status_im/ui/screens/wallet/db.cljs b/src/status_im/ui/screens/wallet/db.cljs index 8c64f859a2..dab2326506 100644 --- a/src/status_im/ui/screens/wallet/db.cljs +++ b/src/status_im/ui/screens/wallet/db.cljs @@ -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. diff --git a/src/status_im/ui/screens/wallet/navigation.cljs b/src/status_im/ui/screens/wallet/navigation.cljs index aa560a3fc3..455300ff09 100644 --- a/src/status_im/ui/screens/wallet/navigation.cljs +++ b/src/status_im/ui/screens/wallet/navigation.cljs @@ -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 diff --git a/src/status_im/ui/screens/wallet/send/db.cljs b/src/status_im/ui/screens/wallet/send/db.cljs index 9ea7b01656..6a5173d43a 100644 --- a/src/status_im/ui/screens/wallet/send/db.cljs +++ b/src/status_im/ui/screens/wallet/send/db.cljs @@ -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])) diff --git a/src/status_im/ui/screens/wallet/send/events.cljs b/src/status_im/ui/screens/wallet/send/events.cljs index 3edb6b53c4..61c36be623 100644 --- a/src/status_im/ui/screens/wallet/send/events.cljs +++ b/src/status_im/ui/screens/wallet/send/events.cljs @@ -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 diff --git a/src/status_im/ui/screens/wallet/send/subs.cljs b/src/status_im/ui/screens/wallet/send/subs.cljs index 1bb7f9b9fa..fa955eb9cf 100644 --- a/src/status_im/ui/screens/wallet/send/subs.cljs +++ b/src/status_im/ui/screens/wallet/send/subs.cljs @@ -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)))) diff --git a/src/status_im/ui/screens/wallet/send/views.cljs b/src/status_im/ui/screens/wallet/send/views.cljs index e7b95784c9..7f48143362 100644 --- a/src/status_im/ui/screens/wallet/send/views.cljs +++ b/src/status_im/ui/screens/wallet/send/views.cljs @@ -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])]])) diff --git a/src/status_im/ui/screens/wallet/transaction_fee/views.cljs b/src/status_im/ui/screens/wallet/transaction_fee/views.cljs new file mode 100644 index 0000000000..0043c2ba3e --- /dev/null +++ b/src/status_im/ui/screens/wallet/transaction_fee/views.cljs @@ -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)]]]]))) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/send/transaction_sent/styles.cljs b/src/status_im/ui/screens/wallet/transaction_sent/styles.cljs similarity index 94% rename from src/status_im/ui/screens/wallet/send/transaction_sent/styles.cljs rename to src/status_im/ui/screens/wallet/transaction_sent/styles.cljs index 99afde0015..1b4a4774de 100644 --- a/src/status_im/ui/screens/wallet/send/transaction_sent/styles.cljs +++ b/src/status_im/ui/screens/wallet/transaction_sent/styles.cljs @@ -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])) diff --git a/src/status_im/ui/screens/wallet/send/transaction_sent/views.cljs b/src/status_im/ui/screens/wallet/transaction_sent/views.cljs similarity index 93% rename from src/status_im/ui/screens/wallet/send/transaction_sent/views.cljs rename to src/status_im/ui/screens/wallet/transaction_sent/views.cljs index 809bbb6a51..86bbfb7909 100644 --- a/src/status_im/ui/screens/wallet/send/transaction_sent/views.cljs +++ b/src/status_im/ui/screens/wallet/transaction_sent/views.cljs @@ -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] diff --git a/src/status_im/ui/screens/wallet/transactions/subs.cljs b/src/status_im/ui/screens/wallet/transactions/subs.cljs index 8cbd71b223..c409e22ddd 100644 --- a/src/status_im/ui/screens/wallet/transactions/subs.cljs +++ b/src/status_im/ui/screens/wallet/transactions/subs.cljs @@ -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 diff --git a/src/status_im/utils/ethereum/erc20.cljs b/src/status_im/utils/ethereum/erc20.cljs index 2a8bf579cb..da17b3e982 100644 --- a/src/status_im/utils/ethereum/erc20.cljs +++ b/src/status_im/utils/ethereum/erc20.cljs @@ -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] diff --git a/src/status_im/utils/web3_provider.cljs b/src/status_im/utils/web3_provider.cljs index d4ccfb7694..d330c06c2a 100644 --- a/src/status_im/utils/web3_provider.cljs +++ b/src/status_im/utils/web3_provider.cljs @@ -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) diff --git a/test/cljs/status_im/test/browser/events.cljs b/test/cljs/status_im/test/browser/events.cljs index 85c2122bb3..267b8e5a11 100644 --- a/test/cljs/status_im/test/browser/events.cljs +++ b/test/cljs/status_im/test/browser/events.cljs @@ -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)))