3 # Use the raw transactions API to spend bitcoins received on particular addresses,
4 # and send any change back to that same address.
7 # spendfrom.py # Lists available funds
8 # spendfrom.py --from=ADDRESS --to=ADDRESS --amount=11.00
10 # Assumes it will talk to a bitcoind or Bitcoin-Qt running
24 from jsonrpc
import ServiceProxy
, json
26 BASE_FEE
=Decimal("0.001")
28 def check_json_precision():
29 """Make sure json library being used does not lose precision converting BTC values"""
30 n
= Decimal("20000000.00000003")
31 satoshis
= int(json
.loads(json
.dumps(float(n
)))*1.0e8
)
32 if satoshis
!= 2000000000000003:
33 raise RuntimeError("JSON encode/decode loses precision")
35 def determine_db_dir():
36 """Return the default location of the bitcoin data directory"""
37 if platform
.system() == "Darwin":
38 return os
.path
.expanduser("~/Library/Application Support/Bitcoin/")
39 elif platform
.system() == "Windows":
40 return os
.path
.join(os
.environ
['APPDATA'], "Bitcoin")
41 return os
.path
.expanduser("~/.bitcoin")
43 def read_bitcoin_config(dbdir
):
44 """Read the bitcoin.conf file from dbdir, returns dictionary of settings"""
45 from ConfigParser
import SafeConfigParser
47 class FakeSecHead(object):
48 def __init__(self
, fp
):
50 self
.sechead
= '[all]\n'
53 try: return self
.sechead
54 finally: self
.sechead
= None
56 s
= self
.fp
.readline()
58 s
= s
[0:s
.find('#')].strip() +"\n"
61 config_parser
= SafeConfigParser()
62 config_parser
.readfp(FakeSecHead(open(os
.path
.join(dbdir
, "bitcoin.conf"))))
63 return dict(config_parser
.items("all"))
65 def connect_JSON(config
):
66 """Connect to a bitcoin JSON-RPC server"""
67 testnet
= config
.get('testnet', '0')
68 testnet
= (int(testnet
) > 0) # 0/1 in config file, convert to True/False
69 if not 'rpcport' in config
:
70 config
['rpcport'] = 18332 if testnet
else 8332
71 connect
= "http://%s:%s@127.0.0.1:%s"%(config
['rpcuser'], config
['rpcpassword'], config
['rpcport'])
73 result
= ServiceProxy(connect
)
74 # ServiceProxy is lazy-connect, so send an RPC command mostly to catch connection errors,
75 # but also make sure the bitcoind we're talking to is/isn't testnet:
76 if result
.getmininginfo()['testnet'] != testnet
:
77 sys
.stderr
.write("RPC server at "+connect
+" testnet setting mismatch\n")
81 sys
.stderr
.write("Error connecting to RPC server at "+connect
+"\n")
84 def unlock_wallet(bitcoind
):
85 info
= bitcoind
.getinfo()
86 if 'unlocked_until' not in info
:
87 return True # wallet is not encrypted
88 t
= int(info
['unlocked_until'])
91 passphrase
= getpass
.getpass("Wallet is locked; enter passphrase: ")
92 bitcoind
.walletpassphrase(passphrase
, 5)
94 sys
.stderr
.write("Wrong passphrase\n")
96 info
= bitcoind
.getinfo()
97 return int(info
['unlocked_until']) > time
.time()
99 def list_available(bitcoind
):
100 address_summary
= dict()
102 address_to_account
= dict()
103 for info
in bitcoind
.listreceivedbyaddress(0):
104 address_to_account
[info
["address"]] = info
["account"]
106 unspent
= bitcoind
.listunspent(0)
107 for output
in unspent
:
108 # listunspent doesn't give addresses, so:
109 rawtx
= bitcoind
.getrawtransaction(output
['txid'], 1)
110 vout
= rawtx
["vout"][output
['vout']]
111 pk
= vout
["scriptPubKey"]
113 # This code only deals with ordinary pay-to-bitcoin-address
114 # or pay-to-script-hash outputs right now; anything exotic is ignored.
115 if pk
["type"] != "pubkeyhash" and pk
["type"] != "scripthash":
118 address
= pk
["addresses"][0]
119 if address
in address_summary
:
120 address_summary
[address
]["total"] += vout
["value"]
121 address_summary
[address
]["outputs"].append(output
)
123 address_summary
[address
] = {
124 "total" : vout
["value"],
125 "outputs" : [output
],
126 "account" : address_to_account
.get(address
, "")
129 return address_summary
131 def select_coins(needed
, inputs
):
132 # Feel free to improve this, this is good enough for my simple needs:
134 have
= Decimal("0.0")
136 while have
< needed
and n
< len(inputs
):
137 outputs
.append({ "txid":inputs
[n
]["txid"], "vout":inputs
[n
]["vout"]})
138 have
+= inputs
[n
]["amount"]
140 return (outputs
, have
-needed
)
142 def create_tx(bitcoind
, fromaddresses
, toaddress
, amount
, fee
):
143 all_coins
= list_available(bitcoind
)
145 total_available
= Decimal("0.0")
147 potential_inputs
= []
148 for addr
in fromaddresses
:
149 if addr
not in all_coins
:
151 potential_inputs
.extend(all_coins
[addr
]["outputs"])
152 total_available
+= all_coins
[addr
]["total"]
154 if total_available
< needed
:
155 sys
.stderr
.write("Error, only %f BTC available, need %f\n"%(total_available
, needed
));
160 # Python's json/jsonrpc modules have inconsistent support for Decimal numbers.
161 # Instead of wrestling with getting json.dumps() (used by jsonrpc) to encode
162 # Decimals, I'm casting amounts to float before sending them to bitcoind.
164 outputs
= { toaddress
: float(amount
) }
165 (inputs
, change_amount
) = select_coins(needed
, potential_inputs
)
166 if change_amount
> BASE_FEE
: # don't bother with zero or tiny change
167 change_address
= fromaddresses
[-1]
168 if change_address
in outputs
:
169 outputs
[change_address
] += float(change_amount
)
171 outputs
[change_address
] = float(change_amount
)
173 rawtx
= bitcoind
.createrawtransaction(inputs
, outputs
)
174 signed_rawtx
= bitcoind
.signrawtransaction(rawtx
)
175 if not signed_rawtx
["complete"]:
176 sys
.stderr
.write("signrawtransaction failed\n")
178 txdata
= signed_rawtx
["hex"]
182 def compute_amount_in(bitcoind
, txinfo
):
183 result
= Decimal("0.0")
184 for vin
in txinfo
['vin']:
185 in_info
= bitcoind
.getrawtransaction(vin
['txid'], 1)
186 vout
= in_info
['vout'][vin
['vout']]
187 result
= result
+ vout
['value']
190 def compute_amount_out(txinfo
):
191 result
= Decimal("0.0")
192 for vout
in txinfo
['vout']:
193 result
= result
+ vout
['value']
196 def sanity_test_fee(bitcoind
, txdata_hex
, max_fee
):
197 class FeeError(RuntimeError):
200 txinfo
= bitcoind
.decoderawtransaction(txdata_hex
)
201 total_in
= compute_amount_in(bitcoind
, txinfo
)
202 total_out
= compute_amount_out(txinfo
)
203 if total_in
-total_out
> max_fee
:
204 raise FeeError("Rejecting transaction, unreasonable fee of "+str(total_in
-total_out
))
206 tx_size
= len(txdata_hex
)/2
207 kb
= tx_size
/1000 # integer division rounds down
208 if kb
> 1 and fee
< BASE_FEE
:
209 raise FeeError("Rejecting no-fee transaction, larger than 1000 bytes")
210 if total_in
< 0.01 and fee
< BASE_FEE
:
211 raise FeeError("Rejecting no-fee, tiny-amount transaction")
212 # Exercise for the reader: compute transaction priority, and
213 # warn if this is a very-low-priority transaction
215 except FeeError
as err
:
216 sys
.stderr
.write((str(err
)+"\n"))
222 parser
= optparse
.OptionParser(usage
="%prog [options]")
223 parser
.add_option("--from", dest
="fromaddresses", default
=None,
224 help="addresses to get bitcoins from")
225 parser
.add_option("--to", dest
="to", default
=None,
226 help="address to get send bitcoins to")
227 parser
.add_option("--amount", dest
="amount", default
=None,
228 help="amount to send")
229 parser
.add_option("--fee", dest
="fee", default
="0.0",
230 help="fee to include")
231 parser
.add_option("--datadir", dest
="datadir", default
=determine_db_dir(),
232 help="location of bitcoin.conf file with RPC username/password (default: %default)")
233 parser
.add_option("--testnet", dest
="testnet", default
=False, action
="store_true",
234 help="Use the test network")
235 parser
.add_option("--dry_run", dest
="dry_run", default
=False, action
="store_true",
236 help="Don't broadcast the transaction, just create and print the transaction data")
238 (options
, args
) = parser
.parse_args()
240 check_json_precision()
241 config
= read_bitcoin_config(options
.datadir
)
242 if options
.testnet
: config
['testnet'] = True
243 bitcoind
= connect_JSON(config
)
245 if options
.amount
is None:
246 address_summary
= list_available(bitcoind
)
247 for address
,info
in address_summary
.iteritems():
248 n_transactions
= len(info
['outputs'])
249 if n_transactions
> 1:
250 print("%s %.8f %s (%d transactions)"%(address
, info
['total'], info
['account'], n_transactions
))
252 print("%s %.8f %s"%(address
, info
['total'], info
['account']))
254 fee
= Decimal(options
.fee
)
255 amount
= Decimal(options
.amount
)
256 while unlock_wallet(bitcoind
) == False:
257 pass # Keep asking for passphrase until they get it right
258 txdata
= create_tx(bitcoind
, options
.fromaddresses
.split(","), options
.to
, amount
, fee
)
259 sanity_test_fee(bitcoind
, txdata
, amount
*Decimal("0.01"))
263 txid
= bitcoind
.sendrawtransaction(txdata
)
266 if __name__
== '__main__':