Reality Keysと連携するiOSアプリをつくる 第5回 claim(出金)
はじめに
今回は一連の最後の処理「claim(出金)」をやります。
目次
By My Coin Web版はどうしているか
以前この記事で調べました。
iOS版はどう実装するか
まず必要なデータを整理します。
to_addr = destination p2shaddress = P2SHAddress yes_user_pubkey = yes pubkey(Mnemonicから作成したPubkey) no_user_pubkey = no pubkey(チャリティ先) yes_pubkey = RK APIが返すyespubkey no_pubkey = RK APIが返すnopubkey txes = unspentトランザクション winner_privkey = RK APIが返すwinnerのprivate key
claim時に取得が必要なのは、unspentトランザクションとwinner_privkeyの2つです。 winner_privkeyは、RK結果取得APIにより取得。txesはblockr.ioから取得します。
unspentトランザクション取得
unspentトランザクション取得はblockr.ioから以下のように取得できます。
https://btc.blockr.io/api/v1/address/unspent/35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT
{ status: "success", data: { address: "35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT", unspent: [ { tx: "89cda9cdf7a78005cb33f12383eb69b90957cbc4fde1ad2833bddb614f551ead", amount: "0.00050000", n: 0, confirmations: 1, script: "a9142a30fa7829a2c326387252d53087eb822269c2bb87" }, { tx: "9214ea8b867a772e868f8aea98cd0da8a32811d703af84492abe61e253a50231", amount: "0.00050000", n: 0, confirmations: 1, script: "a9142a30fa7829a2c326387252d53087eb822269c2bb87" } ] }, code: 200, message: "" }
Winnerのprivateキー取得
WinnerのprivateキーはRKのAPIから取得します。
https://www.realitykeys.com/api/v1/runkeeper/1927/?accept_terms_of_service=current
{ no_pubkey: "02c0b9fba37cfd406874d34a8b1ce133f9ff5b1915f9e27ec07dff12fefc7611ec", user_profile: "1869270714", settlement_date: "2015-02-15", objection_period_secs: 604800, human_resolution_scheduled_datetime: null, measurement: "total_distance", evaluation_method: "ge", is_user_authenticated: true, objection_fee_satoshis_paid: 0, machine_resolution_scheduled_datetime: "2015-02-15 00:00:00", user_id: "40442259", goal: "1000", created_datetime: "2015-02-14 15:56:50", winner: "No", value: "1000", id: 1927, source: "runkeeper", yes_pubkey: "02a5d4a751923c4128332df96887e188c86418820c437b0891fa838cc4ad104a58", activity: "running", objection_fee_satoshis_due: 1000000, user_name: "zono", winner_privkey: "L3jyBVqNFkwzTRoc4c3XsDtSZAVr5jcZRA21Ewh2pLQss3vCgk4f" }
上記の結果からNOがWinnerであることがわかります。
iOSでの実装
JavaScript
By My Coins Web版ではブロードキャスト時にeligiusを使っていますが、iOS版ではBlockchain.infoを使います。
function execute_claim(to_addr, p2sh_address, yes_user_pubkey, no_user_pubkey, yes_pubkey, no_pubkey, txes, winner_privkey) { // 今回NOが勝ったのでダミーでNOの秘密鍵を設定 user_privkey = "***************************************"; var c = { 'address': p2sh_address, 'yes_user_pubkey': yes_user_pubkey, 'no_user_pubkey': no_user_pubkey, 'yes_pubkey': yes_pubkey, 'no_pubkey': no_pubkey } var unspent_txes = JSON.parse(txes); for (var i = 0; i < unspent_txes.data.unspent.length; i++) { console.log(unspent_txes.data.unspent[i].tx); var txHex = hex_for_claim_execution(to_addr, user_privkey, winner_privkey, unspent_txes.data.unspent[i], c); blockchain_cross_domain_post(txHex); } } function hex_for_claim_execution(to_addr, priv1, priv2, tx, c) { var tx = tx_for_claim_execution(to_addr, priv1, priv2, tx, c); var txHex = tx.serialize().toString('hex'); return txHex; } function tx_for_claim_execution(to_addr, priv1, priv2, tx, c) { var network = bitcore.networks['livenet']; var n = tx['n']; var txid = tx['tx']; var amount = tx['amount']; var utxos2 = [ { address: c['address'], txid: tx['tx'], vout: tx['n'], ts: 1396375187, scriptPubKey: tx['script'], amount: amount, confirmations: 1 } ]; var pubkeys = [ [ c['yes_user_pubkey'], c['yes_pubkey'] ], [ c['no_user_pubkey'], c['no_pubkey'] ], [ c['yes_user_pubkey'], c['no_user_pubkey'] ] ]; var opts = {network: network, nreq:[2,2,2], pubkeys:pubkeys}; var fee = 10000 / 100000000; outs = [{address:to_addr, amount:(amount-fee)}]; var hashMap = {}; hashMap[ c['address'] ] = redeem_script(c); var b = new bitcore.TransactionBuilder(opts); b.setUnspent(utxos2); b.setHashToScriptMap(hashMap); b.setOutputs(outs); var user_wk = new bitcore.WalletKey({ network: network }); user_wk.fromObj( { priv: priv1, }); var user_wk_obj = user_wk.storeObj(); var user_privkey_wif = user_wk_obj.priv; var winner_wk = new bitcore.WalletKey({ network: network }); winner_wk.fromObj( { priv: priv2, }); var winner_wk_obj = winner_wk.storeObj(); var winner_privkey_wif = winner_wk_obj.priv; b.sign([user_privkey_wif, winner_privkey_wif]); tx = b.build(); return tx; } function blockchain_cross_domain_post(data) { var form = document.createElement("form"); form.setAttribute("method", "POST"); form.setAttribute("action", 'https://blockchain.info/ja/pushtx'); var hiddenField = document.createElement("input"); hiddenField.setAttribute("type", "hidden"); hiddenField.setAttribute("name", "tx"); hiddenField.setAttribute("value", data); form.appendChild(hiddenField); document.body.appendChild(form); form.submit(); }
Swift
blockr.ioからunspentトランザクションを取得してJSON文字列をJavaScriptに渡しています。(JSONのパースはJS側で行う)
var json: String! override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) Alamofire.request(.GET, "https://btc.blockr.io/api/v1/address/unspent/35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT", parameters: nil) .responseJSON { (request, response, data, error) in let jsonObject = JSON(data!) self.json = jsonObject.rawString(encoding: NSUTF8StringEncoding, options: nil) dispatch_async(dispatch_get_main_queue(),{ let path = NSBundle.mainBundle().pathForResource("index", ofType: "html")! let url = NSURL(string: path)! let urlRequest = NSURLRequest(URL: url) self.webView.loadRequest(urlRequest) }); } } func webViewDidFinishLoad(webView: UIWebView) { let userDefaults = NSUserDefaults.standardUserDefaults() let mnemonic = userDefaults.stringForKey("mnemonic") let k = webView.stringByEvaluatingJavaScriptFromString("pubkey_for_mnemonic('\(mnemonic!)');") let to_addr = Constants.PublicKey.CharityAddress let p2shaddress = "35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT" let yes_user_pubkey = k! let no_user_pubkey = Constants.PublicKey.CharityPubKey let yes_pubkey = contract.yes_pubkey let no_pubkey = contract.no_pubkey let txes = json let winner_privkey = contract.winner_privkey webView.stringByEvaluatingJavaScriptFromString("execute_claim('\(to_addr)','\(p2shaddress)','\(yes_user_pubkey)','\(no_user_pubkey)','\(yes_pubkey)','\(no_pubkey)','\(txes)','\(winner_privkey)');") }
結果確認
https://blockchain.info/address/35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT
2つ問題があります。
- 2つのunspentトランザクションがあるのに1つした出金されていない。
- 30分近くたってもUnconfirmation状態。
1はプログラムのバグ。2はbitcoind 0.1.0の普及でそのうち解決するかもしれません。
まとめ
課題はたくさんありますが、とりあえず一連の流れを実装できました。次回からは使いやすいUIを考えてみます。