commit
476c26119a
|
@ -3,7 +3,13 @@ node_modules
|
|||
node_modules/*
|
||||
deploy/db/*
|
||||
deploy/keys/*
|
||||
deploy/faucet/*
|
||||
|
||||
deploy/faucet/node_modules
|
||||
deploy/faucet/tweet-db
|
||||
deploy/faucet/config.json
|
||||
deploy/faucet/public/assets/javascripts/application.js
|
||||
deploy/faucet/public/assets/stylesheets/application.css
|
||||
|
||||
!.gitkeep
|
||||
*.log
|
||||
.mykeys.json
|
|
@ -16,7 +16,7 @@ You will have to pollute your system a little for this to work. Luckily, it work
|
|||
2. If you want to add some pre-created private keys, add them to the `.mykeys` file.
|
||||
3. Run `node start.js`. Optionally, pass in a `v` argument to autogenerate that many validators (`v=10`) and/or the `mykeys` argument to make the script read the keys specified in step 2.
|
||||
|
||||
The blockchain database will be stored in the `deploy/db` subfolder. The `deploy/keys` subfolder will have keys for relevant accounts generated, including the address to the deposit contract. The `deploy/faucet` folder will contain a simple web UI for a faucet.
|
||||
The blockchain database will be stored in the `deploy/db` subfolder. The `deploy/keys` subfolder will have keys for relevant accounts generated, including the address to the deposit contract. The `deploy/faucet` folder will contain a simple web UI for a faucet. See hosting below for how to run it.
|
||||
|
||||
#### Flags
|
||||
|
||||
|
@ -30,8 +30,8 @@ Augment `start.js` with flags, .e.g. `node start.js v=50 mykeys`:
|
|||
The generator is deterministic. You always end up with the same addresses, accounts and balances if you use the same mnemonic and `.mykeys` list. Thus, to host it somewhere, simply clone this repo to the server and run it the same way you do locally.
|
||||
|
||||
- `node start.js` will run the blockchain and start the server in listen mode with RPC/Web3 allowed
|
||||
- `yarn run faucet --port 8080` will host the ether faucet at `localhost:8080`
|
||||
- `yarn run validator-ui --port 8081` will host the validator UI at `localhost:8081` // @TODO
|
||||
- `yarn faucet` will host the ether faucet at `localhost:5000`
|
||||
- @TODO `yarn validator-ui` will host the validator UI at `localhost:8081`
|
||||
|
||||
### Other commands
|
||||
|
||||
|
@ -39,7 +39,7 @@ The generator is deterministic. You always end up with the same addresses, accou
|
|||
|
||||
## Contributing
|
||||
|
||||
Please consider contributing PRs, we'd love the help!
|
||||
Please consider contributing PRs, we'd love the help! There's only one condition: please try to keep the dependencies to a minimum of minimums, and do NOT use something that needs [node-gyp](https://github.com/nodejs/node-gyp/issues/809).
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"environment": "prod",
|
||||
"debug": false,
|
||||
"Captcha": {
|
||||
"secret": "",
|
||||
"sitekey": ""
|
||||
},
|
||||
"Tweeter": {
|
||||
"similarityTreshold": 0.75,
|
||||
"cooldown": 8,
|
||||
"predefinedTweet": "I'm using the Thundercloud (https://github.com/swader/thundercloud) Faucet to get some Test Ether to launch an Ethereum 2.0 Genesis event",
|
||||
"predefinedHashTags": "ethereum, eth2, serenity"
|
||||
},
|
||||
"Ethereum": {
|
||||
"milliEtherToTransferWithTweet": 32000,
|
||||
"milliEtherToTransferWithoutTweet": 1000,
|
||||
"gasLimit": "21000",
|
||||
"prod": {
|
||||
"rpc": "http://127.0.0.1:8545",
|
||||
"account": "0x471e0575bFC76d7e189ab3354E0ecb70FCbf3E46",
|
||||
"privateKey": "2960a712dcbcd755be598955b08ee0f697b372aed31005bd55fd02949b6917bb"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const sass = require('gulp-sass');
|
||||
const sassGlob = require('gulp-sass-glob');
|
||||
const autoprefixer = require('gulp-autoprefixer');
|
||||
const uglifycss = require('gulp-uglifycss');
|
||||
const include = require('gulp-include');
|
||||
const addsrc = require('gulp-add-src');
|
||||
const order = require('gulp-order');
|
||||
const concat = require('gulp-concat');
|
||||
const concatCss = require('gulp-concat-css');
|
||||
const uglify = require('gulp-uglify');
|
||||
|
||||
gulp.task('sass', function() {
|
||||
return gulp.src([
|
||||
'./public/assets/stylesheets/*.scss',
|
||||
'./public/assets/stylesheets/sweetalert2.min.css'
|
||||
])
|
||||
.pipe(sassGlob())
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(autoprefixer())
|
||||
.pipe(concatCss('application.css'))
|
||||
.pipe(uglifycss())
|
||||
.pipe(gulp.dest('./public/assets/stylesheets/'));
|
||||
});
|
||||
|
||||
gulp.task('javascript', function() {
|
||||
return gulp.src('public/assets/javascripts/application/*.js')
|
||||
.pipe(addsrc('public/assets/javascripts/vendor/index.js'))
|
||||
.pipe(order([
|
||||
"public/assets/javascripts/vendor/index.js",
|
||||
"public/assets/javascripts/application/*.js"
|
||||
], {base: '.'}))
|
||||
.pipe(include())
|
||||
.pipe(concat('application.js'))
|
||||
// .pipe(uglify())
|
||||
.pipe(gulp.dest('public/assets/javascripts'));
|
||||
});
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('./public/assets/stylesheets/**/**/*.scss', ['sass']);
|
||||
gulp.watch('./public/assets/javascripts/application/*.js', ['javascript']);
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const bodyParser = require('body-parser');
|
||||
const path = require('path');
|
||||
|
||||
let app = express();
|
||||
|
||||
require('./src/helpers/blockchain-helper')(app);
|
||||
|
||||
let config;
|
||||
const configPath = './config.json';
|
||||
const configExists = fs.existsSync(configPath, fs.F_OK);
|
||||
if (configExists) {
|
||||
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
} else {
|
||||
return console.log('There is no config.json file');
|
||||
}
|
||||
app.config = config;
|
||||
|
||||
app.configureWeb3(config)
|
||||
.then(web3 => {
|
||||
app.web3 = web3;
|
||||
app.set("view engine", "pug");
|
||||
app.set("views", path.join(__dirname, "/public/views"));
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.use(bodyParser.json({
|
||||
limit: '50mb',
|
||||
}));
|
||||
app.use(bodyParser.urlencoded({
|
||||
limit: '50mb',
|
||||
extended: true,
|
||||
}));
|
||||
|
||||
require('./src/controllers/index')(app);
|
||||
|
||||
app.get('/', function(request, response) {
|
||||
response.render('index', {
|
||||
minAmount: app.config.Ethereum.milliEtherToTransferWithoutTweet / 1000,
|
||||
maxAmount: app.config.Ethereum.milliEtherToTransferWithTweet / 1000,
|
||||
sitekey: app.config.Captcha.sitekey,
|
||||
cooldown: app.config.Tweeter.cooldown,
|
||||
predefinedTweetUrl: encodeURI(
|
||||
"https://twitter.com/intent/tweet?text=" + app.config.Tweeter.predefinedTweet
|
||||
+ "&hashtags=" + app.config.Tweeter.predefinedHashTags
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
app.set('port', (process.env.PORT || 5000));
|
||||
|
||||
app.listen(app.get('port'), function () {
|
||||
console.log('Thundercloud faucet is running on port', app.get('port'));
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
return console.log(error);
|
||||
});
|
||||
|
||||
module.exports = app;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "thundercloud-faucet",
|
||||
"version": "1.0.0",
|
||||
"description": "Thundercloud faucet",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "yarn run sass && yarn run coffee && node index.js",
|
||||
"sass": "gulp sass",
|
||||
"coffee": "gulp javascript",
|
||||
"watch": "gulp watch"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swader/thundercloud"
|
||||
},
|
||||
"author": "swader",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"body-parser": "1.18.3",
|
||||
"dateformat": "^3.0.3",
|
||||
"ethereumjs-tx": "1.3.7",
|
||||
"express": "4.16.4",
|
||||
"level": "^4.0.0",
|
||||
"moment": "2.24.0",
|
||||
"page-scraper": "^2.0.5",
|
||||
"pug": "^2.0.3",
|
||||
"querystring": "0.2.0",
|
||||
"string-similarity": "^3.0.0",
|
||||
"typedarray-to-buffer": "^3.1.5",
|
||||
"web3": "^1.0.0-beta.34"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-add-src": "1.0.0",
|
||||
"gulp-autoprefixer": "6.0.0",
|
||||
"gulp-cli": "2.0.1",
|
||||
"gulp-concat": "2.6.1",
|
||||
"gulp-concat-css": "3.1.0",
|
||||
"gulp-include": "2.3.1",
|
||||
"gulp-order": "1.2.0",
|
||||
"gulp-postcss": "8.0.0",
|
||||
"gulp-sass": "4.0.2",
|
||||
"gulp-sass-glob": "1.0.9",
|
||||
"gulp-uglify": "3.0.1",
|
||||
"gulp-uglifycss": "1.1.0",
|
||||
"http-server": "0.11.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">8.0"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
|
@ -0,0 +1,53 @@
|
|||
$(function() {
|
||||
var loader = $(".loading-container");
|
||||
updateBalance();
|
||||
// on form submit
|
||||
$( "#faucetForm" ).submit(function( e ) {
|
||||
e.preventDefault();
|
||||
$this = $(this);
|
||||
loader.removeClass("hidden");
|
||||
var receiver = $("#receiver").val();
|
||||
$.ajax({
|
||||
url:"/",
|
||||
type:"POST",
|
||||
data: $this.serialize()
|
||||
}).done(function(data) {
|
||||
grecaptcha.reset();
|
||||
if (!data.success) {
|
||||
loader.addClass("hidden");
|
||||
console.log(data)
|
||||
console.log(data.error)
|
||||
swal("Error", data.error.message, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
$("#receiver").val('');
|
||||
loader.addClass("hidden");
|
||||
swal("Success",
|
||||
`0.05 🌩ETH has been successfully transferred to <a href="https://explorer.lisinski.online/tx/${data.success.txHash}" target="blank">${receiver}</a>`,
|
||||
"success"
|
||||
);
|
||||
updateBalance();
|
||||
}).fail(function(err) {
|
||||
grecaptcha.reset();
|
||||
console.log(err);
|
||||
loader.addClass("hidden");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function updateBalance() {
|
||||
$.ajax({
|
||||
url: "/health",
|
||||
type: "GET"
|
||||
}).done(function (data) {
|
||||
if (data.balanceInEth) {
|
||||
$(".footer-balance").text(data.balanceInEth + " 🌩ETH remaining")
|
||||
} else {
|
||||
$(".footer-balance").text("Balance not available");
|
||||
}
|
||||
}).fail(function(err) {
|
||||
$(".footer-balance").text("Balance not available");
|
||||
console.log(err);
|
||||
});
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,2 @@
|
|||
//=require sweetalert2.min.js
|
||||
//=require jquery.min.js
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
@import './index/*';
|
|
@ -0,0 +1,9 @@
|
|||
html,
|
||||
body,
|
||||
.content {
|
||||
color: #333;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
@mixin image-2x($image, $width: 100%, $height: 100%) {
|
||||
@media (min--moz-device-pixel-ratio: 1.3),
|
||||
(-o-min-device-pixel-ratio: 2.6/2),
|
||||
(-webkit-min-device-pixel-ratio: 1.3),
|
||||
(min-device-pixel-ratio: 1.3),
|
||||
(min-resolution: 1.3dppx) {
|
||||
background-image: url($image);
|
||||
background-size: $width $height;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
html,
|
||||
body,
|
||||
main {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
%btn {
|
||||
cursor: pointer;
|
||||
transition: 0.3s background-color;
|
||||
border-radius: 3px;
|
||||
border: 0;
|
||||
padding: 0 15px 0 15px;
|
||||
background-color: #08b3f2;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left 15px center;
|
||||
color: #fff;
|
||||
line-height: 36px;
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
background-color: #20bdf7;
|
||||
}
|
||||
|
||||
&-new {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAFVBMVEX///////////////////////////9nSIHRAAAABnRSTlMASUrk5udXTd49AAAAOUlEQVR42tXQsQEAIAgDQcAn+4+snRZxAK79KokrIcNBwgYdc0Migwxk8Qsd1TJWDf/KQWobqt+9G4coA99W7as5AAAAAElFTkSuQmCC);
|
||||
background-size: 12px 12px;
|
||||
}
|
||||
|
||||
&-vote {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAASCAMAAAB2Mu6sAAAAgVBMVEX///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+xZKUEAAAAKnRSTlMAAQIDPVBWYmNkZmpxeHp7fIOEhYaHiZeZmp6foKGio6TV2dze5unx9f63Mu9fAAAAgElEQVR4AXXO1w7CMBSD4VM2lFE2lD0I1O//gIBrCUUn/Hf5rCixRI3V695KeLMEcEn4Dp+C9z2+DZ0f6IXz4x8/0Sf63c/P9DEP7fDM5VkpZw8Ao9rX9JnVBWjJNvSlqW7F67GzAZdiS1+QooXNLaqvRe/6ZWquXuVcdW7XPJY3R9MVHwqyyb8AAAAASUVORK5CYII=);
|
||||
background-size: 12px 9px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
.hidden {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.pd-3 {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.mgl-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.mgl-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bring-to-front {
|
||||
z-index: 200;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
.row {
|
||||
margin: 0!important;
|
||||
}
|
||||
|
||||
.ft-img {
|
||||
width: 100%;
|
||||
max-height: 60px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.mastfoot {
|
||||
background-color: #04091e;
|
||||
color: #545454;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.center {
|
||||
position: relative;
|
||||
float: left;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.social-button {
|
||||
color: rgb(40, 196, 255);
|
||||
}
|
||||
|
||||
.footer-balance {
|
||||
color: #3b3c44;
|
||||
background-color: lightgray;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
.center-logo-img {
|
||||
display: block;
|
||||
margin: 5px auto;
|
||||
max-width: 50%;
|
||||
max-height: 80px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.masthead {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
padding-bottom: 10px;
|
||||
padding-top: 5px;
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
button,
|
||||
input,
|
||||
textarea {
|
||||
outline: none;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
input {
|
||||
transition: 0.3s border-color;
|
||||
width: 100%;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #eee;
|
||||
|
||||
&:focus {
|
||||
border-color: #08b3f2;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0 15px;
|
||||
height: 36px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.request-tokens-button {
|
||||
@extend %btn;
|
||||
|
||||
background-color: #08b3f2;
|
||||
|
||||
&:hover {
|
||||
background-color: #079dd4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* hide the blue outline */
|
||||
.form-control:focus {
|
||||
outline: 0 !important;
|
||||
border-color: initial;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
// Input card
|
||||
|
||||
.card-footer {
|
||||
font-style: italic;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.25rem !important;
|
||||
}
|
||||
|
||||
.card-flip {
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
.card-flip.flip .flip {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
.card-flip,
|
||||
.front,
|
||||
.back {
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
}
|
||||
|
||||
.flip {
|
||||
transition: 0.6s;
|
||||
transform-style: preserve-3d;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.front,
|
||||
.back {
|
||||
backface-visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.front {
|
||||
z-index: 2;
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
|
||||
.back {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
.card-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gray-button {
|
||||
background-color: #0d191d;
|
||||
}
|
||||
|
||||
.footer-button {
|
||||
float: right;
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
@keyframes fadeOut {
|
||||
0% {
|
||||
opacity: .2;
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: .2;
|
||||
transform: scale(0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 206px;
|
||||
margin: -30px 0 0 -111.5px;
|
||||
padding-top: 50px;
|
||||
|
||||
&:before {
|
||||
@include image-2x('../images/cloud.png', 206px);
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 206px;
|
||||
height: 50px;
|
||||
background-image: url("../images/cloud.png");
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
&-container {
|
||||
position: fixed;
|
||||
z-index: 1000000;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: fade-out(#1c1c21, 0.2);
|
||||
}
|
||||
|
||||
&-i {
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: fadeOut;
|
||||
animation-timing-function: linear;
|
||||
opacity:.2;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: .1s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: .2s;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
animation-delay: .3s;
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
animation-delay: .4s;
|
||||
}
|
||||
|
||||
&:nth-child(6) {
|
||||
animation-delay: .5s;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
main {
|
||||
padding-top: 2%;
|
||||
padding-bottom: 5%;
|
||||
background-image: url('../images/cloud.png');
|
||||
background-size: cover;
|
||||
overflow: auto;
|
||||
|
||||
}
|
||||
|
||||
// OVERLAY
|
||||
main::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(148, 146, 129, 0.9);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
width: 50%;
|
||||
}
|
||||
.row {
|
||||
width: auto;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.text-xs-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.g-recaptcha {
|
||||
display: inline-block;
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,133 @@
|
|||
html
|
||||
head
|
||||
<!------- META ------->
|
||||
meta(charset='utf-8')
|
||||
meta(name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no')
|
||||
// Meta Description & Keywords
|
||||
meta(name='description' content='The Thundercloud faucet lets you get 32 ether to deposit and become a test validator for Ethereum 2.0.')
|
||||
meta(name='keywords' content='blockchain, ethereum')
|
||||
// Favicon
|
||||
link(rel='shortcut icon' href='assets/images/cloud.png')
|
||||
// Site Title
|
||||
title Thundercloud faucet
|
||||
|
||||
<!------- CSS ------->
|
||||
link(rel='stylesheet' type='text/css' href='./assets/stylesheets/application.css')
|
||||
link(rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css' integrity='sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T' crossorigin='anonymous')
|
||||
link(href='https://fonts.googleapis.com/css?family=Poppins:300,400,500,700' rel='stylesheet')
|
||||
// Fontawesome icons
|
||||
link(rel='stylesheet' href='https://use.fontawesome.com/releases/v5.7.2/css/all.css' integrity='sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr' crossorigin='anonymous')
|
||||
|
||||
body
|
||||
.d-flex.w-100.h-100.mx-auto.flex-column
|
||||
header.masthead.mb-auto.bring-to-front
|
||||
.inner
|
||||
h1.hidden Thundercloud Testnet Faucet
|
||||
.allign-middle
|
||||
a(href='https://github.com/swader/thundercloud')
|
||||
img.center-logo-img(src='assets/images/cloud.png' alt='Thundercloud Logo')
|
||||
|
||||
main.inner.content(role='main')
|
||||
.container
|
||||
.row.justify-content-center
|
||||
.flex-container.bring-to-front
|
||||
.card-container
|
||||
.card-flip.noflip
|
||||
.flip
|
||||
.front
|
||||
.card.text-center
|
||||
.card-header
|
||||
| Thundercloud faucet
|
||||
.card-body
|
||||
form#faucetForm(action='/' method='POST')
|
||||
.form-group
|
||||
.input-group
|
||||
span.input-group-prepend
|
||||
span.input-group-text.bg-transparent.border-right-0
|
||||
i.fas.fa-wallet.fa-2x
|
||||
input#receiver.form-control.form-control-lg.py-2.border-left-0.border(name='receiver' type='text' placeholder='Enter wallet address...')
|
||||
span.input-group-append
|
||||
button.btn.btn-outline-secondary.border-left-0.border(type='button' data-toggle='popover' data-trigger='focus' title='Wallet Address' data-content='This address will get the Ether')
|
||||
i.fas.fa-info
|
||||
.form-group
|
||||
.input-group
|
||||
span.input-group-prepend
|
||||
span.input-group-text.bg-transparent.border-right-0
|
||||
i.fab.fa-twitter.fa-2x
|
||||
input#tweetUrl.form-control.form-control-lg.py-2.border-left-0.border(name='tweetUrl' type='text' placeholder='Enter tweet url...')
|
||||
span.input-group-append
|
||||
button.btn.btn-outline-secondary.border-left-0.border(type='button' data-toggle='popover' data-trigger='focus' title='Tweet Url' data-content='Tweet about Thundercloud, then paste tweet URL here.')
|
||||
i.fas.fa-info
|
||||
.form-group
|
||||
a.twitter-share-button(href=predefinedTweetUrl data-size='large')
|
||||
| Tweet about Thundercloud
|
||||
.form-group
|
||||
button#requestTokens.request-tokens-button(type='submit') Request #{maxAmount} Thundercloud Ether (🌩ETH)
|
||||
.card-footer.text-muted
|
||||
.footer-balance
|
||||
.footer-note
|
||||
| There's a cooldown of #{cooldown} hours in place.
|
||||
.footer-button
|
||||
button.request-tokens-button.gray-button.toggle I want #{minAmount} 🌩ETH
|
||||
.back
|
||||
.card.text-center
|
||||
.card-header
|
||||
| Thundercloud Faucet
|
||||
.card-body
|
||||
form#faucetFormMin(action='/' method='POST')
|
||||
.form-group
|
||||
.input-group
|
||||
span.input-group-prepend
|
||||
span.input-group-text.bg-transparent.border-right-0
|
||||
i.fas.fa-wallet.fa-2x
|
||||
input#receiverMin.form-control.form-control-lg.py-2.border-left-0.border(name='receiver' type='text' placeholder='Enter wallet address...')
|
||||
span.input-group-append
|
||||
button.btn.btn-outline-secondary.border-left-0.border(type='button' data-toggle='popover' data-trigger='focus' title='Wallet Address' data-content='This address will get the Ether.')
|
||||
i.fas.fa-info
|
||||
.form-group
|
||||
button.request-tokens-button(type='submit') Request #{minAmount} 🌩ETH
|
||||
.card-footer.text-muted
|
||||
.footer-balance
|
||||
.footer-button
|
||||
button.request-tokens-button.gray-button.toggle I want #{maxAmount} 🌩ETH
|
||||
.loading-container.hidden
|
||||
.loading
|
||||
.loading-i
|
||||
.loading-i
|
||||
.loading-i
|
||||
.loading-i
|
||||
.loading-i
|
||||
.loading-i
|
||||
script(src='./assets/javascripts/application.js?v=1.4' type='text/javascript')
|
||||
// jQuery, Popper.js, Bootstrap JS
|
||||
script(src='assets/javascripts/vendor/jquery-3.3.1.min.js' integrity='sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=' crossorigin='anonymous')
|
||||
script(src='assets/javascripts/vendor/popper.min.js' integrity='sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1' crossorigin='anonymous')
|
||||
script(src='assets/javascripts/vendor/bootstrap.min.js' integrity='sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM' crossorigin='anonymous')
|
||||
script.
|
||||
// Activate info popovers
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="popover"]').popover();
|
||||
});
|
||||
$('.popover-dismiss').popover({
|
||||
trigger: 'focus'
|
||||
});
|
||||
// Flip input card
|
||||
$('.toggle').on('click', function () {
|
||||
$('.card-flip').toggleClass("flip");
|
||||
});
|
||||
|
||||
script.
|
||||
// Activate tweet button
|
||||
window.twttr = (function (d, s, id) {
|
||||
var js, fjs = d.getElementsByTagName(s)[0], t = window.twttr || {};
|
||||
if (d.getElementById(id)) return t;
|
||||
js = d.createElement(s);
|
||||
js.id = id;
|
||||
js.src = "https://platform.twitter.com/widgets.js";
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
t._e = [];
|
||||
t.ready = function (f) {
|
||||
t._e.push(f);
|
||||
};
|
||||
return t;
|
||||
}(document, "script", "twitter-wjs"));
|
|
@ -0,0 +1,151 @@
|
|||
const EthereumTx = require('ethereumjs-tx');
|
||||
const { generateErrorResponse } = require('../helpers/generate-response');
|
||||
const { validateCaptcha } = require('../helpers/captcha-helper');
|
||||
const { debug } = require('../helpers/debug');
|
||||
const { checkIfValidTweet } = require('../helpers/tweet-helper');
|
||||
|
||||
module.exports = function (app) {
|
||||
const config = app.config;
|
||||
const web3 = app.web3;
|
||||
|
||||
const messages = {
|
||||
INVALID_ADDRESS: 'Invalid address',
|
||||
TX_HAS_BEEN_MINED_WITH_FALSE_STATUS: 'Transaction has been mined, but status is false',
|
||||
TX_HAS_BEEN_MINED: 'Tx has been mined',
|
||||
};
|
||||
|
||||
app.post('/', async function(request, response) {
|
||||
const isDebug = app.config.debug;
|
||||
debug(isDebug, "REQUEST:");
|
||||
debug(isDebug, request.body);
|
||||
|
||||
// const recaptureResponse = request.body["g-recaptcha-response"];
|
||||
// if (!recaptureResponse) {
|
||||
// const error = {
|
||||
// message: messages.INVALID_CAPTCHA,
|
||||
// };
|
||||
// return generateErrorResponse(response, error);
|
||||
// }
|
||||
|
||||
let captchaResponse;
|
||||
// Temp disable
|
||||
// try {
|
||||
// captchaResponse = await validateCaptcha(app, recaptureResponse);
|
||||
// } catch(e) {
|
||||
// return generateErrorResponse(response, e);
|
||||
// }
|
||||
|
||||
const receiver = request.body.receiver;
|
||||
if (await validateCaptchaResponse(captchaResponse, receiver, response)) {
|
||||
if (!web3.utils.isAddress(receiver)) {
|
||||
return generateErrorResponse(response, {message: messages.INVALID_ADDRESS});
|
||||
}
|
||||
const noTweet = !request.body.tweetUrl;
|
||||
if (noTweet || await validateTweet(request.body.tweetUrl, response)) {
|
||||
await checkBalanceStatus(response);
|
||||
await sendPOAToRecipient(web3, receiver, response, isDebug, noTweet);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/health', async function(request, response) {
|
||||
const resp = await checkBalanceStatus(response);
|
||||
response.send(resp);
|
||||
});
|
||||
|
||||
async function checkBalanceStatus(response) {
|
||||
let balanceInWei;
|
||||
let balanceInEth;
|
||||
const address = config.Ethereum[config.environment].account;
|
||||
// get balance
|
||||
try {
|
||||
balanceInWei = await web3.eth.getBalance(address);
|
||||
balanceInEth = await web3.utils.fromWei(balanceInWei, "ether");
|
||||
} catch (error) {
|
||||
return generateErrorResponse(response, error);
|
||||
}
|
||||
return {
|
||||
address,
|
||||
balanceInWei: balanceInWei,
|
||||
balanceInEth: Math.round(balanceInEth)
|
||||
};
|
||||
}
|
||||
|
||||
async function validateCaptchaResponse(captchaResponse, receiver, response) {
|
||||
// hack to disable captcha for now
|
||||
return true;
|
||||
if (!captchaResponse || !captchaResponse.success) {
|
||||
generateErrorResponse(response, {message: messages.INVALID_CAPTCHA});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function validateTweet(tweetUrl, response) {
|
||||
const resp = await checkIfValidTweet(tweetUrl);
|
||||
if (!resp.valid) {
|
||||
generateErrorResponse(response, {message: resp.message});
|
||||
}
|
||||
return resp.valid;
|
||||
}
|
||||
|
||||
async function sendPOAToRecipient(web3, receiver, response, isDebug, isWithoutTweet) {
|
||||
let senderPrivateKey = config.Ethereum[config.environment].privateKey;
|
||||
const privateKeyHex = Buffer.from(senderPrivateKey, 'hex');
|
||||
const gasPrice = web3.utils.toWei('1', 'gwei');
|
||||
const gasPriceHex = web3.utils.toHex(gasPrice);
|
||||
const gasLimitHex = web3.utils.toHex(config.Ethereum.gasLimit);
|
||||
const nonce = await web3.eth.getTransactionCount(config.Ethereum[config.environment].account);
|
||||
const nonceHex = web3.utils.toHex(nonce);
|
||||
const BN = web3.utils.BN;
|
||||
const miliEthToSend = (isWithoutTweet) ?
|
||||
config.Ethereum.milliEtherToTransferWithoutTweet : config.Ethereum.milliEtherToTransferWithTweet;
|
||||
const ethToSend = web3.utils.toWei(new BN(miliEthToSend), "milliether");
|
||||
const rawTx = {
|
||||
nonce: nonceHex,
|
||||
gasPrice: gasPriceHex,
|
||||
gasLimit: gasLimitHex,
|
||||
to: receiver,
|
||||
value: ethToSend,
|
||||
data: '0x00'
|
||||
};
|
||||
|
||||
const tx = new EthereumTx(rawTx);
|
||||
tx.sign(privateKeyHex);
|
||||
|
||||
const serializedTx = tx.serialize();
|
||||
|
||||
let txHash;
|
||||
web3.eth.sendSignedTransaction("0x" + serializedTx.toString('hex'))
|
||||
.on('transactionHash', (_txHash) => {
|
||||
txHash = _txHash
|
||||
})
|
||||
.on('receipt', (receipt) => {
|
||||
debug(isDebug, receipt);
|
||||
if (receipt.status == '0x1') {
|
||||
return sendRawTransactionResponse(txHash, response);
|
||||
} else {
|
||||
const error = {
|
||||
message: messages.TX_HAS_BEEN_MINED_WITH_FALSE_STATUS,
|
||||
};
|
||||
return generateErrorResponse(response, error);
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
return generateErrorResponse(response, error);
|
||||
});
|
||||
}
|
||||
|
||||
function sendRawTransactionResponse(txHash, response) {
|
||||
const successResponse = {
|
||||
code: 200,
|
||||
title: 'Success',
|
||||
message: messages.TX_HAS_BEEN_MINED,
|
||||
txHash: txHash
|
||||
};
|
||||
|
||||
response.send({
|
||||
success: successResponse
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
const Web3 = require('web3')
|
||||
|
||||
module.exports = function (app) {
|
||||
app.configureWeb3 = configureWeb3
|
||||
|
||||
function configureWeb3 (config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let web3
|
||||
if (typeof web3 !== 'undefined') {
|
||||
web3 = new Web3(web3.currentProvider)
|
||||
}
|
||||
else {
|
||||
web3 = new Web3(new Web3.providers.HttpProvider(config.Ethereum[config.environment].rpc))
|
||||
}
|
||||
|
||||
if (typeof web3 !== 'undefined') {
|
||||
return resolve(web3)
|
||||
}
|
||||
|
||||
reject({
|
||||
code: 500,
|
||||
title: "Error",
|
||||
message: "check RPC"
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
const querystring = require('querystring')
|
||||
const https = require('https')
|
||||
const { debug } = require('../helpers/debug')
|
||||
|
||||
function validateCaptcha (app, captchaResponse) {
|
||||
const config = app.config
|
||||
return new Promise((resolve, reject) => {
|
||||
const isDebug = app.config.debug
|
||||
const secret = config.Captcha.secret
|
||||
const post_data_json = {
|
||||
secret,
|
||||
"response": captchaResponse
|
||||
}
|
||||
|
||||
const post_data = querystring.stringify(post_data_json)
|
||||
|
||||
debug(isDebug, post_data_json)
|
||||
debug(isDebug, post_data)
|
||||
|
||||
const post_options = {
|
||||
host: 'www.google.com',
|
||||
port: '443',
|
||||
path: '/recaptcha/api/siteverify',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
|
||||
debug(isDebug, post_options)
|
||||
|
||||
const post_req = https.request(post_options, function (res) {
|
||||
res.setEncoding('utf8')
|
||||
let output = ''
|
||||
res.on('data', function (chunk) {
|
||||
output += chunk
|
||||
})
|
||||
|
||||
res.on('end', function () {
|
||||
debug(isDebug, "##############")
|
||||
debug(isDebug, 'Output from validateCaptcha: ')
|
||||
debug(isDebug, output)
|
||||
debug(isDebug, "##############")
|
||||
if (output) {
|
||||
debug(isDebug, JSON.parse(output))
|
||||
resolve(JSON.parse(output))
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
post_req.on('error', function (error) {
|
||||
debug(isDebug, error)
|
||||
reject(error)
|
||||
})
|
||||
post_req.write(post_data, 'binary', function(error) {
|
||||
if (error) debug(isDebug, error)
|
||||
})
|
||||
post_req.end()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { validateCaptcha }
|
|
@ -0,0 +1,6 @@
|
|||
function debug (isDebug, text) {
|
||||
if (isDebug) {
|
||||
console.log(text)
|
||||
}
|
||||
}
|
||||
module.exports = { debug }
|
|
@ -0,0 +1,13 @@
|
|||
function generateErrorResponse (response, err) {
|
||||
const out = {
|
||||
error: {
|
||||
code: err.code || 500,
|
||||
title: err.title || 'Error',
|
||||
message: err.message || 'Internal server error'
|
||||
}
|
||||
};
|
||||
console.log(err);
|
||||
response.send(out);
|
||||
}
|
||||
|
||||
module.exports = { generateErrorResponse };
|
|
@ -0,0 +1,93 @@
|
|||
const scrape = require('page-scraper');
|
||||
const level = require('level');
|
||||
const stringSimilarity = require('string-similarity');
|
||||
const app = require('../../index');
|
||||
|
||||
// similarity constants
|
||||
const referenceString = app.config.Tweeter.predefinedTweet + app.config.Tweeter.predefinedHashTags;
|
||||
const similarityThreshold = app.config.Tweeter.similarityTreshold || 0.8;
|
||||
|
||||
// defined as number of hours
|
||||
const timeoutThresholdInHours = app.config.Tweeter.cooldown || 6;
|
||||
|
||||
const db = level('tweet-db');
|
||||
|
||||
async function checkIfValidTweet (tweetUrl) {
|
||||
const response = {valid: false, message: ""};
|
||||
try {
|
||||
// Check if user claimed reward in last 8h
|
||||
const tweetUser = getTweetUsername(tweetUrl);
|
||||
await checkIfTimeoutExpired(tweetUser);
|
||||
// Check if tweet already used for reward
|
||||
await checkIfNewTweet(tweetUrl);
|
||||
// Check if tweet content is about Lisinski Testnet
|
||||
const tweetContent = await scrapeTweetContent(tweetUrl);
|
||||
checkIfValidTweetContent(tweetContent);
|
||||
// Tweet is valid, save record
|
||||
await saveTweetData(tweetUrl, tweetUser);
|
||||
response.valid = true;
|
||||
} catch (err) {
|
||||
response.message = err.message;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
async function saveTweetData(tweetUrl, tweetUser) {
|
||||
await db.put("tweet::" + tweetUrl, tweetUser);
|
||||
await db.put("user::" + tweetUser, Date.now());
|
||||
console.log(`${tweetUser} claimed reward for ${tweetUrl}.`)
|
||||
}
|
||||
|
||||
async function checkIfNewTweet(tweetUrl) {
|
||||
try {
|
||||
await db.get("tweet::" + tweetUrl);
|
||||
} catch (error) {
|
||||
if (error.type === 'NotFoundError') return;
|
||||
console.log(error.message);
|
||||
throw new Error(error.message);
|
||||
}
|
||||
throw new Error("This tweet already used for claiming LETH reward!");
|
||||
}
|
||||
|
||||
async function scrapeTweetContent(tweetUrl) {
|
||||
let content = '';
|
||||
try {
|
||||
const $ = await scrape(tweetUrl);
|
||||
content = $('.tweet-text').text();
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
if (content.length === 0) {
|
||||
throw new Error('Tweet url is not valid!');
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function checkIfValidTweetContent(tweetContent) {
|
||||
const similarity = stringSimilarity.compareTwoStrings(tweetContent, referenceString);
|
||||
if (similarity < similarityThreshold) {
|
||||
throw new Error('Tweet content is not valid, Lisinski Testnet must be mentioned in Tweet!');
|
||||
}
|
||||
}
|
||||
|
||||
function getTweetUsername(tweetUrl) {
|
||||
return tweetUrl.split("/")[3];
|
||||
}
|
||||
|
||||
async function checkIfTimeoutExpired(tweetUser) {
|
||||
let timestamp;
|
||||
try {
|
||||
timestamp = await db.get("user::" + tweetUser);
|
||||
} catch (error) {
|
||||
if (error.type !== 'NotFoundError') {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
}
|
||||
// check if timeout expired
|
||||
const hoursFromLastTweet = Math.abs(timestamp - Date.now()) / 36e5;
|
||||
if (hoursFromLastTweet <= timeoutThresholdInHours) {
|
||||
throw new Error(`Reward already claimed in last ${timeoutThresholdInHours} hours!`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkIfValidTweet };
|
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,7 @@
|
|||
"ganache-cli": "^6.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf deploy/db/*"
|
||||
"clean": "rm -rf deploy/db/*",
|
||||
"faucet": "cd deploy/faucet && yarn start"
|
||||
}
|
||||
}
|
||||
|
|
11
start.js
11
start.js
|
@ -96,7 +96,7 @@ provider.listAccounts().then(function(result){
|
|||
let mnemonic = process.env.mnemonic;
|
||||
let mnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic);
|
||||
|
||||
fs.writeFile("deploy/keys/faucetkey.txt", mnemonicWallet.privateKey, function(err) {
|
||||
fs.writeFile("deploy/keys/faucetkey.txt", mnemonicWallet.privateKey + ":" + mnemonicWallet.address, function(err) {
|
||||
if(err) {
|
||||
return console.log(err);
|
||||
}
|
||||
|
@ -189,11 +189,12 @@ async function makeValidatorDeposits() {
|
|||
let withdraw_pubkey = new keypair.Keypair(privateKey.PrivateKey.fromHexString(item.bls_key_withdraw)).publicKey.toHexString();
|
||||
|
||||
// Withdrawal credentials is the sha256 hash of the withdrawal pubkey (32 bytes), but the first byte of the hash is replaced with the prefix (currently 0 for version 0)
|
||||
//let withdrawal_credentials = "00" + sha256.sha256(withdraw_pubkey).slice(2); // 32 byte output
|
||||
let withdrawal_credentials = Buffer.from(sha256.arrayBuffer(withdraw_pubkey))
|
||||
let withdrawal_credentials_hex = "0x00" + sha256.sha256(withdraw_pubkey).slice(2); // 32 byte output
|
||||
let withdrawal_credentials = Buffer.from(sha256.arrayBuffer(withdraw_pubkey));
|
||||
withdrawal_credentials[0] = 0;
|
||||
|
||||
// Signature is technically bls_sign(signing_privkey, signing_root(deposit_data)) but due to the circular dependency the signature here is actually ignored (!!) and can be nothing, null, or random data.
|
||||
let signature_dd = Buffer.alloc(5);
|
||||
let signature_dd = Buffer.alloc(0);
|
||||
|
||||
// Put it together somehow
|
||||
let depositData = {
|
||||
|
@ -219,7 +220,7 @@ async function makeValidatorDeposits() {
|
|||
// A signer is needed to sign a transaction from a given account
|
||||
let wallet = new ethers.Wallet(item.pk, provider);
|
||||
contract = contract.connect(wallet);
|
||||
let tx = contract.deposit(sign_pubkey, withdrawal_credentials, signature_d).then(console.log);
|
||||
let tx = contract.deposit(signkeys.publicKey.toHexString(), withdrawal_credentials_hex, signature_d).then(console.log);
|
||||
|
||||
//console.log("Validator " + item.address + " is depositing 32 ether to the deposit contract at " + contractAddress + " via TX " + tx.hash);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue