use universal-dapp interface

Universal-dapp was originaly factored out of browser-solidity to modularise
the contract interface component. And so is a direct decended of the
code it removes/replaces. Adding a number of features:

- constants are shown by default when appropriate
- all methods show gas execution costs when run
- smoothes the path to interacting with a local node aswell as vm
- allows instanciating contracts from an address
- shows events where supported
pull/1/head
d11e9 9 years ago
parent 4cffd49bfe
commit bf0acf274b
  1. 137
      index.html
  2. 335
      libs/universal-dapp.js
  3. 103
      stylesheets/browser-solidity.css
  4. 270
      stylesheets/universal-dapp.css

@ -30,6 +30,7 @@ THE SOFTWARE.
<title>Solidity realtime compiler and runtime</title>
<link rel="stylesheet" href="stylesheets/styles.css">
<link rel="stylesheet" href="stylesheets/pygment_trac.css">
<link rel="stylesheet" href="stylesheets/universal-dapp.css">
<link rel="stylesheet" href="stylesheets/browser-solidity.css">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style type="text/css">
@ -41,6 +42,7 @@ THE SOFTWARE.
<script src="libs/mode-solidity.js"></script>
<script src="bin/soljson-latest.js"></script>
<script src="libs/ethereumjs-vm.js"></script>
<script src="libs/universal-dapp.js"></script>
<script src="libs/web3.min.js"></script>
<script src="ballot.sol.js"></script>
@ -450,27 +452,27 @@ THE SOFTWARE.
};
var renderContracts = function(data, source) {
$('#output').empty();
console.log( "rendering ", data)
for (var contractName in data.contracts) {
var contract = data.contracts[contractName];
var title = $('<h3 class="title"/>').text(contractName);
var contractOutput = $('<div class="contractOutput"/>')
.append(title);
var body = $('<div class="body" />')
contractOutput.append(body);
if (contract.bytecode.length > 0)
title.append($('<div class="size"/>').text((contract.bytecode.length / 2) + ' bytes'))
body.append(getExecuteInterface(contract, contractName))
.append(tableRow('Bytecode', contract.bytecode));
body.append(tableRow('Interface', contract['interface']))
var dapp = new UniversalDApp([{
name: contractName,
interface: contract['interface'],
bytecode: contract.bytecode
}], {
vm: true,
removable: false,
removable_instances: true
});
var $contractOutput = dapp.render();
$contractOutput.append(textRow('Interface', contract['interface']))
.append(textRow('Web3 deploy', gethDeploy(contractName.toLowerCase(),contract['interface'],contract.bytecode), 'deploy'))
.append(textRow('uDApp', combined(contractName,contract['interface'],contract.bytecode), 'deploy'))
.append(getDetails(contract, source, contractName));
$('#output').append(contractOutput);
title.click(function(ev){ $(this).parent().toggleClass('hide') });
console.log("contract output", $contractOutput )
$('#output').append( $contractOutput );
}
$('.col2 input,textarea').click(function() { this.select(); });
};
var tableRowItems = function(first, second, cls) {
@ -491,7 +493,7 @@ THE SOFTWARE.
cls);
};
var getDetails = function(contract, source, contractName) {
var button = $('<button>Details</button>');
var button = $('<button>Toggle Details</button>');
var details = $('<div style="display: none;"/>')
.append(tableRow('Solidity Interface', contract.solidity_interface))
.append(tableRow('Opcodes', contract.opcodes));
@ -513,7 +515,7 @@ THE SOFTWARE.
button.click(function() { detailsOpen[contractName] = !detailsOpen[contractName]; details.toggle(); });
if (detailsOpen[contractName])
details.show();
return $('<div/>').append(button).append(details);
return $('<div class="contractDetails"/>').append(button).append(details);
};
var formatGasEstimates = function(data) {
var gasToText = function(g) { return g === null ? 'unknown' : g; }
@ -552,35 +554,8 @@ THE SOFTWARE.
return text;
};
$('.asmOutput button').click(function() {$(this).parent().find('pre').toggle(); })
// ----------------- VM ----------------------
var stateTrie = new EthVm.Trie();
var vm = new EthVm.VM(stateTrie);
//@todo this does not calculate the gas costs correctly but gets the job done.
var identityCode = 'return { gasUsed: 1, return: opts.data, exception: 1 };';
var identityAddr = ethUtil.pad(new Buffer('04', 'hex'), 20)
vm.loadPrecompiled(identityAddr, identityCode);
var secretKey = '3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511'
var publicKey = '0406cc661590d48ee972944b35ad13ff03c7876eae3fd191e8a2f77311b0a3c6613407b5005e63d7d8d76b89d5f900cde691497688bb281e07a5052ff61edebdc0'
var address = ethUtil.pubToAddress(new Buffer(publicKey, 'hex'));
$('#txorigin').text('0x' + address.toString('hex'));
var account = new EthVm.Account();
account.balance = 'f00000000000000001';
var nonce = 0;
stateTrie.put(address, account.serialize());
var runTx = function(data, to, cb) {
var tx = new EthVm.Transaction({
nonce: new Buffer([nonce++]), //@todo count beyond 255
gasPrice: '01',
gasLimit: '3000000000', // plenty
to: to,
data: data
});
tx.sign(new Buffer(secretKey, 'hex'));
vm.runTx({tx: tx}, cb);
};
$('.asmOutput button').click(function() {$(this).parent().find('pre').toggle(); });
var getConstructorInterface = function(abi) {
var funABI = {'name':'','inputs':[],'type':'constructor','outputs':[]};
@ -592,78 +567,6 @@ THE SOFTWARE.
return funABI;
};
var getCallButton = function(args) {
// args.abi, args.bytecode [constr only], args.address [fun only]
// args.appendFunctions [constr only]
var isConstructor = args.bytecode !== undefined;
var fun = new web3.eth.function(args.abi);
var inputs = '';
$.each(args.abi.inputs, function(i, inp) {
if (inputs != '') inputs += ', ';
inputs += inp.type + ' ' + inp.name;
});
var inputField = $('<input/>').attr('placeholder', inputs);
var outputSpan = $('<div class="output"/>');
var button = $('<button/>')
.text(args.bytecode ? 'Create' : fun.displayName())
.click(function() {
var funArgs = $.parseJSON('[' + inputField.val() + ']');
var data = fun.toPayload(funArgs).data;
if (data.slice(0, 2) == '0x') data = data.slice(2);
if (isConstructor)
data = args.bytecode + data.slice(8);
outputSpan.text('...');
runTx(data, args.address, function(err, result) {
if (err)
outputSpan.text(err);
else if (isConstructor) {
outputSpan.text(' Creation used ' + result.vm.gasUsed.toString(10) + ' gas.');
args.appendFunctions(result.createdAddress);
} else {
var outputObj = fun.unpackOutput('0x' + result.vm.return.toString('hex'));
outputSpan.text(' Returned: ' + JSON.stringify(outputObj));
}
});
});
if (!isConstructor)
button.addClass('runButton');
var c = $('<div class="contractProperty"/>')
.append(button);
if (args.abi.inputs.length > 0)
c.append(inputField);
return c.append(outputSpan);
};
var getExecuteInterface = function(contract, name) {
var abi = $.parseJSON(contract.interface);
var execInter = $('<div/>');
var funABI = getConstructorInterface(abi);
var appendFunctions = function(address) {
var instance = $('<div class="contractInstance"/>');
var title = $('<span class="title"/>').text('Contract at ' + address.toString('hex'));
instance.append(title);
$.each(abi, function(i, funABI) {
if (funABI.type != 'function') return;
instance.append(getCallButton({
abi: funABI,
address: address
}));
});
execInter.append(instance);
title.click(function(ev){ $(this).parent().toggleClass('hide') });
};
if (contract.bytecode.length > 0)
execInter
.append(getCallButton({
abi: funABI,
bytecode: contract.bytecode,
appendFunctions: appendFunctions
}));
return execInter;
};
</script>
</body>
</html>

