EIPs/EIPS/eip-107.md

615 lines
41 KiB
Markdown
Raw Normal View History

---
2018-03-21 12:51:05 +00:00
eip: 107
title: safe "eth_sendTransaction" authorization via html popup
author: Ronan Sandford <wighawag@gmail.com>
created: 2016-06-05
status: Draft
type: Standards Track
2018-03-21 12:51:05 +00:00
category: Interface
---
2016-05-07 05:30:33 +00:00
Abstract
========
This draft EIP describes the details of an authorization method that if provided by rpc enabled ethereum nodes would allow regular websites to send transactions (via ```eth_sendTransaction```) without the need to enable CORS. Instead, user would be asked to confirm the transaction via an html popup.
2016-05-08 22:25:30 +00:00
2016-05-12 08:34:19 +00:00
Every read only rpc call the dapp wants to perform is redirected to an invisible iframe from the node's domain and for every transaction that the dapp wish to execute, an html popup is presented to the user to allow him/her to cancel or confirm the transaction. This allows the dapp to connect to the node's rpc api without being granted any kind of privileges. This allows users to safely interact with dapps running in their everyday web browser while their accounts are unlocked. In case the account is not unlocked, and the node has allowed the "personal" api via rpc,the html page also allow the user to enter their password to unlock the account for the scope of the transaction.
2016-05-07 05:30:33 +00:00
Motivation
==========
2016-05-12 08:34:19 +00:00
Currently, if a user navigates to a dapp running on a website using her/his everyday browser, the dapp will by default have no access to the rpc api for security reasons. The user will have to enable CORS for the website's domain in order for the dapp to work. Unfortunately if the user does so, the dapp will be able to send transactions from any unlocked account without the need for any user consent. In other words, not only does the user need to change the node's default setting, but the user is also forced to trust the dapp in order to use it. This is of course not acceptable and forces existing dapps to rely on the use of workarrounds like:
- if the transaction is a plain ether transfer, the user is asked to enter it in a dedicated trusted wallet like "Mist"
2016-05-07 05:30:33 +00:00
- For more complex case, the user is asked to enter the transaction manually via the node command line interface.
2016-05-08 17:26:03 +00:00
2016-05-09 15:41:46 +00:00
This proposal aims to provide a safe and user friendly alternative.
2016-05-08 17:28:06 +00:00
2016-05-12 08:34:19 +00:00
Here are some screenshots of the provided implementation of that html popup:
2016-05-08 22:25:30 +00:00
2016-05-08 17:28:06 +00:00
Account unlocked :
-----------------
2016-05-12 08:34:19 +00:00
When the account is already unlocked, the user is presented with the following popup for every transaction that the dapp attempts to make:
2016-05-08 22:25:30 +00:00
![](../assets/eip-107/authorization.png)
2016-05-07 05:30:33 +00:00
2016-05-08 17:28:06 +00:00
Account locked and no "personal" api exposed via rpc:
-----------------
2016-05-12 08:34:19 +00:00
When the account is locked, and the node does not provide access to account unlocking via its rpc interface, the following popup will be presented. This is not ideal since this requires the user to know how to unlock an account:
2016-05-08 22:25:30 +00:00
![](../assets/eip-107/authorization-locked.png)
2016-05-08 17:28:06 +00:00
Account locked but node exposing the "personal" api via rpc :
-----------------
2016-05-12 08:34:19 +00:00
A better option is to ask the user for their password, but this is only possible if the node allows access to the "personal" api via rpc. In such case, the following dialog will be presented to the user so he/she can accept the transaction by providing the password required to unlock the account:
2016-05-08 22:25:30 +00:00
![](../assets/eip-107/authorization-password.png)
2016-05-08 17:26:03 +00:00
2016-05-07 05:30:33 +00:00
Specification
=============
2016-05-12 08:34:19 +00:00
In order for the mechanism to work, the node needs to serve an html file via http at the url \<node url\>/authorization.html
2016-05-08 22:25:30 +00:00
2016-05-07 07:00:34 +00:00
This file will then be used by the dapp in 2 different modes (invisible iframe and popup window).
2016-05-08 22:25:30 +00:00
The invisible iframe will be embeded in the dapp to allow the dapp to send its read-only rpc call without having to enable CORS for the dapp's website domain. This is done by sending message to the iframe (via javascript ```window.postMessage```) which in turn execute the rpc call. This works since the iframe and the node share the same domain/port.
2016-05-08 22:25:30 +00:00
2016-05-12 16:14:58 +00:00
In the iframe mode, the html file's javascript code will ensure that no call requiring an unlocked key can be made. This is to prevent dapps from embedding the invisible iframe and tricking the user into clicking the confirm button.
2016-05-12 08:34:19 +00:00
If the dapp requires an ```eth_sendTransaction``` call, the dapp will instead open a new window using the same url.
2016-05-08 22:25:30 +00:00
In this popup window mode, the html file's javascript code will alow ```eth_sendTransaction``` (but not ```eth_sign```, as there is no way to display to the user the meaningful content of the transaction to sign in a safe way) to be called. But instead of sending the call to the node directly, a confirmation dialog will be presented showing the sender and recipient addresses, as well as the amount being transfered along with the potential gas cost. Upon the user approving, the request will be sent and the result returned to the dapp. An error will be returned in case the user cancel the request.
2016-05-08 22:25:30 +00:00
2016-05-12 08:34:19 +00:00
The html page also checks for the availability of the "personal" api and if so, will ask the user to unlock the account if necessary. The unlocking is temporary (3s) so the password will be asked again if a transaction is attempted before the end of this short time.
2016-05-07 05:30:33 +00:00
In both iframe mode and window mode, the communication with the dapp is achieved using ```window.postMessage```.
2016-05-12 08:34:19 +00:00
The fist message the iframe/window sends is a message containing the string "ready" to let the dapp know that it now accepts messages. Then the dapp can start performing rpc call by sending message using the following object :
2016-05-09 15:41:46 +00:00
```
{
id:<a an id>, //so responses can be match as there is no guarantee of the order of the response
payload:<json rpc object> //this is the exact object that usually send to the node
}
```
For ```eth_sendTransaction``` the "gas", "gasPrice" and "from" field need to be set in the rpc parameter so that the window can display the correct value. If not all of these are passed in, the window will return an error.
Upon receiving such message, the iframe will perform the actual rpc call to the node but only if such a call is a read only call (not requiring an unlocked key). If it is not it will return a error. The window on the other will only accept ```eth_sendTransaction``` calls but will display a dialog so the user can accept or cancel the request.
2016-05-09 15:41:46 +00:00
In all the cases, the iframe/window will send a message back to the dapp using the following object:
```
{
id:<id matchign the request>,
result:<rpc result as is>,
error:<error object>
}
```
the error object cannot be a javascript Error object due to postMessage limitation. Instead it is
2016-05-09 15:41:46 +00:00
```
{
message:<a string describing the error>,
type:<a string defining the type of error> //type="cancel" means the user cancel the transaction
}
```
2016-05-07 05:30:33 +00:00
Rationale
=========
The design for that proposal was chosen for its simplicity and security. A previous idea was to use an oauth-like protocol in order for the user to accept or deny a transaction request. It would have required deeper code change in the node and some geth contributors argues that such change did not fit into geth code base as it would have required dapp aware code.
2016-05-08 22:25:30 +00:00
The current design, instead has a very simple implementation (self contained html file that can be shared across node's implementation) and its safeness is guarantess by browsers' cross domain policies.
2016-05-12 08:34:19 +00:00
The use of iframe/ window was required to have both security and user friendliness. The invisble iframe allows the dapp to execute read only calls without the need for user input, and the window ensures user approval before making a call. While we could have made it without the window mode by making the iframe confirmation use the native browser ```window.confirm``` dialog, this would have prevented the use of a more elegant confirmation popup that the current design allows. It also happens to be that the ```window.confirm``` is not safe in some browsers, as it gives focus to the accept option and can be triggered automatically (https://bugs.chromium.org/p/chromium/issues/detail?id=260653).
2016-05-07 05:30:33 +00:00
Implementations
===============
2016-05-12 08:34:19 +00:00
In order to implement this design, the following html file or an equivalent one needs to be served at the url \<node url\>/authorization.html
2016-05-09 15:41:46 +00:00
2016-05-12 08:34:19 +00:00
That's it.
2016-05-07 07:00:34 +00:00
```
2016-05-08 17:21:04 +00:00
<!DOCTYPE html>
<html>
2016-05-09 15:41:46 +00:00
<head>
<title>Ethereum Authorization</title>
</head>
<script>
//https://github.com/alexvandesande/blockies
!function(){function r(r){for(var t=0;t<l.length;t++)l[t]=0;for(var t=0;t<r.length;t++)l[t%4]=(l[t%4]<<5)-l[t%4]+r.charCodeAt(t)}function t(){var r=l[0]^l[0]<<11;return l[0]=l[1],l[1]=l[2],l[2]=l[3],l[3]=l[3]^l[3]>>19^r^r>>8,(l[3]>>>0)/(1<<31>>>0)}function e(){var r=Math.floor(360*t()),e=60*t()+40+"%",o=25*(t()+t()+t()+t())+"%",n="hsl("+r+","+e+","+o+")";return n}function o(r){for(var e=r,o=r,n=Math.ceil(e/2),a=e-n,l=[],c=0;o>c;c++){for(var f=[],h=0;n>h;h++)f[h]=Math.floor(2.3*t());var i=f.slice(0,a);i.reverse(),f=f.concat(i);for(var v=0;v<f.length;v++)l.push(f[v])}return l}function n(r,t,e,o,n){var a=document.createElement("canvas"),l=Math.sqrt(r.length);a.width=a.height=l*e;var c=a.getContext("2d");c.fillStyle=o,c.fillRect(0,0,a.width,a.height),c.fillStyle=t;for(var f=0;f<r.length;f++){var h=Math.floor(f/l),i=f%l;c.fillStyle=1==r[f]?t:n,r[f]&&c.fillRect(i*e,h*e,e,e)}return a}function a(t){t=t||{};var a=t.size||8,l=t.scale||4,c=t.seed||Math.floor(Math.random()*Math.pow(10,16)).toString(16);r(c);var f=t.color||e(),h=t.bgcolor||e(),i=t.spotcolor||e(),v=o(a),u=n(v,f,l,h,i);return u}var l=new Array(4);window.blockies={create:a}}();
2016-05-09 15:41:46 +00:00
/* bignumber.js v2.3.0 https://github.com/MikeMcl/bignumber.js/LICENCE */
!function(e){"use strict";function n(e){function E(e,n){var t,r,i,o,u,s,f=this;if(!(f instanceof E))return j&&L(26,"constructor call without new",e),new E(e,n);if(null!=n&&H(n,2,64,M,"base")){if(n=0|n,s=e+"",10==n)return f=new E(e instanceof E?e:s),U(f,P+f.e+1,k);if((o="number"==typeof e)&&0*e!=0||!new RegExp("^-?"+(t="["+N.slice(0,n)+"]+")+"(?:\\."+t+")?$",37>n?"i":"").test(s))return h(f,s,o,n);o?(f.s=0>1/e?(s=s.slice(1),-1):1,j&&s.replace(/^0\.0*|\./,"").length>15&&L(M,v,e),o=!1):f.s=45===s.charCodeAt(0)?(s=s.slice(1),-1):1,s=D(s,10,n,f.s)}else{if(e instanceof E)return f.s=e.s,f.e=e.e,f.c=(e=e.c)?e.slice():e,void(M=0);if((o="number"==typeof e)&&0*e==0){if(f.s=0>1/e?(e=-e,-1):1,e===~~e){for(r=0,i=e;i>=10;i/=10,r++);return f.e=r,f.c=[e],void(M=0)}s=e+""}else{if(!g.test(s=e+""))return h(f,s,o);f.s=45===s.charCodeAt(0)?(s=s.slice(1),-1):1}}for((r=s.indexOf("."))>-1&&(s=s.replace(".","")),(i=s.search(/e/i))>0?(0>r&&(r=i),r+=+s.slice(i+1),s=s.substring(0,i)):0>r&&(r=s.length),i=0;48===s.charCodeAt(i);i++);for(u=s.length;48===s.charCodeAt(--u););if(s=s.slice(i,u+1))if(u=s.length,o&&j&&u>15&&(e>y||e!==d(e))&&L(M,v,f.s*e),r=r-i-1,r>z)f.c=f.e=null;else if(G>r)f.c=[f.e=0];else{if(f.e=r,f.c=[],i=(r+1)%O,0>r&&(i+=O),u>i){for(i&&f.c.push(+s.slice(0,i)),u-=O;u>i;)f.c.push(+s.slice(i,i+=O));s=s.slice(i),i=O-s.length}else i-=u;for(;i--;s+="0");f.c.push(+s)}else f.c=[f.e=0];M=0}function D(e,n,t,i){var o,u,f,c,a,h,g,p=e.indexOf("."),d=P,m=k;for(37>t&&(e=e.toLowerCase()),p>=0&&(f=J,J=0,e=e.replace(".",""),g=new E(t),a=g.pow(e.length-p),J=f,g.c=s(l(r(a.c),a.e),10,n),g.e=g.c.length),h=s(e,t,n),u=f=h.length;0==h[--f];h.pop());if(!h[0])return"0";if(0>p?--u:(a.c=h,a.e=u,a.s=i,a=C(a,g,d,m,n),h=a.c,c=a.r,u=a.e),o=u+d+1,p=h[o],f=n/2,c=c||0>o||null!=h[o+1],c=4>m?(null!=p||c)&&(0==m||m==(a.s<0?3:2)):p>f||p==f&&(4==m||c||6==m&&1&h[o-1]||m==(a.s<0?8:7)),1>o||!h[0])e=c?l("1",-d):"0";else{if(h.length=o,c)for(--n;++h[--o]>n;)h[o]=0,o||(++u,h.unshift(1));for(f=h.length;!h[--f];);for(p=0,e="";f>=p;e+=N.charAt(h[p++]));e=l(e,u)}return e}function F(e,n,t,i){var o,u,s,c,a;if(t=null!=t&&H(t,0,8,i,w)?0|t:k,!e.c)return e.toString();if(o=e.c[0],s=e.e,null==n)a=r(e.c),a=19==i||24==i&&B>=s?f(a,s):l(a,s);else if(e=U(new E(e),n,t),u=e.e,a=r(e.c),c=a.length,19==i||24==i&&(u>=n||B>=u)){for(;n>c;a+="0",c++);a=f(a,u)}else if(n-=s,a=l(a,u),u+1>c){if(--n>0)for(a+=".";n--;a+="0");}else if(n+=u-c,n>0)for(u+1==c&&(a+=".");n--;a+="0");return e.s<0&&o?"-"+a:a}function _(e,n){var t,r,i=0;for(u(e[0])&&(e=e[0]),t=new E(e[0]);++i<e.length;){if(r=new E(e[i]),!r.s){t=r;break}n.call(t,r)&&(t=r)}return t}function x(e,n,t,r,i){return(n>e||e>t||e!=c(e))&&L(r,(i||"decimal places")+(n>e||e>t?" out of range":" not an integer"),e),!0}function I(e,n,t){for(var r=1,i=n.length;!n[--i];n.pop());for(i=n[0];i>=10;i/=10,r++);return(t=r+t*O-1)>z?e.c=e.e=null:G>t?e.c=[e.e=0]:(e.e=t,e.c=n),e}function L(e,n,t){var r=new Error(["new BigNumber","cmp","config","div","divToInt","eq","gt","gte","lt","lte","minus","mod","plus","precision","random","round","shift","times","toDigits","toExponential","toFixed","toFormat","toFraction","pow","toPrecision","toString","BigNumber"][e]+"() "+n+": "+t);throw r.name="BigNumber Error",M=0,r}function U(e,n,t,r){var i,o,u,s,f,l,c,a=e.c,h=S;if(a){e:{for(i=1,s=a[0];s>=10;s/=10,i++);if(o=n-i,0>o)o+=O,u=n,f=a[l=0],c=f/h[i-u-1]%10|0;else if(l=p((o+1)/O),l>=a.length){if(!r)break e;for(;a.length<=l;a.push(0));f=c=0,i=1,o%=O,u=o-O+1}else{for(f=s=a[l],i=1;s>=10;s/=10,i++);o%=O,u=o-O+i,c=0>u?0:f/h[i-u-1]%10|0}if(r=r||0>n||null!=a[l+1]||(0>u?f:f%h[i-u-1]),r=4>t?(c||r)&&(0==t||t==(e.s<0?3:2)):c>5||5==c&&(4==t||r||6==t&&(o>0?u>0?f/h[i-u]:0:a[l-1])%10&1||t==(e.s<0?8:7)),1>n||!a[0])return a.length=0,r?(n-=e.e+1,a[0]=h[(O-n%O)%O],e.e=-n||0):a[0]=e.e=0,e;if(0==o?(a.length=l,s=1,l--):(a.length=l+1,s=h[O-o],a[l]=u>0?d(f/h[i-u]%h[u])*s:0),r)for(;;){if(0==l){for(o=1,u=a[0];u>=10;u/=10,o++);for(u=a[0]+=s,s=1;u>=10;u/=10,s++);o!=s&&(e.e++,a[0]==b&&(a[0]=1));break}if(a[l]+=s,a[l]!=b)break;a[l--]=0,s=1}for(o=a.length;0===a[--o];a.pop());}e.e>z?e.c=e.e=null:e.e<G&&(e.c=[e.e=0])}return e}var
</script>
2016-05-09 15:41:46 +00:00
<style>
body{
font-family: 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', 'Helvetica', 'Arial', 'Lucida Grande', sans-serif;
background: #E2E2E2;
}
*, *:after, *:before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
2016-05-09 15:41:46 +00:00
#pleasewait{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#infomessage {
text-align: center;
font-size: 1rem;
margin: 0 2rem 4.5rem;
}
2016-05-09 15:41:46 +00:00
.wrapper{
background: #E2E2E2;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
display:none;
text-align: center;
}
.title {
text-align: center;
font-size: 1.2rem;
margin: 1rem 0rem;
}
.message {
text-align: center;
font-size: 1rem;
/*margin: 0 2rem 4.5rem;*/
}
2016-05-09 15:41:46 +00:00
#passwordField {
text-align: center;
font-size: 1rem;
margin: 1rem 0rem;
/*margin: 0 2rem 4.5rem;*/
}
2016-05-09 15:41:46 +00:00
.wrapper button {
background: transparent;
border: none;
color: #1678E5;
height: 3rem;
font-size: 1rem;
width: 50%;
position: absolute;
bottom: 0;
cursor: pointer;
}
#cancel-button {
border-top: 1px solid #B4B4B4;
border-right: 1px solid #B4B4B4;
left: 0;
border-radius: 0 0 0 10px;
}
#confirm-button {
border-top: 1px solid #B4B4B4;
right: 0;
border-radius: 0 0 10px 0;
}
.wrapper button:focus,
.wrapper button:hover {
font-weight: bold;
background: #EFEFEF;
}
.wrapper button:active {
background: #D6D6D6;
}
2016-05-09 15:41:46 +00:00
.button {
margin: 1rem 0rem;
display: inline-block;
padding: 9px 15px;
background-color: #3898EC;
color: white;
border: 0;
line-height: inherit;
text-decoration: none;
cursor: pointer;
border-radius: 0;
}
input.button {
-webkit-appearance: button;
}
</style>
2016-05-09 15:41:46 +00:00
<body>
<div id="pleasewait">
<br/>
<p id="infomessage">Please wait...</p>
</div>
2016-05-10 17:41:46 +00:00
<form id="form" class="wrapper">
2016-05-09 15:41:46 +00:00
<br/>
<p id="message" class="message"></p>
<p id="passwordField"><label>Password Required:</label><input id="password" type="password" /></p>
2016-05-10 17:41:46 +00:00
<button id="cancel-button" type="button" autofocus>Cancel</button>
<button id="confirm-button" type="button" >Confirm</button>
</form>
2016-05-09 15:41:46 +00:00
<div id="modal-dialog" class="wrapper">
<h3 id="modal-dialog-title" class="title">Title</h3>
<p id="modal-dialog-message" class="message">Message</p>
<span id="modal-dialog-button" class="button">Ok</span>
</div>
2016-05-08 17:21:04 +00:00
<script>
2016-05-10 17:41:46 +00:00
var noMessageReceivedYet = true;
var closedByCode = false;
2016-05-09 15:41:46 +00:00
var pleaseWait = document.getElementById("pleasewait");
var form = document.getElementById("form");
var cancelButton = document.getElementById("cancel-button");
var confirmButton = document.getElementById("confirm-button");
var message = document.getElementById("message");
var infoMessage = document.getElementById("infomessage");
2016-05-09 15:41:46 +00:00
var password = document.getElementById("password");
var passwordField = document.getElementById("passwordField");
var modalDialog = document.getElementById("modal-dialog");
var modalDialogButton = document.getElementById("modal-dialog-button");
var modalDialogTitle = document.getElementById("modal-dialog-title");
var modalDialogMessage = document.getElementById("modal-dialog-message");
2016-05-09 15:41:46 +00:00
var firstUrl = null;
2016-05-09 15:41:46 +00:00
var inIframe = true;
var source = null;
if(window.opener){
inIframe = false;
source = window.opener;
}else if(window.parent != window){
source = window.parent;
}else{
2016-05-10 17:41:46 +00:00
console.log("closing");
window.close();
}
2016-05-10 17:41:46 +00:00
function closeWindow(){
closedByCode = true;
2016-05-09 15:41:46 +00:00
window.close();
}
function showWaiting(text){
if(!text){
text = "Please wait..."
}
infoMessage.innerHTML = text;
2016-05-09 15:41:46 +00:00
pleaseWait.style.display = "block";
form.style.display = "none";
}
2016-05-09 15:41:46 +00:00
function hideWaiting(){
pleaseWait.style.display = "none";
form.style.display = "block";
window.onbeforeunload = null;
2016-05-09 15:41:46 +00:00
}
2016-05-09 15:41:46 +00:00
function showMessage(title, message, callback, buttonText){
modalDialog.style.display = "block";
modalDialogTitle.innerHTML = title;
modalDialogMessage.innerHTML = "";
if((typeof message) == "string"){
modalDialogMessage.innerHTML += message;
}else{
modalDialogMessage.appendChild(message);
}
modalDialogMessage.appendChild(document.createElement('br'));
2016-05-09 15:41:46 +00:00
if(!buttonText){
buttonText = "Ok";
}
modalDialogButton.innerHTML = buttonText;
modalDialogButton.onclick = function(){
modalDialogButton.onclick = null;
modalDialog.style.display = "none";
if(callback){
callback();
2016-05-08 17:21:04 +00:00
}
2016-05-09 15:41:46 +00:00
}
}
2016-05-10 17:41:46 +00:00
function hideMessage(){
modalDialog.style.display = "none";
modalDialogButton.onclick = null;
}
2016-05-09 15:41:46 +00:00
function sendAsync(url,payload, callback) {
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type','application/json');
2016-05-08 17:21:04 +00:00
2016-05-09 15:41:46 +00:00
request.onreadystatechange = function() {
if (request.readyState === 4) {
var result = request.responseText;
var error = null;
try {
result = JSON.parse(result);
} catch(e) {
var message = !!result && !!result.error && !!result.error.message ? result.error.message : 'Invalid JSON RPC response: ' + JSON.stringify(result);
error = {message:message,type:"JsonParse"};
}
callback(error, result);
2016-05-08 17:21:04 +00:00
}
2016-05-09 15:41:46 +00:00
};
2016-05-09 15:41:46 +00:00
try {
request.send(JSON.stringify(payload));
} catch(e) {
callback({message:'CONNECTION ERROR: Couldn\'t connect to node '+ url +'.',type:"noConnection"});
}
}
2016-05-09 15:41:46 +00:00
function addBlocky(message, address){
var icon = blockies.create({
2016-05-09 15:41:46 +00:00
seed: address,
size: 8,
scale: 6
});
message.appendChild(icon);
}
2016-05-09 15:41:46 +00:00
function askAuthorization(transactionInfo, data, requireUnlock, sourceWindow){
2016-05-09 15:41:46 +00:00
var value = transactionInfo["value"] ? transactionInfo.value : "0";
var gasProvided = transactionInfo.gas;
var gasPriceProvided = transactionInfo.gasPrice;
var gasPrice = new BigNumber(gasPriceProvided,16);
2016-05-09 15:41:46 +00:00
var gas = new BigNumber(gasProvided,16);
var weiValue = new BigNumber(value,16);
var gasWeiValue = gas.times(gasPrice);
var etherValue = weiValue.dividedBy(new BigNumber("1000000000000000000"));
var gasEtherValue = gasWeiValue.dividedBy(new BigNumber("1000000000000000000"));
2016-05-09 15:41:46 +00:00
hideWaiting();
2016-05-09 15:41:46 +00:00
message.innerHTML = "";
2016-05-09 15:41:46 +00:00
addBlocky(message,transactionInfo.from);
2016-05-09 15:41:46 +00:00
var span = document.createElement('span');
span.style="font-size:3em;";
span.innerHTML = "&nbsp;&nbsp;&nbsp;" + "&#x2192;" + "&nbsp;&nbsp;&nbsp;";
message.appendChild(span);
2016-05-09 15:41:46 +00:00
addBlocky(message,transactionInfo.to);
2016-05-09 15:41:46 +00:00
message.appendChild(document.createElement('br'));
var textSpan = document.createElement("span");
message.appendChild(textSpan);
textSpan.innerHTML = etherValue.toFormat() + " ether <br/> + gas cost (" + gasEtherValue.toFormat() + " ether )"
2016-05-09 15:41:46 +00:00
if(requireUnlock){
passwordField.style.display = "block";
2016-05-09 15:41:46 +00:00
}else{
passwordField.style.display = "none";
2016-05-09 15:41:46 +00:00
}
2016-05-09 15:41:46 +00:00
cancelButton.onclick = function(){
2016-05-12 06:13:33 +00:00
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Not Authorized"},type:"cancel"},firstUrl);
2016-05-10 17:41:46 +00:00
closeWindow();
2016-05-09 15:41:46 +00:00
}
2016-05-10 17:41:46 +00:00
var submitFunc = function(){
window.onbeforeunload = function (event) {
if(!closedByCode){
return "do not close now as a transaction is progress, this cannot be canceled and we wait for an answer";
}
};
2016-05-09 15:41:46 +00:00
if(requireUnlock){
if(password.value == ""){
password.style.border = "2px solid red";
2016-05-10 17:41:46 +00:00
return false;
2016-05-09 15:41:46 +00:00
}
password.style.border = "none";
var params = [transactionInfo.from,password.value,3];
showWaiting("Please wait...<br/>Do not close the window.");
2016-05-09 15:41:46 +00:00
sendAsync(data.url,{id:999992,method:"personal_unlockAccount",params:params},function(error,result){
if(error || result.error){
showMessage("Error unlocking account", "Please retry.", hideWaiting);
2016-05-08 17:21:04 +00:00
}else{
2016-05-09 15:41:46 +00:00
sendAsync(data.url,data.payload,function(error,result){
2016-05-12 06:13:33 +00:00
sourceWindow.postMessage({id:data.id,result:result,error:error},firstUrl);
2016-05-10 17:39:24 +00:00
closeWindow();
2016-05-09 15:41:46 +00:00
});
2016-05-08 17:21:04 +00:00
}
2016-05-09 15:41:46 +00:00
});
}else{
sendAsync(data.url,data.payload,function(error,result){
if(result && result.error){
processMessage(data,sourceWindow);
2016-05-08 17:21:04 +00:00
}else{
2016-05-12 06:13:33 +00:00
sourceWindow.postMessage({id:data.id,result:result,error:error},firstUrl);
2016-05-10 17:41:46 +00:00
closeWindow();
2016-05-10 17:39:24 +00:00
}
2016-05-09 15:41:46 +00:00
});
showWaiting();
2016-05-08 17:21:04 +00:00
}
2016-05-10 17:41:46 +00:00
return false;
2016-05-09 15:41:46 +00:00
}
2016-05-10 17:41:46 +00:00
form.onsubmit = submitFunc;
confirmButton.onclick = submitFunc;
2016-05-09 15:41:46 +00:00
}
2016-05-09 15:41:46 +00:00
function needToAndCanUnlockAccount(address,url,callback){
sendAsync(url,{id:9999990,method:"eth_sign",params:[address,"0xc6888fa8d57087278718986382264244252f8d57087278718986382264244252f"]},function(error,result){
if(error || result.error){
sendAsync(url,{id:9999991,method:"personal_listAccounts",params:[]},function(error,result){
if(error || result.error){
callback(true,false);
2016-05-08 17:21:04 +00:00
}else{
2016-05-09 15:41:46 +00:00
callback(true,true);
2016-05-08 17:21:04 +00:00
}
2016-05-09 15:41:46 +00:00
});
}else{
callback(false);
2016-05-08 17:21:04 +00:00
}
2016-05-09 15:41:46 +00:00
});
}
2016-05-09 15:41:46 +00:00
function receiveMessage(event){
if(event.source != source){
return;
}
2016-05-09 15:41:46 +00:00
if(firstUrl){
if(firstUrl != event.origin){
return;
2016-05-10 17:39:24 +00:00
}
2016-05-09 15:41:46 +00:00
}else{
firstUrl = event.origin;
}
2016-05-10 17:41:46 +00:00
hideMessage();
noMessageReceivedYet = false;
2016-05-09 15:41:46 +00:00
var data = event.data;
2016-05-10 17:41:46 +00:00
try{
processMessage(data,event.source);
}catch(e){
2016-05-12 06:13:33 +00:00
event.source.postMessage({id:data.id,result:null,error:{message:"Could not process message data"},type:"notValid"},firstUrl);
2016-05-10 17:41:46 +00:00
showMessage("Error","The application has sent invalid data", function(){
closeWindow();
});
}
2016-05-09 15:41:46 +00:00
}
var allowedMethods = [
"web3_clientVersion"
,"web3_sha3"
,"net_version"
,"net_peerCount"
,"net_listening"
,"eth_protocolVersion"
,"eth_syncing"
,"eth_coinbase"
,"eth_mining"
,"eth_hashrate"
,"eth_gasPrice"
,"eth_accounts"
,"eth_blockNumber"
,"eth_getBalance"
,"eth_getStorageAt"
,"eth_getTransactionCount"
,"eth_getBlockTransactionCountByHash"
,"eth_getBlockTransactionCountByNumber"
,"eth_getUncleCountByBlockHash"
,"eth_getUncleCountByBlockNumber"
,"eth_getCode"
,"eth_sendRawTransaction"
,"eth_call"
,"eth_estimateGas"
,"eth_getBlockByHash"
,"eth_getBlockByNumber"
,"eth_getTransactionByHash"
,"eth_getTransactionByBlockHashAndIndex"
,"eth_getTransactionByBlockNumberAndIndex"
,"eth_getTransactionReceipt"
,"eth_getUncleByBlockHashAndIndex"
,"eth_getUncleByBlockNumberAndIndex"
,"eth_getCompilers"
,"eth_compileLLL"
,"eth_compileSolidity"
,"eth_compileSerpent"
,"eth_newFilter"
,"eth_newBlockFilter"
,"eth_newPendingTransactionFilter"
,"eth_uninstallFilter"
,"eth_getFilterChanges"
,"eth_getFilterLogs"
,"eth_getLogs"
,"eth_getWork"
,"eth_submitWork"
,"eth_submitHashrate"
// ,"shh_post"
// ,"shh_version"
// ,"shh_newIdentity"
// ,"shh_hasIdentity"
// ,"shh_newGroup"
// ,"shh_addToGroup"
// ,"shh_newFilter"
// ,"shh_uninstallFilter"
// ,"shh_getFilterChanges"
// ,"shh_getMessages"
];
function isMethodAllowed(method){
return allowedMethods.indexOf(method) != -1;
}
2016-05-09 15:41:46 +00:00
function processMessage(data, sourceWindow){
if(inIframe){
if(isMethodAllowed(data.payload.method)){
sendAsync(data.url,data.payload,function(error,result){
2016-05-12 06:13:33 +00:00
sourceWindow.postMessage({id:data.id,result:result,error:error},firstUrl);
});
2016-05-09 15:41:46 +00:00
}else{
2016-05-12 06:13:33 +00:00
sourceWindow.postMessage({id:data.id,result:null,error:{message:"method (" + data.payload.method + ") not allowed in iframe"},type:"notAllowed"},firstUrl);
}
}else if(data.payload.method == "eth_sendTransaction"){
var transactionInfo = null;
if(data.payload.params.length > 0){
if(data.payload.params[0]["gas"] && data.payload.params[0]["gasPrice"] && data.payload.params[0]["to"] && data.payload.params[0]["from"]){
transactionInfo = data.payload.params[0];
2016-05-09 15:41:46 +00:00
}
}
if(transactionInfo != null){
needToAndCanUnlockAccount(transactionInfo.from,data.url,function(requireUnlock,canUnlock){
if(requireUnlock && canUnlock){
askAuthorization(transactionInfo,data,true, sourceWindow);
}else if(!requireUnlock){
askAuthorization(transactionInfo,data,false,sourceWindow);
}else if(requireUnlock && !canUnlock){
var messageHtml = document.createElement('span');
addBlocky(messageHtml,transactionInfo.from);
messageHtml.appendChild(document.createElement('br'));
var span = document.createElement('span');
span.innerHTML = "You need to unlock your account first : <br/>" + transactionInfo.from;
messageHtml.appendChild(span);
showMessage("Account Locked",messageHtml,function(){
processMessage(data,sourceWindow);
}, "Done");
}
});
}else{
2016-05-12 06:13:33 +00:00
sourceWindow.postMessage({id:data.id,result:null,error:{message:"Need to specify from , to, gas and gasPrice"},type:"notValid"},firstUrl);
closeWindow();
2016-05-08 17:21:04 +00:00
}
2016-05-09 15:41:46 +00:00
}else{
2016-05-12 06:13:33 +00:00
sourceWindow.postMessage({id:data.id,result:null,error:{message:"method (" + data.payload.method + ") not allowed in popup"},type:"notAllowed"},firstUrl);
}
2016-05-09 15:41:46 +00:00
}
2016-05-10 17:41:46 +00:00
function checkMessageNotReceived(){
if(noMessageReceivedYet){
showMessage("Error","No transaction received. Please make sure popup are not blocked.", function(){
closeWindow();
});
}
}
setTimeout(checkMessageNotReceived,1000);
2016-05-09 15:41:46 +00:00
window.addEventListener("message", receiveMessage);
if(source){
2016-05-12 06:13:33 +00:00
source.postMessage("ready","*");
2016-05-09 15:41:46 +00:00
}
2016-05-09 15:41:46 +00:00
</script>
</body>
2016-05-08 17:21:04 +00:00
</html>
2016-05-12 08:34:19 +00:00
```