"""This module corresponds to functionality documented at https://blockchain.info/api/blockchain_wallet_api """ import json from . import util from .exceptions import * class Wallet: """The :class:`Wallet` class mirrors operations listed on the wallet API page. It needs to be initialized on a per-wallet basis and will cache the wallet identitifer, password, second password and API key (if provided). """ def __init__(self, identifier, password, service_url, second_password=None, api_code=None): """Initializes a wallet object. :param str identifier: wallet identifier (GUID) :param str password : decryption password :param str service_url : URL to an instance of service-my-wallet-v3 (with trailing slash) :param str second_password: second password (optional) :param str api_code: Blockchain.info API code """ self.identifier = identifier self.password = password self.service_url = service_url self.second_password = second_password self.api_code = api_code def send(self, to, amount, from_address=None, fee=None): """Send bitcoin from your wallet to a single address. :param str to: recipient bitcoin address :param int amount: amount to send (in satoshi) :param str from_address: specific address to send from (optional) :param int fee: transaction fee in satoshi. Must be greater than the default fee (optional). :return: an instance of :class:`PaymentResponse` class """ recipient = {to: amount} return self.send_many(recipient, from_address, fee) def send_many(self, recipients, from_address=None, fee=None): """Send bitcoin from your wallet to multiple addresses. :param dictionary recipients: dictionary with the structure of 'address':amount :param str from_address: specific address to send from (optional) :param int fee: transaction fee in satoshi. Must be greater than the default fee (optional). :return: an instance of :class:`PaymentResponse` class """ params = self.build_basic_request(read_only=False) if len(recipients) == 1: to_address, amount = recipients.popitem() params['to'] = to_address params['amount'] = amount method = 'payment' else: params['recipients'] = json.dumps(recipients) method = 'sendmany' if from_address is not None: params['from'] = from_address if fee is not None: params['fee'] = fee response = util.call_api("merchant/{0}/{1}".format(self.identifier, method), params, base_url=self.service_url) json_response = json.loads(response) self.parse_error(json_response) payment_response = PaymentResponse(json_response['message'], json_response['tx_hash'], json_response.get('notice')) return payment_response def get_balance(self): """Fetch the wallet balance. Includes unconfirmed transactions and possibly double spends. :return: wallet balance in satoshi """ response = util.call_api("merchant/{0}/balance".format(self.identifier), self.build_basic_request(), base_url=self.service_url) json_response = json.loads(response) self.parse_error(json_response) return json_response.get('balance') def list_addresses(self): """List all active addresses in the wallet. :return: an array of :class:`Address` objects """ params = self.build_basic_request() response = util.call_api("merchant/{0}/list".format(self.identifier), params, base_url=self.service_url) json_response = json.loads(response) self.parse_error(json_response) addresses = [] for a in json_response['addresses']: address = Address(a['balance'], a['address'], a.get('label'), a['total_received']) addresses.append(address) return addresses def get_address(self, address): """Retrieve an address from the wallet. :param str address: address in the wallet to look up :return: an instance of :class:`Address` class """ params = self.build_basic_request() params['address'] = address response = util.call_api("merchant/{0}/address_balance".format(self.identifier), params, base_url=self.service_url) json_response = json.loads(response) self.parse_error(json_response) return Address(json_response['balance'], json_response['address'], None, json_response['total_received']) def new_address(self, label=None): """Generate a new address and add it to the wallet. :param str label: label to attach to this address (optional) :return: an instance of :class:`Address` class """ params = self.build_basic_request() if label is not None: params['label'] = label response = util.call_api("merchant/{0}/new_address".format(self.identifier), params, base_url=self.service_url) json_response = json.loads(response) self.parse_error(json_response) return Address(0, json_response['address'], json_response.get('label'), 0) def archive_address(self, address): """Archive an address. :param str address: address to archive :return: string representation of the archived address """ params = self.build_basic_request() params['address'] = address response = util.call_api("merchant/{0}/archive_address".format(self.identifier), params, base_url=self.service_url) json_response = json.loads(response) self.parse_error(json_response) return json_response['archived'] def unarchive_address(self, address): """Unarchive an address. :param str address: address to unarchive :return: string representation of the unarchived address """ params = self.build_basic_request() params['address'] = address response = util.call_api("merchant/{0}/unarchive_address".format(self.identifier), params, base_url=self.service_url) json_response = json.loads(response) self.parse_error(json_response) return json_response['active'] def build_basic_request(self, read_only=True): params = {'password': self.password} if self.second_password is not None and read_only is False: params['second_password'] = self.second_password if self.api_code is not None: params['api_code'] = self.api_code return params @staticmethod def parse_error(json_response): error = json_response.get('error') if error is not None: raise APIException(error, 0) class PaymentResponse: def __init__(self, message, tx_hash, notice): self.message = message self.tx_hash = tx_hash self.notice = notice class Address: def __init__(self, balance, address, label, total_received): self.balance = balance self.address = address self.label = label self.total_received = total_received