예제 #1
0
 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
예제 #2
0
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
예제 #3
0
    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)
예제 #4
0
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
예제 #5
0
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)
예제 #6
0
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")
예제 #7
0
 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
예제 #8
0
    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
예제 #9
0
    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
예제 #10
0
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
예제 #11
0
    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
예제 #12
0
    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
예제 #13
0
    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
예제 #14
0
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']
예제 #15
0
    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
예제 #16
0
 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
예제 #17
0
    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