def clean_btc_address(self): address = self.cleaned_data.get('btc_address').strip() exchange_choice = self.cleaned_data.get('exchange_choice') if address and not is_valid_btc_address(address): msg = _("Sorry, that's not a valid bitcoin address") raise forms.ValidationError(msg) if exchange_choice == 'selfmanaged' and not is_valid_btc_address(address): msg = _("Please enter a valid bitcoin address") raise forms.ValidationError(msg) return address
def fetch_bcypher_txn_data_from_address(address, merchant=None, forwarding_obj=None): BASE_URL = 'http://api.blockcypher.com/v1/btc/main/addrs/%s' url_to_hit = BASE_URL % address if BLOCKCYPHER_API_KEY: url_to_hit += '?token=%s' % BLOCKCYPHER_API_KEY assert is_valid_btc_address(address), address r = requests.get(url_to_hit) # Log the API call APICall.objects.create( api_name=APICall.BLOCKCYPHER_TXN_FROM_ADDR, url_hit=url_to_hit, response_code=r.status_code, post_params=None, api_results=r.content, merchant=merchant, forwarding_address=forwarding_obj, ) err_msg = 'Expected status code 2XX but got %s' % r.status_code assert str(r.status_code).startswith('2'), err_msg dict_response = json.loads(r.content) assert 'error' not in dict_response, dict_response assert 'errors' not in dict_response, dict_response return dict_response
def save(self, *args, **kwargs): """ Be sure b58_address is valid before saving """ assert is_valid_btc_address(self.b58_address), self.b58_address super(BaseAddressFromCredential, self).save(*args, **kwargs)
def fetch_chaincom_txn_data_from_address(address, merchant=None, forwarding_obj=None): BASE_URL = 'https://api.chain.com/v1/bitcoin/addresses/%s/transactions' url_to_hit = BASE_URL % address if CHAIN_COM_API_KEY: url_to_hit += '?api-key-id=%s' % CHAIN_COM_API_KEY assert is_valid_btc_address(address), address r = requests.get(url_to_hit) # Log the API call APICall.objects.create( api_name=APICall.CHAINCOM_TXN_FROM_ADDR, url_hit=url_to_hit, response_code=r.status_code, post_params=None, api_results=r.content, merchant=merchant, forwarding_address=forwarding_obj, ) err_msg = 'Expected status code 2XX but got %s' % r.status_code assert str(r.status_code).startswith('2'), err_msg dict_response = json.loads(r.content) assert 'error' not in dict_response, dict_response assert 'errors' not in dict_response, dict_response return dict_response
def fetch_bci_txn_data_from_address(address, merchant=None, forwarding_obj=None): """ Get all BCI transactions for a given address """ assert address, 'Must supply an address' assert is_valid_btc_address(address), address bci_url = 'https://blockchain.info/address/%s?format=json&api_code=%s' % ( address, BCI_SECRET_KEY) r = requests.get(bci_url) # Log the API call APICall.objects.create( api_name=APICall.BCI_TXN_FROM_ADDR, url_hit=bci_url, response_code=r.status_code, post_params=None, api_results=r.content, merchant=merchant, forwarding_address=forwarding_obj, ) err_msg = 'Expected status code 200 but got %s' % r.status_code assert r.status_code == 200, err_msg return json.loads(r.content)
def process_bci_webhook(request, random_id): # Log webhook webhook = WebHook.log_webhook(request, WebHook.BCI_PAYMENT_FORWARDED) # parse webhook input_txn_hash = request.GET['input_transaction_hash'] destination_txn_hash = request.GET['transaction_hash'] satoshis = int(request.GET['value']) num_confirmations = int(request.GET['confirmations']) input_address = request.GET['input_address'] destination_address = request.GET['destination_address'] if input_address: forwarding_obj = get_object_or_None(ForwardingAddress, b58_address=input_address) if forwarding_obj: # Tie webhook to merchant. Optional. webhook.merchant = forwarding_obj.merchant webhook.save() # These defensive checks should always be true assert is_valid_btc_address(input_address), input_address assert is_valid_btc_address(destination_address), destination_address msg = '%s == %s' % (input_txn_hash, destination_txn_hash) assert input_txn_hash != destination_txn_hash, msg # process the transactions ForwardingAddress.handle_forwarding_txn( input_address=input_address, satoshis=satoshis, num_confirmations=num_confirmations, input_txn_hash=input_txn_hash, ) ForwardingAddress.handle_destination_txn( forwarding_address=input_address, destination_address=destination_address, satoshis=satoshis, num_confirmations=num_confirmations, destination_txn_hash=destination_txn_hash, ) if num_confirmations > 6: return HttpResponse("*ok*") else: return HttpResponse("Robot: please come back with more confirmations")
def clean_btc_address(self): address = self.cleaned_data.get('btc_address') email_or_btc_address = self.cleaned_data.get('email_or_btc_address') # sending to a bitcoin address if email_or_btc_address == '2': if not is_valid_btc_address(address): msg = _("Please enter a valid bitcoin address") raise forms.ValidationError(msg) return address
def send_btc(self, satoshis_to_send, destination_btc_address): """ Returns a tuple of the form (some or all may be none): btc_txn, sent_btc_obj, api_call, err_str """ msg = '%s is not a valid bitcoin address' % destination_btc_address assert is_valid_btc_address(destination_btc_address), msg BASE_URL = 'https://blockchain.info/merchant/%s/payment?password=%s&to=%s&amount=%s&shared=false' SEND_URL = BASE_URL % (self.username, urllib.quote( self.main_password), destination_btc_address, satoshis_to_send) if self.second_password: SEND_URL += '&second_password=%s' % urllib.quote( self.second_password) r = requests.get(SEND_URL) # Log the API call api_call = APICall.objects.create( api_name=APICall.BLOCKCHAIN_WALLET_SEND_BTC, url_hit=SEND_URL, response_code=r.status_code, post_params=None, api_results=r.content, merchant=self.merchant, credential=self) self.handle_status_code(r.status_code) resp_json = json.loads(r.content) if 'error' in resp_json: # TODO: this assumes all error messages here are safe to display to the user return None, None, api_call, resp_json['error'] if 'tx_hash' not in resp_json: # TODO: this assumes all error messages here are safe to display to the user return None, None, api_call, 'No Transaction Hash Received from Blockchain.Info' tx_hash = resp_json['tx_hash'] # Record the Send sent_btc_obj = BCISentBTC.objects.create( credential=self, satoshis=satoshis_to_send, destination_btc_address=destination_btc_address, txn_hash=tx_hash, ) return BTCTransaction.objects.create( txn_hash=tx_hash, satoshis=satoshis_to_send, conf_num=0), sent_btc_obj, api_call, None
def get_receiving_address(self, set_as_merchant_address=False): """ Gets current receiving address. There is no way to generate a new one via API (can be done manually) """ ADDRESS_URL = 'https://www.bitstamp.net/api/bitcoin_deposit_address/' trading_obj = self.get_trading_obj() try: address = trading_obj.bitcoin_deposit_address() msg = '%s is not a valid bitcoin address' % address assert is_valid_btc_address(address), msg # Log the API call APICall.objects.create( api_name=APICall.BITSTAMP_BTC_ADDRESS, url_hit=ADDRESS_URL, response_code=200, post_params=None, # not accurate api_results=address, merchant=self.merchant, credential=self) BaseAddressFromCredential.objects.create(credential=self, b58_address=address, retired_at=None) self.mark_success() if set_as_merchant_address: self.merchant.set_destination_address(dest_address=address, credential_used=self) return address except Exception as e: # Log the API call APICall.objects.create( api_name=APICall.BITSTAMP_BTC_ADDRESS, url_hit=ADDRESS_URL, response_code=0, # this is not accurate post_params=None, # not accurate api_results=str(e), merchant=self.merchant, credential=self) self.mark_failure() print 'Error was: %s' % e return None
def set_blockcypher_webhook(monitoring_address, callback_url, merchant=None): ''' NOT USED ANYMORE Set a blockcypher webhook to monitoring an address. We'll use this for the forwarding address, but the code here is generic and could be used for any address. http://dev.blockcypher.com/#webhooks ''' assert is_valid_btc_address(monitoring_address), monitoring_address assert callback_url, 'Must supply a callback URL' payload = { # YUCK: this is how they do it though 'filter': 'event=tx-confirmation&addr=%s' % monitoring_address, 'url': callback_url, } if BLOCKCYPHER_API_KEY: payload['token'] = BLOCKCYPHER_API_KEY POST_URL = 'http://api.blockcypher.com/v1/btc/main/hooks' r = requests.post(POST_URL, data=json.dumps(payload)) # Log the API call APICall.objects.create( api_name=APICall.BLOCKCYPHER_ADDR_MONITORING, url_hit=POST_URL, response_code=r.status_code, post_params=payload, api_results=r.content, merchant=merchant) err_msg = 'Expected status code 201 but got %s' % r.status_code assert r.status_code == 201, err_msg dict_response = json.loads(r.content) err_msg = 'Expected 0 errors, got: %s' % dict_response['errors'] assert dict_response['errors'] == 0, err_msg return dict_response
def create_wallet_credential(cls, user_password, merchant, user_email=None): """ Create a wallet object and return its (newly created) bitcoin address """ BASE_URL = 'https://blockchain.info/api/v2/create_wallet' label = 'CoinSafe Address %s' % now().strftime("%Y%m%d %H%M%S") post_params = { 'password': user_password, 'api_code': BCI_SECRET_KEY, 'label': label, } if user_email: post_params['email'] = user_email r = requests.post(BASE_URL, data=post_params) # Log the API call APICall.objects.create( api_name=APICall.BLOCKCHAIN_CREATE_WALLET, url_hit=BASE_URL, response_code=r.status_code, post_params=post_params, api_results=r.content, merchant=merchant, ) resp_json = json.loads(r.content) guid = resp_json['guid'] btc_address = resp_json['address'] msg = '%s is not a valid bitcoin address' % btc_address assert is_valid_btc_address(btc_address), msg cls.objects.create(merchant=merchant, username=guid, main_password=user_password, second_password=None) return btc_address
def get_new_receiving_address(self, set_as_merchant_address=False): """ Generates a new receiving address """ ADDRESS_URL = 'https://coinbase.com/api/v1/account/generate_receive_address' label = 'CoinSafe Address %s' % now().strftime("%Y-%m-%d %H:%M:%S") body = 'address[label]=%s' % label r = get_cb_request( url=ADDRESS_URL, api_key=self.api_key, api_secret=self.api_secret, body=body, ) # Log the API call APICall.objects.create(api_name=APICall.COINBASE_NEW_ADDRESS, url_hit=ADDRESS_URL, response_code=r.status_code, post_params={'label': label}, api_results=r.content, merchant=self.merchant, credential=self) self.handle_status_code(r.status_code) resp_json = json.loads(r.content) assert resp_json['success'] is True, resp_json address = resp_json['address'] msg = '%s is not a valid bitcoin address' % address assert is_valid_btc_address(address), msg BaseAddressFromCredential.objects.create(credential=self, b58_address=address, retired_at=None) if set_as_merchant_address: self.merchant.set_destination_address(dest_address=address, credential_used=self) return address
def get_new_receiving_address(self, set_as_merchant_address=False): """ Generates a new receiving address """ label = urllib.quote('CoinSafe Address %s' % now().strftime("%Y-%m-%d %H.%M.%S")) BASE_URL = 'https://blockchain.info/merchant/%s/new_address?password=%s&label=%s' ADDRESS_URL = BASE_URL % (self.username, urllib.quote(self.main_password), label) if self.second_password: ADDRESS_URL += '&second_password=%s' % urllib.quote( self.second_password) r = requests.get(url=ADDRESS_URL) # Log the API call api_call = APICall.objects.create( api_name=APICall.BLOCKCHAIN_WALLET_NEW_ADDRESS, url_hit=ADDRESS_URL, response_code=r.status_code, post_params=None, api_results=r.content, merchant=self.merchant, credential=self) self.handle_status_code(r.status_code) resp_json = json.loads(r.content) address = resp_json['address'] msg = '%s is not a valid bitcoin address' % address assert is_valid_btc_address(address), msg BaseAddressFromCredential.objects.create(credential=self, b58_address=address, retired_at=None) if set_as_merchant_address: self.merchant.set_destination_address(dest_address=address, credential_used=self) return address
def set_bci_webhook(dest_address, callback_url, merchant=None): ''' Set a blockchain.info webhook to generate a receiving address that will forward to dest_address Note: the minimum supported transaction size is 0.001 BTC https://blockchain.info/api/api_receive ''' # TODO: secure callback with some sort of an API key as a URL param cb_section = urlencode({'callback': callback_url}) bci_url = 'https://blockchain.info/api/receive?method=create&address=%s&%s' % ( dest_address, cb_section) r = requests.get(bci_url) # Log the API call APICall.objects.create(api_name=APICall.BCI_RECEIVE_PAYMENTS, url_hit=bci_url, response_code=r.status_code, post_params=None, api_results=r.content, merchant=merchant) err_msg = 'Expected status code 200 but got %s' % r.status_code assert r.status_code == 200, err_msg resp_json = json.loads(r.content) # Safety checks assert resp_json['fee_percent'] == 0 assert resp_json['destination'] == dest_address assert resp_json['callback_url'] == callback_url assert is_valid_btc_address(resp_json['input_address']) return resp_json['input_address']
def send_btc(self, satoshis_to_send, destination_btc_address, destination_email_address=None, notes=None): """ Send satoshis to a destination address or email address. CB requires a fee for txns < .001 BTC, so this method will automatically include txn fees for those. Returns a tuple of the form (some or all may be none): btc_txn, sent_btc_obj, api_call, err_str """ msg = "Can't have both a destination email and BTC address. %s | %s" % ( destination_email_address, destination_btc_address) assert not (destination_email_address and destination_btc_address), msg msg = 'Must send to a destination email OR BTC address' assert destination_email_address or destination_btc_address, msg dest_addr_to_use = None if destination_btc_address: dest_addr_to_use = destination_btc_address send_btc_dict = { 'destination_btc_address': destination_btc_address } msg = '%s is not a valid bitcoin address' % destination_btc_address assert is_valid_btc_address(destination_btc_address), msg if destination_email_address: dest_addr_to_use = destination_email_address send_btc_dict = {'destination_email': destination_email_address} msg = '%s is not a valid email address' % destination_email_address # FIXME: implement btc_to_send = satoshis_to_btc(satoshis_to_send) SEND_URL = 'https://coinbase.com/api/v1/transactions/send_money' body = 'transaction[to]=%s&transaction[amount]=%s' % (dest_addr_to_use, btc_to_send) post_params = {'to': dest_addr_to_use, 'amount': btc_to_send} if satoshis_to_send <= btc_to_satoshis( .001) and not destination_email_address: # https://coinbase.com/api/doc/1.0/transactions/send_money.html # Coinbase pays transaction fees on payments greater than or equal to 0.001 BTC. # But for smaller amounts you have to add your own # For some reason, coinbase requires 2x fees of .2 mBTC vs (.1 mBTC) body += '&transaction[user_fee]=0.0002' post_params['user_fee'] = 0.0002 if notes: # TODO: url encode this? body += '&transaction[notes]=' + notes post_params['notes'] = notes r = get_cb_request(url=SEND_URL, api_key=self.api_key, api_secret=self.api_secret, body=body) # Log the API call api_call = APICall.objects.create(api_name=APICall.COINBASE_SEND_BTC, url_hit=SEND_URL, response_code=r.status_code, post_params=post_params, api_results=r.content, merchant=self.merchant, credential=self) self.handle_status_code(r.status_code) resp_json = json.loads(r.content) if resp_json.get('error') or resp_json.get('errors'): err_str = resp_json.get('error') # combine the two if resp_json.get('errors'): if err_str: err_str += ' %s' % resp_json.get('errors') else: err_str = resp_json.get('errors') # this assumes all error messages here are safe to display to the user return None, None, api_call, err_str transaction = resp_json['transaction'] satoshis = -1 * btc_to_satoshis(transaction['amount']['amount']) txn_hash = transaction['hsh'] # Record the Send send_btc_dict.update({ 'credential': self, 'txn_hash': txn_hash, 'satoshis': satoshis, 'transaction_id': transaction['id'], 'notes': notes, }) sent_btc_obj = CBSSentBTC.objects.create(**send_btc_dict) if txn_hash: return BTCTransaction.objects.create( txn_hash=txn_hash, satoshis=satoshis, conf_num=0), sent_btc_obj, api_call, None else: # Coinbase seems finicky about transaction hashes return None, sent_btc_obj, api_call, None
def clean_btc_address(self): address = self.cleaned_data.get('btc_address') if not is_valid_btc_address(address): msg = _("Sorry, that's not a valid bitcoin address") raise forms.ValidationError(msg) return address
def send_btc(self, satoshis_to_send, destination_btc_address): """ Returns a tuple of the form (some or all may be none): btc_txn, sent_btc_obj, api_call, err_str """ msg = '%s is not a valid bitcoin address' % destination_btc_address assert is_valid_btc_address(destination_btc_address), msg btc_to_send = satoshis_to_btc(satoshis_to_send) SEND_URL = 'https://www.bitstamp.net/api/bitcoin_withdrawal/' trading_obj = self.get_trading_obj() try: post_params = { 'amount': btc_to_send, 'address': destination_btc_address } withdrawal_info = trading_obj.bitcoin_withdrawal(**post_params) withdrawal_id = withdrawal_info['id'] msg = "%s is not an int, it's a %s" % (withdrawal_id, type(withdrawal_id)) assert type(withdrawal_id) is int, msg # Log the API call api_call = APICall.objects.create( api_name=APICall.BITSTAMP_SEND_BTC, url_hit=SEND_URL, response_code=200, post_params=post_params, api_results=str(withdrawal_info), merchant=self.merchant, credential=self) self.mark_success() except Exception as e: # Log the API Call api_call = APICall.objects.create( api_name=APICall.BITSTAMP_SEND_BTC, url_hit=SEND_URL, response_code=0, # not accurate post_params=post_params, api_results=str(e), merchant=self.merchant, credential=self) self.mark_failure() print 'Error was: %s' % e # TODO: this assumes all error messages here are safe to display to the user return None, None, api_call, str(e) sent_btc_obj = BTSSentBTC.objects.create( credential=self, satoshis=satoshis_to_send, destination_btc_address=destination_btc_address, withdrawal_id=withdrawal_id, status='0', ) # This API doesn't return a TX hash on sending bitcoin :( return None, sent_btc_obj, api_call, None