From 2dc0f69d000b77d049e3ebb57362062d67174577 Mon Sep 17 00:00:00 2001
From: Wei Lu <luwei.here@gmail.com>
Date: Mon, 24 Mar 2014 00:19:46 +0800
Subject: [PATCH] createTX returns tx with expected inputs and outputs

---
 src/wallet.js  |  48 ++++++++++++++++++++
 test/wallet.js | 116 +++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 161 insertions(+), 3 deletions(-)

diff --git a/src/wallet.js b/src/wallet.js
index 5475957..5c153cc 100644
--- a/src/wallet.js
+++ b/src/wallet.js
@@ -164,6 +164,54 @@ var Wallet = function (seed, options) {
         })
     }
 
+    function getCandidateOutputs(value){
+      var unspent = []
+      for (var key in me.outputs){
+        var output = me.outputs[key]
+        if(!output.value.spend) unspent.push(output)
+      }
+
+      var sortByValueDesc = unspent.sort(function(o1, o2){
+        return o2.value - o1.value
+      })
+
+      return sortByValueDesc;
+    }
+
+    this.createTx = function(to, value, fixedFee) {
+        var tx = new Transaction()
+        tx.addOutput(to, value)
+
+        var utxo = getCandidateOutputs(value)
+        var totalInValue = 0
+        for(var i=0; i<utxo.length; i++){
+          var output = utxo[i]
+          tx.addInput(output.receive)
+
+          totalInValue += output.value
+          if(totalInValue < value) continue;
+
+          var fee = fixedFee || tx.estimateFee()
+          if(totalInValue < value + fee) continue;
+
+          var change = totalInValue - value - fee
+          var changeAddress = getChangeAddress()
+          if(change > 0) {
+            tx.addOutput(changeAddress, change)
+            // TODO: recalculate fee
+          }
+          break;
+        }
+
+        // TODO: sign tx
+        return tx
+    }
+
+    function getChangeAddress() {
+      if(me.changeAddresses.length === 0) me.generateChangeAddress()
+      return me.changeAddresses[me.changeAddresses.length - 1]
+    }
+
     this.getUtxoToPay = function(value) {
         var h = []
         for (var out in this.outputs) h.push(this.outputs[out])
diff --git a/test/wallet.js b/test/wallet.js
index 6bd7a7a..841d1cf 100644
--- a/test/wallet.js
+++ b/test/wallet.js
@@ -1,6 +1,9 @@
 var Wallet = require('../src/wallet.js')
 var HDNode = require('../src/hdwallet.js')
-var Transaction = require('../src/transaction.js').Transaction
+var T = require('../src/transaction.js')
+var Transaction = T.Transaction
+var TransactionOut = T.TransactionOut
+var Script = require('../src/script.js')
 var convert = require('../src/convert.js')
 var assert = require('assert')
 var SHA256 = require('crypto-js/sha256')
@@ -158,7 +161,7 @@ describe('Wallet', function() {
             "hashLittleEndian":"c7b97b432e7b3a8e31d1a4a0fa326c8fb002d19f2da5fc4feaf9c43a2762406a",
             "outputIndex": 0,
             "scriptPubKey":"76a91468edf28474ee22f68dfe7e56e76c017c1701b84f88ac",
-            "address" : "1azpkpcfczkduetfbqul4mokqai3m3hmxv",
+            "address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv",
             "value": 20000
           }
         ]
@@ -284,7 +287,7 @@ describe('Wallet', function() {
 
     describe("when tx ins outpoint contains a known txhash:i, the corresponding 'output' gets updated", function(){
       beforeEach(function(){
-        wallet.addresses = [tx.outs[0].address.toString()] // the address fixtureTx2 is spending from
+        wallet.addresses = [tx.outs[0].address.toString()] // the address fixtureTx2 used as input
         wallet.processTx(tx)
 
         tx = Transaction.deserialize(fixtureTx2Hex)
@@ -314,6 +317,113 @@ describe('Wallet', function() {
     })
   })
 
+  describe('createTx', function(){
+    var to, value, prevTx;
+
+    beforeEach(function(){
+      to = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3'
+      value = 500000
+
+      // generate 2 addresses
+      wallet.generateAddress()
+      wallet.generateAddress()
+
+      // set up 3 utxo
+      utxo = [
+        {
+          "hash": fakeTxHash(1),
+          "outputIndex": 0,
+          "scriptPubKey": scriptPubKeyFor(wallet.addresses[0], 300000),
+          "address" : wallet.addresses[0],
+          "value": 400000 // not enough for value
+        },
+        {
+          "hash": fakeTxHash(2),
+          "outputIndex": 1,
+          "scriptPubKey": scriptPubKeyFor(wallet.addresses[0], 500000),
+          "address" : wallet.addresses[0],
+          "value": 500000 // enough for only value
+        },
+        {
+          "hash": fakeTxHash(3),
+          "outputIndex": 0,
+          "scriptPubKey": scriptPubKeyFor(wallet.addresses[1], 520000),
+          "address" : wallet.addresses[1],
+          "value": 520000 // enough for value and fee
+        }
+      ]
+      wallet.setUnspentOutputs(utxo)
+
+      function scriptPubKeyFor(address, value){
+        var txOut = new TransactionOut({
+          value: value,
+          script: Script.createOutputScript(address)
+        })
+
+        return txOut.scriptPubKey()
+      }
+    })
+
+    function fakeTxHash(i) {
+      return "txtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtxtx" + i
+    }
+
+    describe('choosing utxo', function(){
+      it('calculates fees', function(){
+        var tx = wallet.createTx(to, value)
+
+        assert.equal(tx.ins.length, 1)
+        assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 })
+      })
+
+      it('allows fee to be specified', function(){
+        var fee = 30000
+        var tx = wallet.createTx(to, value, fee)
+
+        assert.equal(tx.ins.length, 2)
+        assert.deepEqual(tx.ins[0].outpoint, { hash: fakeTxHash(3), index: 0 })
+        assert.deepEqual(tx.ins[1].outpoint, { hash: fakeTxHash(2), index: 1 })
+      })
+    })
+
+    describe('transaction outputs', function(){
+      it('includes the specified address and amount', function(){
+        var tx = wallet.createTx(to, value)
+
+        assert.equal(tx.outs.length, 1)
+        var out = tx.outs[0]
+        assert.equal(out.address, to)
+        assert.equal(out.value, value)
+      })
+
+      describe('change', function(){
+        it('uses the last change address if there is any', function(){
+          var fee = 15000
+          wallet.generateChangeAddress()
+          wallet.generateChangeAddress()
+          var tx = wallet.createTx(to, value, fee)
+
+          assert.equal(tx.outs.length, 2)
+          var out = tx.outs[1]
+          assert.equal(out.address, wallet.changeAddresses[1])
+          assert.equal(out.value, 5000)
+        })
+
+        it('generates a change address if there is not any', function(){
+          var fee = 15000
+          assert.equal(wallet.changeAddresses.length, 0)
+
+          var tx = wallet.createTx(to, value, fee)
+
+          assert.equal(wallet.changeAddresses.length, 1)
+          var out = tx.outs[1]
+          assert.equal(out.address, wallet.changeAddresses[0])
+          assert.equal(out.value, 5000)
+        })
+      })
+    })
+  })
+
   function assertEqual(obj1, obj2){
     assert.equal(obj1.toString(), obj2.toString())
   }