概要
geth使ってパブリック・ネットワークのウォレットを作成しようと色々調べていたら、本番と同期した上でpersonalのapi開放しないと無理だったので、もっと敷居の低いwalletを作成する。
構成
──index.html
やること
- アドレス + private_keyで送金できるようにする
- 環境を選択できるようにする
- keystoreからprivate_keyを複合できるようにする
- 本番と同期はしない(同期まで2日くらいかかるし数十GBの容量食うし爆音だしでいいことない)
接続先
https://infura.io/
必要な情報を送ると、パプリック・ropstenなどのエンドポイントをメールで送ってくれる。
personalのAPIが開放されていないため、送金などは証明書付きで行う必要がある。
作成
1.js
必要なファイルをcdnにする
https://qiita.com/takanorig/items/89db46120d2ec171e3d8
- keythereum
- ethereumjs-tx
- web3(ver 1.0)
<script src="https://cdn.rawgit.com/ethereumjs/keythereum/622b3a4b/dist/keythereum.min.js"></script>
<script src="https://cdn.rawgit.com/ethereum/web3.js/1.0/dist/web3.min.js"></script>
<script src="https://cdn.rawgit.com/ethereumjs/browser-builds/2fb69a714afe092b06645286f14b94f41e5c062c/dist/ethereumjs-tx.js"></script>
※web3はver1.0(開発中)を利用するため、https://github.com/ethereum/web3.js
のブランチを『1.0』に切り替えて使用している
機能
- アカウントを作成 + keystore作成 + ローカル保存
async function generate_account() {
ret = await web3.eth.accounts.create();
s = web3.eth.accounts.encrypt(ret.privateKey, $("#apw").val());
var blob = new Blob([JSON.stringify(s)], { "type" : "application/octet-stream" });
url = URL.createObjectURL(blob);
window.open(url);
update_addr();
}
- keystoreからprivateKeyの復元
$('#check').click(function(e) {
var key = prompt('paste keystore(json_string)');
var keyObj = JSON.parse(key);
var password = prompt('password?');
var privateKey=keythereum.recover(password, keyObj);
console.log(privateKey.toString('hex'));
$("#address").val(keyObj.address);
$("#private_key").val(privateKey.toString('hex'));
});
- 証明書付き送金
async function main() {
// 送信元のアドレス (Walletの作成手順にあるwallet.getAddress()のメソッドによって得られたaddress)
const fromAddress = $("#address").val();
// 送信先のアドレス
const toAddress = $("#address_to").val();
// 送信先のアドレス
const gass_price = "0x" + Number($("#gass_price").val()).toString(16);
// 送信先のアドレス
const gass_limit = "0x" + Number($("#gass_limit").val()).toString(16);
// 著名付きトランザクションのnonce値として必要なため、送信元アドレスのTransaction countを取得する
const count = await web3.eth.getTransactionCount("0x" +fromAddress);
const countHex = `0x` + count.toString(16);
const money = "0x" + Number(web3.utils.toWei($('#money').val(), 'ether')).toString(16);
//console.log(money);
// 上記のWallet作成手順にある、wallet.getPrivateKeyString()によって得られた、Private keyの値を設定する
const privateKeyStr = $("#private_key").val();
let privateKey = new EthJS.Buffer.Buffer(privateKeyStr, 'hex');
// transactionのパラメータの設定
const txParams = {
nonce: countHex,
// 適切なgasPriceとgasLimitを設定する
// gasPrice: '0x09184e72a000',
// gasLimit: '0x30000',
gasPrice: gass_price,
gasLimit: gass_limit,
to: toAddress,
// 送金する金額
value: money
};
console.log(txParams);
web3.eth.estimateGas(txParams,function(error, result){
console.log("gasLimit:");
console.log(result.toString(16));
});
// Transactionオブジェクトの生成
const tx = new EthJS.Tx(txParams);
// 秘密鍵での署名
tx.sign(privateKey);
// Transactionオブジェクトをシリアライズして、16進数で表現
const serializedTx = tx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');
// 署名付きトランザクションの送信
web3.eth.sendSignedTransaction(rawTx)
.on('transactionHash', function(hash){
console.log('transactionHash');
web3.eth.getTransaction(hash).then(alertJSON);
})
.on('receipt', function(receipt){
console.log('receipt');
})
.on('confirmation', function(confirmationNumber, receipt){
console.log('confirmation');
})
.on('error', function(error){
if (error.indexOf("Transaction was not mined within 50 blocks")) {
return;
}
alert(error);
console.log(error);
});
}
※web3のバグで50秒後に『Transaction was not mined within 50 blocks』のエラーが飛んでくるため、無視する。
全体
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" type="text/css" media="all" />
</head>
<body>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdn.rawgit.com/ethereumjs/keythereum/622b3a4b/dist/keythereum.min.js"></script>
<script src="https://cdn.rawgit.com/ethereum/web3.js/1.0/dist/web3.min.js"></script>
<script src="https://cdn.rawgit.com/ethereumjs/browser-builds/2fb69a714afe092b06645286f14b94f41e5c062c/dist/ethereumjs-tx.js"></script>
<script type="text/javascript">
var price = null;
const web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider('http://localhos:8545'));
function setGasPrice(){
web3.eth.getGasPrice(function(error, result){
console.log(result.toString());
$("#gass_price").val(result.toString());
});
}
async function main() {
// 送信元のアドレス (Walletの作成手順にあるwallet.getAddress()のメソッドによって得られたaddress)
const fromAddress = $("#address").val();
// 送信先のアドレス
const toAddress = $("#address_to").val();
// 送信先のアドレス
const gass_price = "0x" + Number($("#gass_price").val()).toString(16);
// 送信先のアドレス
const gass_limit = "0x" + Number($("#gass_limit").val()).toString(16);
// 著名付きトランザクションのnonce値として必要なため、送信元アドレスのTransaction countを取得する
const count = await web3.eth.getTransactionCount("0x" +fromAddress);
const countHex = `0x` + count.toString(16);
const money = "0x" + Number(web3.utils.toWei($('#money').val(), 'ether')).toString(16);
//console.log(money);
// 上記のWallet作成手順にある、wallet.getPrivateKeyString()によって得られた、Private keyの値を設定する
const privateKeyStr = $("#private_key").val();
let privateKey = new EthJS.Buffer.Buffer(privateKeyStr, 'hex');
// transactionのパラメータの設定
const txParams = {
nonce: countHex,
// 適切なgasPriceとgasLimitを設定する
// gasPrice: '0x09184e72a000',
// gasLimit: '0x30000',
gasPrice: gass_price,
gasLimit: gass_limit,
to: toAddress,
// 送金する金額
value: money
};
console.log(txParams);
web3.eth.estimateGas(txParams,function(error, result){
console.log("gasLimit:");
console.log(result.toString(16));
});
// Transactionオブジェクトの生成
const tx = new EthJS.Tx(txParams);
// 秘密鍵での署名
tx.sign(privateKey);
// Transactionオブジェクトをシリアライズして、16進数で表現
const serializedTx = tx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');
// 署名付きトランザクションの送信
web3.eth.sendSignedTransaction(rawTx)
.on('transactionHash', function(hash){
console.log('transactionHash');
web3.eth.getTransaction(hash).then(alertJSON);
})
.on('receipt', function(receipt){
console.log('receipt');
})
.on('confirmation', function(confirmationNumber, receipt){
console.log('confirmation');
})
.on('error', function(error){
if (error.indexOf("Transaction was not mined within 50 blocks")) {
return;
}
alert(error);
console.log(error);
});
}
</script>
<div class="container">
<h2>ローカル ウォレット</h2>
<div class="panel panel-default">
<div class="panel-body">
<div class="row">
<div class="col-sm-4"><label>送り主 / private_key</label></div>
<div class="col-sm-8"><input type="text" id="address"/><input type="password" id="private_key"/></div>
</div>
<div class="row">
<div class="col-sm-4"><label>送り先 / 金額(eth)</label></div>
<div class="col-sm-8"><input type="text" id="address_to"/><input type="text" id="money" value="1"/></div>
</div>
<div class="row">
<div class="col-sm-4"><label>gass price(wei) / gass limit</label></div>
<div class="col-sm-8"><input type="text" id="gass_price" value="10000000000000"/><input type="text" id="gass_limit" value="196608"/></div>
</div>
<div class="row">
<div class="col-sm-4"></div>
<div class="col-sm-8"><input type="button" class="btn btn-primary active" id="go" value="送金"/></div>
</div>
<hr/>
<div class="row">
<div class="col-sm-4"><label>キーストア読み込み</label></div>
<div class="col-sm-8"><input type="button" class="btn btn-primary active" id="check" value="keystore + password -> private_key"/></div>
</div>
<hr/>
<div class="row">
<div class="col-sm-4"><label>アカウント作成(パスワード入力)</label></div>
<div class="col-sm-8">
<input type="password" id="apw" value=""/>
<input type="button" class="btn btn-primary active" id="create" value="登録"/>
</div>
</div>
<div class="row">
<div class="col-sm-4" id="keystore"></div>
</div>
<hr/>
<!-- 接続先。infura.ioに登録してエンドポイントを取得し、ここに貼り付ける。geth立ち上げて指定するのもあり -->
<div class="row">
<div class="col-sm-12">
<input type="radio" name=host value="https://mainnet.infura.io/xxxxxxxxxxxxxxx"> Ethereum<br/>
<input type="radio" name=host value="http://localhost:8545" checked="checked"> private_net(local)<br/>
<input type="radio" name=host value="https://ropsten.infura.io/xxxxxxxxxx"> test_net(Ropsten)<br/>
</div>
</div>
<hr/>
<div class="row">
<div class="col-sm-12">mining : <label id="mining" /></label></div>
</div>
</div>
</div>
<hr/>
<label> addr_list</label>
<ul id="demo">
</ul>
<script type="text/javascript">
function alertJSON(json) {
alert(JSON.stringify(json));
}
function logDemoElements(elements) {
ul = $("#demo");
elements.forEach(function(val){
ul.append('<li class="addr">' + val + "</li>");
});
}
function update_addr() {
$("#demo li").remove();
web3.eth.getAccounts().then(logDemoElements);
}
function update_wallet() {
$("#demo li").remove();
web3.eth.getAccounts().then(logDemoElements);
}
async function generate_account() {
ret = await web3.eth.accounts.create();
s = web3.eth.accounts.encrypt(ret.privateKey, $("#apw").val());
var blob = new Blob([JSON.stringify(s)], { "type" : "application/octet-stream" });
url = URL.createObjectURL(blob);
window.open(url);
update_addr();
}
$(function() {
$( 'input[name="host"]:radio' ).change( function() {
console.log( $( this ).val()); // valueを表示
web3.setProvider(new web3.providers.HttpProvider($( this ).val()));
update_addr();
setGasPrice();
});
$('#go').click(function(e) {
main();
});
$('#create').click(function(e) {
generate_account();
});
$('#demo').on('click','.addr', function(){
$("#address_to").val(this.innerText);
});
// アカウント作成
$('#check').click(function(e) {
var key = prompt('paste keystore(json_string)');
var keyObj = JSON.parse(key);
var password = prompt('password?');
var privateKey=keythereum.recover(password, keyObj);
console.log(privateKey.toString('hex'));
$("#address").val(keyObj.address);
$("#private_key").val(privateKey.toString('hex'));
});
$(document).ready(function(){
update_addr();
setGasPrice();
});
});
</script>
</body>
</html>
- 送信元 / privateKey / 送信先 / 金額を入力して送金ができる
- キーストア読み込みボタンを押すと入力boxが出るので、keystore内のjsonをテキストに貼り付ける
- ok押下後パスワードを聞かれるのでパスワード入力
- 少し待つと送信元とprivateKeyのテキストボックスに値が入力される
- アカウント作成を行うと、keystoreファイルがダウンロードされる
- ラジオボタン選択で接続先を切り替える。接続先はinfura.ioに登録して取得なりGeth使って自分で立ち上げるなりする
- 自前のgethでノードを立ち上げた場合、そこで作成したアカウントはadd_listに表示される。選択すると送金先のテキストにアドレスをコピー
参考
geth consoleを使わずJavaScriptのみでブロックチェーン(Ethereum)を操作する方法
https://qiita.com/fukumame55/items/e631119877c4cd77d0a5