@ -0,0 +1,335 @@
function UniversalDApp (contracts, options) {
this.options = options || {};
this.$el = $('<div class="udapp" />');
this.contracts = contracts;
if (web3.currentProvider) {
} else if (options.vm) {
this.stateTrie = new EthVm.Trie();
this.vm = new EthVm.VM(this.stateTrie);
//@todo this does not calculate the gas costs correctly but gets the job done.
this.identityCode = 'return { gasUsed: 1, return: opts.data, exception: 1 };';
this.identityAddr = ethUtil.pad(new Buffer('04', 'hex'), 20)
this.vm.loadPrecompiled(this.identityAddr, this.identityCode);
this.secretKey = '3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511'
this.publicKey = '0406cc661590d48ee972944b35ad13ff03c7876eae3fd191e8a2f77311b0a3c6613407b5005e63d7d8d76b89d5f900cde691497688bb281e07a5052ff61edebdc0'
this.address = ethUtil.pubToAddress(new Buffer(this.publicKey, 'hex'));
this.account = new EthVm.Account();
this.account.balance = 'f00000000000000001';
this.nonce = 0;
this.stateTrie.put(this.address, this.account.serialize());
} else {
var host = options.host || "localhost";
var port = options.port || "8545";
var rpc_url = 'http://' + host + ':' + port;
web3.setProvider( new web3.providers.HttpProvider( rpc_url ) );
}
}
UniversalDApp.prototype.render = function () {
if (this.contracts.length == 0) {
this.$el.append( this.getABIInputForm() );
} else {
for (var c in this.contracts) {
var $contractEl = $('<div class="contract"/>');
if (this.contracts[c].address) {
this.getInstanceInterface(this.contracts[c], this.contracts[c].address, $contractEl );
} else {
var $title = $('<span class="title"/>').text( this.contracts[c].name );
$contractEl.append( $title ).append( this.getCreateInterface( $contractEl, this.contracts[c]) );
}
this.$el.append( $contractEl );
}
}
$legend = $('<div class="legend" />')
.append( $('<div class="attach"/>').text('Attach') )
.append( $('<div class="transact"/>').text('Transact') )
.append( $('<div class="call"/>').text('Call') )
this.$el.append( $('<div class="poweredBy" />')
.html("<a href='http://github.com/d11e9/universal-dapp'>Universal ÐApp</a> powered by The Blockchain") )
this.$el.append( $legend )
return this.$el;
}
UniversalDApp.prototype.getABIInputForm = function (cb){
var self = this;
var $el = $('<div class="udapp-setup" />');
var $jsonInput = $('<textarea class="json" placeholder=\'[ { "name": name, "bytecode": bytyecode, "interface": abi }, { ... } ]\'/>')
var $createButton = $('<button class="udapp-create"/>').text('Create a Universal ÐApp')
$createButton.click(function(ev){
var contracts = $.parseJSON( $jsonInput.val() );
if (cb) {
var err = null;
var dapp = null;
try {
dapp = new UniversalDApp( contracts, self.options );
} catch(e) {
err = e;
}
cb( err, dapp )
} else {
self.contracts = contracts;
self.$el.empty().append( self.render() )
}
})
$el.append( $jsonInput ).append( $createButton )
return $el;
}
UniversalDApp.prototype.getCreateInterface = function ($container, contract) {
var self = this;
var $createInterface = $('<div class="create"/>');
if (this.options.removable) {
var $close = $('<div class="udapp-close" />')
$close.click( function(){ self.$el.remove(); } )
$createInterface.append( $close );
}
var $newButton = this.getInstanceInterface( contract )
var $atButton = $('<button class="atAddress"/>').text('At Address').click( function(){ self.clickContractAt( self, $container, contract ) } );
$createInterface.append( $atButton ).append( $newButton );
return $createInterface;
}
UniversalDApp.prototype.getInstanceInterface = function (contract, address, $target) {
var self = this;
var abi = JSON.parse(contract.interface).sort(function(a,b){
if (a.name > b.name) return -1;
else return 1;
}).sort(function(a,b){
if (a.constant == true) return -1;
else return 1;
});
var funABI = this.getConstructorInterface(abi);
var $createInterface = $('<div class="createContract"/>');
var appendFunctions = function (address, $el){
var $instance = $('<div class="instance"/>');
if (self.options.removable_instances) {
var $close = $('<div class="udapp-close" />')
$close.click( function(){ $instance.remove(); } )
$instance.append( $close );
}
var $title = $('<span class="title"/>').text( contract.name + " at " + (self.options.vm ? '0x' : '') + address.toString('hex') );
$title.click(function(){
$instance.toggleClass('hide');
});
$events = $('<div class="events"/>');
if (!self.options.vm){
var jsInterface = web3.eth.contract(abi).at(address)
var eventFilter = jsInterface.allEvents();
eventFilter.watch(function(err,response){
$event = $('<div class="event" />')
var $close = $('<div class="udapp-close" />')
$close.click( function(){ $event.remove(); } )
$event.append( $('<span class="name"/>').text(response.event) )
.append( $('<span class="args" />').text( JSON.stringify(response.args, null, 2) ) )
.append( $close );
$events.append( $event )
})
}
$instance.append( $title )
$.each(abi, function(i, funABI) {
if (funABI.type != 'function') return;
$instance.append(self.getCallButton({
abi: funABI,
address: address
}));
});
($el || $createInterface ).append( $instance.append( $events ) )
}
if (!address || !$target) {
$createInterface.append( this.getCallButton({
abi: funABI,
bytecode: contract.bytecode,
appendFunctions: appendFunctions
}));
} else {
appendFunctions( address, $target );
}
return $createInterface;
}
UniversalDApp.prototype.getConstructorInterface = function(abi) {
var funABI = {'name':'','inputs':[],'type':'constructor','outputs':[]};
for (var i = 0; i < abi.length; i++)
if (abi[i].type == 'constructor') {
funABI.inputs = abi[i].inputs || [];
break;
}
return funABI;
}
UniversalDApp.prototype.getCallButton = function(args) {
var self = this;
// args.abi, args.bytecode [constr only], args.address [fun only]
// args.appendFunctions [constr only]
var isConstructor = args.bytecode !== undefined;
var lookupOnly = ( args.abi.constant && !isConstructor );
var fun = new web3.eth.function(args.abi);
var inputs = '';
$.each(args.abi.inputs, function(i, inp) {
if (inputs != '') inputs += ', ';
inputs += inp.type + ' ' + inp.name;
});
if (!args.bytecode && !fun.displayName()) return;
var inputField = $('<input/>').attr('placeholder', inputs);
var $outputOverride = $('<div class="value" />');
var outputSpan = $('<div class="output"/>');
var getReturnOutput = function(result) {
var returnName = lookupOnly ? 'Value' : 'Result';
var returnCls = lookupOnly ? 'value' : 'returned';
return $('<div class="' + returnCls + '">').html('<strong>' + returnName + ':</strong> ' + JSON.stringify( result, null, 2 ) )
}
var getGasUsedOutput = function (result) {
var $gasUsed = $('<div class="gasUsed">')
var caveat = lookupOnly ? '<em>(<a href="#" title="Cost only applies when called by a contract">caveat</a>)</em>' : '';
if (result.gasUsed) {
var gas = result.gasUsed.toString(10)
$gasUsed.html('<strong>Cost:</strong> ' + gas + ' gas. ' + caveat )
}
return $gasUsed;
}
var getOutput = function() {
var values = Array.prototype.slice.call(arguments);
var $result = $('<div class="result" />');
var $close = $('<div class="udapp-close" />')
$close.click( function(){ $result.remove(); } )
$result.append( $close );
for( var v in values ) { $result.append( values[v] ); }
return $result;
}
var handleCallButtonClick = function( ev ) {
var funArgs = $.parseJSON('[' + inputField.val() + ']');
var data = fun.toPayload(funArgs).data;
if (data.slice(0, 2) == '0x') data = data.slice(2);
if (isConstructor) data = args.bytecode + data.slice(8);
var $result = getOutput( $('<a class="waiting" href="#" title="Waiting for transaction to be mined.">Polling for tx receipt...</a>') );
if (lookupOnly && !inputs.length) {
$outputOverride.html( $result )
} else {
outputSpan.append( $result );
}
self.runTx(data, args, function(err, result) {
if (err) {
$result.replaceWith( getOutput( $('<span/>').text(err).addClass('error') ) );
} else if (self.options.vm && isConstructor) {
$result.replaceWith( getOutput( getGasUsedOutput( result ) ) );
args.appendFunctions(result.createdAddress);
} else if (self.options.vm){
var outputObj = fun.unpackOutput('0x' + result.vm.return.toString('hex'));
$result.replaceWith( getOutput( getReturnOutput( outputObj ), getGasUsedOutput( result.vm ) ) );
} else if (args.abi.constant && !isConstructor) {
$result.replaceWith( getOutput( getReturnOutput( result ) ) );
} else {
function tryTillResponse (txhash, done) {
web3.eth.getTransactionReceipt(result, testResult );
function testResult (err, address) {
if (!err && !address) {
console.log( "Polling for tx receipt....")
setTimeout( function(){ tryTillResponse(txhash, done) }, 500)
} else done( err, address )
}
}
tryTillResponse( result, function(err, result) {
if (isConstructor) {
$result.html('');
args.appendFunctions(result.contractAddress);
} else $result.replaceWith( getOutput( getReturnOutput( result ), getGasUsedOutput( result ) ) );
})
}
});
}
var button = $('<button />')
.addClass( 'call' )
.text(args.bytecode ? 'Create' : fun.displayName())
.click( handleCallButtonClick );
if (lookupOnly && !inputs.length) {
handleCallButtonClick();
}
var $contractProperty = $('<div class="contractProperty"/>');
$contractProperty
.toggleClass( 'constant', !isConstructor && args.abi.constant )
.toggleClass( 'hasArgs', args.abi.inputs.length > 0)
.toggleClass( 'constructor', isConstructor)
.append(button)
.append( (lookupOnly && !inputs.length) ? $outputOverride : inputField );
return $contractProperty.append(outputSpan);
}
UniversalDApp.prototype.clickNewContract = function ( self, $contract, contract ) {
$contract.append( self.getInstanceInterface(contract) );
}
UniversalDApp.prototype.clickContractAt = function ( self, $contract, contract ) {
var address = prompt( "What Address is this contract at in the Blockchain? ie: '0xdeadbeaf...'" )
self.getInstanceInterface(contract, address, $contract );
}
UniversalDApp.prototype.runTx = function( data, args, cb) {
var to = args.address;
var constant = args.abi.constant;
var isConstructor = args.bytecode !== undefined;
console.log( "runtx (" + args.abi.name + ") data: ", data )
console.log( "runtx (" + args.abi.name + ") to:", to )
if (!this.vm) {
if (constant && !isConstructor) {
var func = web3.eth.contract( [args.abi] ).at( to );
func[args.abi.name].call( cb );
} else {
web3.eth.sendTransaction({
from: web3.eth.accounts[0],
to: to,
data: data,
gas: 1000000
}, function(err, resp) {
console.log( 'sendTx callback:', err, resp )
cb( err, resp )
})
}
} else {
try {
var tx = new EthVm.Transaction({
nonce: new Buffer([this.nonce++]), //@todo count beyond 255
gasPrice: '01',
gasLimit: '3000000',
to: to,
data: data
});
tx.sign(new Buffer(this.secretKey, 'hex'));
this.vm.runTx({tx: tx}, cb);
} catch (e) {
cb( e, null );
}
}
}

@ -119,9 +119,21 @@ body {
float: left;
}
#output .poweredBy,
#output .legend { display: none; }
#output .udapp {
border: 0 none;
box-shadow: none;
}
.row {
overflow: auto;
display: block;
clear: both;
margin: 0.5em;
}
.gethDeployText {
border-color: #bebebe;
height: 2.5em;
@ -129,88 +141,17 @@ body {
display: block;
}
.contractInstance {
background-color: #ccc;
margin-bottom: 1em;
padding: 0.6em;
}
.contractInstance.hide > *:not(.title) {
display: none;
}
.contractInstance .contractProperty input,
.contractInstance .contractProperty button {
text-align: left;
padding: 0.3em;
width: 50%;
margin: 0;
}
.contractOutput .contractInstance .contractProperty {
margin-bottom: 0;
}
.contractOutput .contractInstance .contractProperty .output {
padding: 0.4em;
background-color: #333;
color: white;
margin-bottom: 1em;
display: block;
}
.contractInstance .contractProperty .output:empty { display: none; }
.contractOutput {
border-bottom: 1px dotted black;
padding: 0.6em;
box-sizing: border-box;
}
.contractOutput .contractProperty {
margin-bottom: 0.6em;
}
.contractOutput .contractProperty .output {
display: inline;
}
.contractOutput .body {
margin-top: 10px;
}
.contractOutput.hide {
background-color: #4C4C67;
color: white;
}
.contractOutput.hide > *:not(.title) {
display: none;
}
.title {
margin: 0;
.contractDetails button {
background-color: transparent;
border: 0 none;
padding: 0;
display: inline-block;
text-decoration: underline;
color: blue;
cursor: pointer;
font-family: monospace;
font-weight: bold;
}
.title:before {
content: "\25BC";
opacity: 0.5;
margin-right: 0.4em;
font-size: 10px;
}
.hide > .title:before {
content: "\25B6";
}
.contractOutput > .title {
border-bottom: #4C4C67;
}
.title .size {
font-weight: normal;
float: right;
width: auto;
min-width: 4em;
margin-bottom: 1em;
}
.error {

@ -0,0 +1,270 @@
.udapp {
padding: 1em;
border: 1px dotted #4D5686;
position: relative;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
box-sizing: border-box;
overflow: auto;
}
.udapp a {
color: #7A7AE2;
}
.udapp a:visited {
color: #7A7AE2;
}
.udapp input,
.udapp button,
.udapp-setup textarea,
.udapp-setup button {
display: block;
width: 100%;
padding: 0.6em;
box-sizing: border-box;
border: 1px solid rgba( 0,0,0,0.3 );
border-radius: 0.5em;
}
.udapp-setup textarea {
border-radius: 0;
margin-bottom: 1em;
height: 7em;
}
.udapp button {
min-width: 8em;
}
.udapp-setup button {
background-color: #556DF3;
color: white;
cursor: pointer;
}
.udapp .contract {
margin-bottom: 1em;
}
.udapp .create {
overflow: auto;
margin-bottom: 1em;
}
.udapp .title {
margin-bottom: 0.4em;
display: inline-block;
padding: 0.2em;
background-color: rgba( 255,255,255,0.5 );
display: block;
font-weight: bold;
padding-right: 2em;
word-wrap: break-word;
}
.udapp .output {
padding: 1em;
clear: both;
word-wrap: break-word;
}
.udapp .constructor > .output {
padding-right: 1em;
}
.udapp .output .error {
color: red;
}
.udapp .output .result {
position: relative;
margin-bottom: 0.5em;
white-space: pre;
}
.udapp .result { position: relative; }
.udapp .output .result .returned,
.udapp .output .result .value,
.udapp .output .result .waiting,
.udapp .output .result .gasUsed {
padding-right: 2em;
word-wrap: break-word;
}
.udapp .output .result:last-child { margin: 0; }
.udapp .output:empty {
display: none;
}
.udapp-close:before {
position: absolute;
top: .4em;
right: .4em;
width: 1.5em;
height: 1.5em;
text-align: center;
content: "x";
cursor: pointer;
}
.udapp .instance {
padding: 0.4em;
background-color: #ECD7D7;
margin-bottom: 1em;
position: relative;
border: 1px solid #999;
}
.udapp .instance:last-child {
margin-bottom: 0;
}
.udapp .instance .title {
cursor: pointer;
}
.udapp .instance.hide .title {
margin-bottom: 0;
padding-right: 1.5em;
word-wrap: break-word;
}
.udapp .instance .title:before {
content: "\25BC";
opacity: 0.5;
margin-right: 0.4em;
font-size: 10px;
}
.udapp .instance.hide > *:not(.title) {
display: none;
}
.udapp .instance.hide > .title:before {
content: "\25B6";
}
.udapp .contractProperty {
overflow: auto;
margin-bottom: 0.4em;
}
.udapp input,
.udapp button {
width: 33%;
display: block;
float: left;
}
.udapp .atAddress {
background-color: #62B762;
margin-right: 1em;
border-radius: 0.5em;
}
.udapp input { border-left: 0 none;}
.udapp button {
background-color: #666;
color: white;
cursor: pointer;
}
.udapp .instance input,
.udapp .instance button {
width: 50%;
float: left;
}
.udapp .contractProperty.hasArgs input,
.udapp .contractProperty.hasArgs button {
width: 50%;
}
.udapp .contractProperty .call {
background-color: #D42828;
}
.udapp .contractProperty.constant .call {
background-color: #556DF3;
}
.udapp .contractProperty input {
display: none;
}
.udapp .contractProperty > .value {
padding: 0 0.4em;
box-sizing: border-box;
width: 50%;
float: left;
word-wrap: break-word;
}
.udapp .contractProperty.hasArgs input {
display: block;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.udapp .contractProperty.hasArgs button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 0;
}
.udapp .events:not(:empty):before {
content: "Events";
font-weight: bold;
display: block;
margin-bottom: 0.4em;
}
.udapp .events .event {
padding: 0.4em;
position: relative;
word-wrap: break-word;
padding-right: 3em;
background-color: white;
margin-bottom: 0.5em;
white-space: pre;
}
.udapp .events .event .name { margin-right: 0.5em; }
.udapp .legend {
font-size: 12px;
float: left;
color: #666;
}
.udapp .legend div {
display: inline-block;
margin-right: 0.5em;
}
.udapp .legend div:before {
content: ".";
color: transparent;
display: inline-block;
background-color: #ccc;
height: 1em;
margin-right: 0.5em;
width: 1em;
}
.udapp .legend .attach:before { background-color: #62B762; }
.udapp .legend .transact:before { background-color: #D42828; }
.udapp .legend .call:before { background-color: #556DF3; }
.udapp .poweredBy {
float: right;
color: #666;
font-size: 12px;
}
Loading…
Cancel
Save