I mentioned in my original post about this that this part is not trivial. It's because selecting coins for a transaction is not a one-shot process -- you have to declare what your desired output amount is
and your desired fee, then Armory will create the best transaction it can based on those parameters. If the transaction has properties that require a bigger fee, then you need to re-select coins with the new fee. Theoretically, this should be part of a while-loop or something (in case the new selection requires an even-bigger fee), but I designed the SelectCoins algorithm to use a selection that leaves some margin (if possible) so that a higher fee can be included without changing the coin selection.
The functions for reference are:
validateInputsGetTxDP() at qtdialogs.py:4867,
createTxAndBroadcast() at qtdialogs.py:4811 (Maybe)
broadcastTransaction() in ArmoryQt.py:1889).
PySelctCoins() in armoryengine.py:5049.
There's a lot of code here, because it is also validating the data was entered properly on the Send-Tx dialog. But what you need is there. I'll try to summarize it here.
PySelectCoins arguments:
- unspentTxOutInfo: a list of unspent TxOuts, retrieved via wallet.getTxOutList("Spendable") (qtdialogs.py:4968)
- targetOutVal: total amount to send to (all) recipients(s), in satoshis (i.e. 10.0*ONE_BTC for 10BTC)
- minFee: you can always set this to 0 the first time, and re-call it with higher if the output suggests more
- numRand (optional): number of randomized SelectCoins solutions to try (more about this in the appendix, below)
- margin (optional): how much margin you'd like to build into the selection, for the reasons stated above
Actually executing the transaction -- you must get all the spendable coins, make a coin selection, then calculate the change and generate the recipient list (you will need to
wallet.getNextUnusedAddress() generate a change address, or you can do something else). Shuffle the list. Then create the "PyTxDistProposal" -- this is an intermediate step where the transaction is constructed as a
BIP 0010 packet, in case it needs to be signed by an offline computer. In the case that you have the full wallet -- this TxDP will be signed immediately, then converted to a standard transaction and broadcast.
And finally, here's the code sample you've been waiting for: Create one transaction from a wallet, to send 10.0 BTC to one recipient
# Get unspent TxOutList and select the coins
totalSend, fee = long(10.0 * ONE_BTC), 0
spendBal = wallet.getBalance('Spendable')
utxoList = wallet.getTxOutList('Spendable')
utxoSelect = PySelectCoins(utxoList, totalSend, fee)
# Check the minimum fee for the coin selection
minFeeRec = calcMinSuggestedFees(utxoSelect, totalSend, fee)[1]
if fee if totalSend + minFeeRec > spendBal:
raise NotEnoughCoinsError, "You can't afford the fee!"
utxoSelect = PySelectCoins(utxoList, totalSend, minFeeRec)
fee = minFeeRec
if len(utxoSelect)==0:
raise CoinSelectError, "Somehow, coin selection failed. This shouldn't happen"
# We have a good coinselection, now compute change and create recip list
totalSelected = sum([u.getValue() for u in utxoSelect])
totalChange = totalSelected - (totalSend + fee)
outputPairs = []
outputPairs.append( [addr160_recipient, totalSend] )
if totalChange > 0:
outputPairs.append( [wallet.getNextUnusedAddress().getAddr160(), totalChange] )
# Shuffle the list, create the TxDP
random.shuffle(outputPairs)
txdp = PyTxDistProposal().createFromTxOutSelection(utxoSelect, outputPairs)
# Sign the TxDP
wallet.signTxDistProposal(txdp)
finalTx = txdp.prepareFinalTx()
wallet.setComment(finalTx.getHash(), "This was the example tx created by the python script")
finalTx.pprint()
print "Hexified Tx: ", binary_to_hex(finalTx.serialize())
# Broadcast the transaction
... (will fill this part in later)
I suppose I could encapsulate much of this code in a more friendly way. On the other hand, you can pretty much copy this code and make the appropriate adjustments, and you'll get a signed transaction. You can modify it for multiple recipients, handling fees differently (but you should trust the calcMinSuggestedFee[1] value), and saving off copies of the transaction before sending.
I will include more about actually broadcasting the transaction, in a bit. The problem is that you need to start the networking engine in order to actually broadcast it. On the other hand, you have a PyTx object (finalTx), so you can call
binary_to_hex(finalTx.serialize()) to get the raw hex version of it, which you might be able to broadcast in other ways. (like "sendrawtransaction" RPC call with bitcoind)
I
know you'll have questions. Fire away!
Appendix: The way PySelectCoins works: a bunch of deterministic coin-selections (solutions to the pseudo-knapsack problem) are generated based on a variety of standard coin pools (the types and sizes of unspent TxOuts for your wallet). A few random solutions are thrown on top to generate some variety. Usually, the deterministic algorithms find the best answer, but for strange coin-pool make-ups, some semi-randomized selections will produce decent answers. In all, we get about a couple dozen possible solutions, and then each one is evaluated for "fitness" ... how big is the fee? how many addresses are combine? how obvious is it which output is the recipient and which one is change? The relative weightings of each of these is defined in armoryengine.py:5000 -- the WEIGHTS variable defines how much relative weighting to use for each factor. There's six of them, though it really could've been reduced to 3 important ones: tx-fee/allow-free, input-anonymity, and output-anonymity.
WEIGHTS[IDX_ALLOWFREE] = 100000
WEIGHTS[IDX_NOZEROCONF] = 1000000
WEIGHTS[IDX_PRIORITY] = 50
WEIGHTS[IDX_NUMADDR] = 100000
WEIGHTS[IDX_TXSIZE] = 100
WEIGHTS[IDX_OUTANONYM] = 30
You can manually modify these weightings, or find a way to inject a new weights matrix into the PyEvalCoinSelect (I apologize, I didn't make that easy, you might be better to just manually change armoryengine.py, for now). What do these mean? The default behavior is to avoid using zero-conf coins at all costs. Any solution with no zero-conf coins will be scored higher than any solution with zero-conf. The next two biggest weights are "ALLOWFREE" and "NUMADDR". This means that equal weighting is given to "please give me a transaction that can be free" and "please use as few addresses as possible on the input side". "TXSIZE" and "PRIORITY" are next, but probably shouldn't even be there, because they are already tied into the ALLOWFREE and NOZEROCONF scores.
Finally, OUTANONYM is the last critieria. Given a set of coin-selects that are basically all the same for the other 5 criteria, it will select the solution that has better output-anonymity. i.e. -- if the outputs are 10.0 and 1.3482023, it's fairly obvious to someone observing the blockchain which one is the recipient, which is the change-back-to-self (1.3482023). However, if a solution is found that gives 11.34 and 5.11 -- it's not so obvious anymore, and that solution will get a higher score. It gets extra credit if it's deceptive: if the recipient is getting 1.384 and it finds a solution that has a change of 8.1: then most would observers would make the wrong conclusion.
The point of this description was not only to describe the weightings, but suggest you change them for your own usage. I had planned to make this customizable in Armory, I just hadn't gotten around to it. If all you care about is input-anonymity and output-anonymity, I suggest changing NUMADDR and OUTANONYM to high numbers, and change the other 4 to something tiny. You might pay a fee when you otherwise wouldn't have to, but it will be more anonymous.
DISCLAIMER: I make no guarantees about transaction anonymity at all. Use of this feature is completely at your own risk. This is implemented solely for the purposes of declaring a rough ordering for an under-defined set of solutions. Setting any particular weight to 10
10 and others to 0 does not mean you are guaranteed a "good" solution -- there may only be one solution.