├── .gitignore ├── .travis.yml ├── MANIFEST.in ├── README.md ├── circle.yml ├── coinbase ├── __init__.py ├── ca_certs.txt ├── config.py ├── errors.py ├── mock.py ├── models │ ├── __init__.py │ ├── amount.py │ ├── button.py │ ├── contact.py │ ├── error.py │ ├── order.py │ ├── transaction.py │ ├── transfer.py │ ├── user.py │ └── util.py └── tests │ ├── __init__.py │ ├── account_setup.py │ ├── http_mocking.py │ ├── test_amount.py │ ├── test_balance.py │ ├── test_button_1.py │ ├── test_button_2.py │ ├── test_buy_price.py │ ├── test_contacts.py │ ├── test_error.py │ ├── test_exchange_rate.py │ ├── test_mocking.py │ ├── test_order.py │ ├── test_order_callback.py │ ├── test_order_creation.py │ ├── test_order_list.py │ ├── test_receive_address.py │ ├── test_request.py │ ├── test_sell_price.py │ ├── test_send_btc_to_bitcoin.py │ ├── test_send_btc_to_email.py │ ├── test_send_usd_to_bitcoin.py │ ├── test_send_usd_to_email.py │ ├── test_transaction.py │ ├── test_transaction_list.py │ ├── test_transfer.py │ └── test_user.py ├── coinbase_oauth2 ├── __init__.py ├── ca_certs.txt ├── requirements.txt └── templates │ └── register.jinja2 ├── example.py ├── requirements.txt ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | 37 | # Vim 38 | *.swp 39 | 40 | # Other 41 | 42 | /.idea/ 43 | /coinbase_oauth2/secrets.py 44 | /MANIFEST 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: pip install --use-mirrors -r requirements.txt 5 | notifications: 6 | email: 7 | on_success: change 8 | on_failure: change 9 | script: 10 | - nosetests coinbase 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | README.md 2 | coinbase/ca_certs.txt 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Unofficial Coinbase Python Library 2 | ================================== 3 | 4 | Python Library for the Coinbase API for use with three legged oAuth2 and classic API key usage 5 | 6 | [](https://travis-ci.org/sibblegp/coinbase) 7 | 8 | ## Version 9 | 10 | 0.3.0 11 | 12 | ## Requirements 13 | - [Coinbase Account](http://www.coinbase.com) 14 | - [Requests](http://docs.python-requests.org/en/latest/) 15 | - [oauth2client](https://developers.google.com/api-client-library/python/guide/aaa_oauth) 16 | 17 | ## Installation 18 | 19 | Automatic installation using [pip](http://pypi.python.org/pypi): 20 | 21 | pip install coinbase 22 | 23 | ## Usage 24 | 25 | ```python 26 | from coinbase import CoinbaseAccount 27 | 28 | print(CoinbaseAccount().exchange_rates['usd_to_btc']) 29 | 30 | account = CoinbaseAccount(oauth2_credentials=JSON_OAUTH2_TOKEN) 31 | transaction = account.send('recipient@example.com', 1.0) 32 | print(transaction.status) 33 | ``` 34 | 35 | ## Examples / Quickstart 36 | 37 | This repo includes an example.py file which demonstrates: 38 | 39 | * Creating the Account object 40 | * Sending BitCoin 41 | * Requesting BitCoin 42 | * Getting the account's balance 43 | * Getting the buy/sell price of BitCoin at CoinBase 44 | * Listing historical transactions 45 | 46 | It also includes a small webserver in the coinbase_oauth2 module which demonstrates how to obtain an oauth2 token. 47 | 48 | ## Methods 49 | 50 | More documentation coming soon. 51 | 52 | ## Changelog 53 | 54 | 0.3.0 55 | 56 | * Major Updates 57 | 58 | 0.2.1 59 | 60 | * Updated SSL Certs 61 | * Added payment button support 62 | 63 | 0.2.0 64 | 65 | * Push many updates to PyPi 66 | 67 | 0.1.0-7 68 | 69 | * Fix SSL Certificates 70 | 71 | 0.1.0-5 72 | 73 | * Set flag for token status when initializing 74 | * Raise error if transaction fails 75 | 76 | 0.1.0-4 77 | 78 | * User Details unittest 79 | * Small tweaks 80 | 81 | 0.1.0-3 82 | 83 | * Get User Details 84 | * Refactor some attribute capitalization 85 | 86 | 0.1.0-2 87 | 88 | * Generate New Receive Address 89 | 90 | 0.1.0 91 | 92 | * Initial Commit 93 | 94 | ## Contributing 95 | 96 | Contributions are greatly appreciated. Please make all requests using built in issue tracking at GitHub. 97 | 98 | ## Credits 99 | 100 | - George Sibble <gsibble@gmail.com> 101 | - Chris Martin <ch.martin@gmail.com> 102 | - Matt Luongo 103 | 104 | ## License 105 | 106 | (The MIT License) 107 | 108 | Copyright (c) 2013 George Sibble <gsibble@gmail.com> 109 | 110 | Permission is hereby granted, free of charge, to any person obtaining 111 | a copy of this software and associated documentation files (the 112 | 'Software'), to deal in the Software without restriction, including 113 | without limitation the rights to use, copy, modify, merge, publish, 114 | distribute, sublicense, and/or sell copies of the Software, and to 115 | permit persons to whom the Software is furnished to do so, subject to 116 | the following conditions: 117 | 118 | The above copyright notice and this permission notice shall be 119 | included in all copies or substantial portions of the Software. 120 | 121 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 122 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 123 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 124 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 125 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 126 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 127 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 128 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | python: 3 | version: 2.7.5 4 | dependencies: 5 | pre: 6 | - pyenv global 3.3.2 7 | override: 8 | - pip install tox 9 | test: 10 | override: 11 | - tox 12 | -------------------------------------------------------------------------------- /coinbase/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Coinbase Python Client Library 3 | 4 | AUTHOR 5 | 6 | George Sibble 7 | Github: sibblegp 8 | 9 | LICENSE (The MIT License) 10 | 11 | Copyright (c) 2013 George Sibble "gsibble@gmail.com" 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining 14 | a copy of this software and associated documentation files (the 15 | "Software"), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 27 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 28 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 29 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 30 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | """ 33 | 34 | __author__ = 'gsibble' 35 | 36 | try: 37 | from oauth2client.client import AccessTokenRefreshError, \ 38 | OAuth2Credentials, AccessTokenCredentialsError 39 | oauth2_supported = True 40 | except ImportError: 41 | oauth2_supported = False 42 | 43 | try: 44 | from urllib.parse import urlsplit, urlunsplit, quote 45 | except ImportError: 46 | from urllib import quote 47 | from urlparse import urlsplit, urlunsplit 48 | 49 | import httplib2 50 | import json 51 | import os 52 | import hashlib 53 | import hmac 54 | import time 55 | import re 56 | from decimal import Decimal 57 | from warnings import warn 58 | 59 | import requests 60 | from requests.auth import AuthBase 61 | 62 | from coinbase.config import COINBASE_ENDPOINT 63 | from coinbase.models import * 64 | from coinbase.errors import * 65 | from coinbase.mock import CoinbaseAccountMock 66 | 67 | 68 | url_path_component_regex = re.compile('^[0-9a-z_\-]+$', re.I) 69 | 70 | 71 | def coinbase_url(*args): 72 | 73 | args = list(map(str, args)) 74 | 75 | # make sure we don't concatenate anything too weird into the url 76 | for c in args: 77 | if not url_path_component_regex.match(c): 78 | raise UrlValueError(c) 79 | 80 | return '/'.join([COINBASE_ENDPOINT] + args) 81 | 82 | 83 | class CoinbaseAuth(AuthBase): 84 | def __init__(self, oauth2_credentials=None, api_key=None, api_secret=None): 85 | #CA Cert Path 86 | ca_directory = os.path.abspath(__file__).split('/')[0:-1] 87 | 88 | ca_path = '/'.join(ca_directory) + '/ca_certs.txt' 89 | 90 | #Set CA certificates (breaks without them) 91 | self.http = httplib2.Http(ca_certs=ca_path) 92 | 93 | self.oauth2_credentials = None 94 | 95 | self.api_key = api_key 96 | self.api_secret = api_secret 97 | 98 | if oauth2_credentials is not None: 99 | if not oauth2_supported: 100 | raise RuntimeError('oauth2 is not supported in this environment') 101 | 102 | #Create our credentials from the JSON sent 103 | self.oauth2_credentials = \ 104 | OAuth2Credentials.from_json(oauth2_credentials) 105 | 106 | #Check our token 107 | self.token_expired = False 108 | try: 109 | self._check_oauth_expired() 110 | except AccessTokenCredentialsError: 111 | self.token_expired = True 112 | 113 | elif api_key and api_secret is None: 114 | warn("API key authentication without a secret has been deprecated" 115 | " by Coinbase- you should use a new key with a secret!") 116 | 117 | def _check_oauth_expired(self): 118 | """ 119 | Internal function to check if the oauth2 credentials are expired 120 | """ 121 | 122 | #Check if they are expired 123 | if self.oauth2_credentials.access_token_expired: 124 | 125 | #Raise the appropriate error 126 | raise AccessTokenCredentialsError 127 | 128 | def refresh_oauth(self): 129 | """ 130 | Refresh our oauth2 token 131 | :return: JSON representation of oauth token 132 | :raise: AccessTokenRefreshError if there was an error refreshing the 133 | token 134 | """ 135 | 136 | #See if we can refresh the token 137 | try: 138 | #Ask to refresh the token 139 | self.oauth2_credentials.refresh(http=self.http) 140 | 141 | #We were successful 142 | 143 | #Return the token for storage 144 | return self.oauth2_credentials 145 | 146 | #If the refresh token was invalid 147 | except AccessTokenRefreshError: 148 | 149 | warn('Your refresh token is invalid.') 150 | 151 | #Raise the appropriate error 152 | raise AccessTokenRefreshError 153 | 154 | def __call__(self, req): 155 | if self.oauth2_credentials: 156 | #Check if the oauth token is expired and refresh it if necessary 157 | self._check_oauth_expired() 158 | 159 | self.oauth2_credentials.apply(headers=req.headers) 160 | elif self.api_key is not None and self.api_secret is not None: 161 | nonce = int(time.time() * 1e6) 162 | message = str(nonce) + req.url + ('' if not req.body else req.body) 163 | signature = hmac.new(self.api_secret, message, hashlib.sha256).hexdigest() 164 | req.headers.update({'ACCESS_KEY': self.api_key, 165 | 'ACCESS_SIGNATURE': signature, 166 | 'ACCESS_NONCE': nonce}) 167 | 168 | elif self.api_key is not None: 169 | url_parts = urlsplit(req.url) 170 | new_query = '&'.join( 171 | filter(None, [url_parts.query, 172 | quote('api_key={}'.format(self.api_key))])) 173 | req.url = urlunsplit(url_parts._replace(query=new_query)) 174 | return req 175 | 176 | 177 | class CoinbaseAccount(object): 178 | """ 179 | Primary object for interacting with a Coinbase account 180 | 181 | You may use oauth credentials, an API key + secret, a lone API key 182 | (deprecated), or no auth (for unauthenticated resources only). 183 | """ 184 | 185 | def __init__(self, oauth2_credentials=None, api_key=None, api_secret=None, 186 | allow_transfers=True): 187 | """ 188 | :param oauth2_credentials: JSON representation of Coinbase oauth2 189 | credentials 190 | :param api_key: Coinbase API key 191 | :param api_secret: Coinbase API secret. Typically included with a key, 192 | since key-only auth is deprecated. 193 | :param allow_transfers: Whether to allow sending money. 194 | You can set this to False for safety while in a development 195 | environment if you want to more sure you don't actually send 196 | any money around. 197 | """ 198 | 199 | self.allow_transfers = allow_transfers 200 | 201 | self.authenticated = (oauth2_credentials is not None 202 | or api_key is not None) 203 | if self.authenticated: 204 | self.auth = CoinbaseAuth(oauth2_credentials=oauth2_credentials, 205 | api_key=api_key, api_secret=api_secret) 206 | else: 207 | self.auth = {} 208 | 209 | #Set up our requests session 210 | self.session = requests.session() 211 | self.session.auth = self.auth 212 | 213 | #Set our Content-Type 214 | self.session.headers.update({'content-type': 'application/json'}) 215 | 216 | def _require_allow_transfers(self): 217 | if not self.allow_transfers: 218 | raise Exception('Transfers are not enabled') 219 | 220 | def _require_authentication(self): 221 | if not self.authenticated: 222 | raise Exception('Authentication credentials required') 223 | 224 | @property 225 | def balance(self): 226 | """ 227 | Retrieve coinbase's account balance 228 | 229 | :return: CoinbaseAmount with currency attribute 230 | """ 231 | self._require_authentication() 232 | 233 | url = coinbase_url('account', 'balance') 234 | response = self.session.get(url) 235 | return CoinbaseAmount.from_coinbase_dict(response.json()) 236 | 237 | @property 238 | def receive_address(self): 239 | """ 240 | Get the account's current receive address 241 | 242 | :return: String address of account 243 | """ 244 | self._require_authentication() 245 | 246 | url = coinbase_url('account', 'receive_address') 247 | response = self.session.get(url) 248 | return response.json()['address'] 249 | 250 | def contacts(self, page=None, limit=None, query=None): 251 | """ 252 | Contacts the user has previously sent to or received from. 253 | 254 | :param page: Can be used to page through results. Default value is 1. 255 | :param limit: Number of records to return. Maximum is 1000. Default 256 | value is 25. 257 | :param query: Optional partial string match to filter contacts. 258 | :return: list of CoinbaseContact 259 | """ 260 | self._require_authentication() 261 | 262 | url = coinbase_url('contacts') 263 | 264 | params = {} 265 | if page is not None: 266 | params['page'] = page 267 | if limit is not None: 268 | params['limit'] = limit 269 | if query is not None: 270 | params['query'] = query 271 | 272 | response = self.session.get(url, params=params) 273 | return [CoinbaseContact.from_coinbase_dict(x['contact']) 274 | for x in response.json()['contacts']] 275 | 276 | def buy_price(self, qty=1): 277 | """ 278 | Return the buy price of BitCoin in USD 279 | :param qty: Quantity of BitCoin to price 280 | :return: CoinbaseAmount with currency attribute 281 | """ 282 | url = coinbase_url('prices', 'buy') 283 | params = {'qty': qty} 284 | response = self.session.get(url, params=params) 285 | return CoinbaseAmount.from_coinbase_dict(response.json()) 286 | 287 | def sell_price(self, qty=1): 288 | """ 289 | Return the sell price of BitCoin in USD 290 | :param qty: Quantity of BitCoin to price 291 | :return: CoinbaseAmount with currency attribute 292 | """ 293 | url = coinbase_url('prices', 'sell') 294 | params = {'qty': qty} 295 | response = self.session.get(url, params=params) 296 | results = response.json() 297 | return CoinbaseAmount.from_coinbase_dict(results) 298 | 299 | def buy_btc(self, qty, pricevaries=False): 300 | """ 301 | Buy BitCoin from Coinbase for USD 302 | :param qty: BitCoin quantity to be bought 303 | :param pricevaries: Boolean value that indicates whether or not the 304 | transaction should be processed if Coinbase cannot guarantee 305 | the current price. 306 | :return: CoinbaseTransfer with all transfer details on success 307 | :raise: CoinbaseError with the error list received from Coinbase on 308 | failure 309 | """ 310 | self._require_allow_transfers() 311 | self._require_authentication() 312 | 313 | url = coinbase_url('buys') 314 | request_data = { 315 | "qty": qty, 316 | "agree_btc_amount_varies": pricevaries 317 | } 318 | response = self.session.post(url=url, data=json.dumps(request_data)) 319 | 320 | response_parsed = response.json() 321 | if not response_parsed.get('success'): 322 | raise CoinbaseError('Failed to buy btc.', 323 | response_parsed.get('errors')) 324 | 325 | return CoinbaseTransfer.from_coinbase_dict(response_parsed['transfer']) 326 | 327 | def sell_btc(self, qty): 328 | """ 329 | Sell Bitcoin to Coinbase for USD 330 | :param qty: BitCoin quantity to be sold 331 | :return: CoinbaseTransfer with all transfer details on success 332 | :raise: CoinbaseError with the error list received from Coinbase on 333 | failure 334 | """ 335 | self._require_allow_transfers() 336 | self._require_authentication() 337 | 338 | url = coinbase_url('sells') 339 | request_data = { 340 | "qty": qty, 341 | } 342 | response = self.session.post(url=url, data=json.dumps(request_data)) 343 | response_parsed = response.json() 344 | if not response_parsed.get('success'): 345 | raise CoinbaseError('Failed to sell btc.', 346 | response_parsed.get('errors')) 347 | 348 | return CoinbaseTransfer.from_coinbase_dict(response_parsed['transfer']) 349 | 350 | def request(self, from_email, amount, notes=''): 351 | """ 352 | Request BitCoin from an email address to be delivered to this account 353 | :param from_email: Email from which to request BTC 354 | :param amount: Amount to request (CoinbaseAmount) 355 | :param notes: Notes to include with the request 356 | :return: CoinbaseTransaction with status and details 357 | :raise: CoinbaseError with the error list received from Coinbase on 358 | failure 359 | """ 360 | self._require_allow_transfers() 361 | self._require_authentication() 362 | 363 | url = coinbase_url('transactions', 'request_money') 364 | 365 | request_data = { 366 | 'transaction': { 367 | 'from': from_email, 368 | 'notes': notes, 369 | }, 370 | } 371 | 372 | if amount.currency == 'BTC': 373 | request_data['transaction']['amount'] = str(amount.amount) 374 | else: 375 | request_data['transaction']['amount_string'] = str(amount.amount) 376 | request_data['transaction']['amount_currency_iso'] = amount.currency 377 | 378 | response = self.session.post(url=url, data=json.dumps(request_data)) 379 | response_parsed = response.json() 380 | if not response_parsed.get('success'): 381 | raise CoinbaseError('Failed to request btc.', 382 | response_parsed.get('errors')) 383 | 384 | return CoinbaseTransaction \ 385 | .from_coinbase_dict(response_parsed['transaction']) 386 | 387 | def send(self, to_address, amount, notes='', user_fee=None, idem=None): 388 | """ 389 | Send BitCoin from this account to either an email address or a BTC 390 | address 391 | :param to_address: Email or BTC address to where coin should be sent 392 | :param amount: Amount of currency to send (CoinbaseAmount) 393 | :param notes: Notes to be included with transaction 394 | :param user_fee: an optionally included miner's fee. Coinbase pays 395 | feeds on all transfers over 0.01 BTC, but under that you should include 396 | a fee. 397 | :param idem: An optional token to ensure idempotence. If a previous 398 | transaction with the same idem parameter already exists for this 399 | sender, that previous transaction will be returned and a new one will 400 | not be created. Max length 100 characters. 401 | :return: CoinbaseTransaction with status and details 402 | :raise: CoinbaseError with the error list received from Coinbase on 403 | failure 404 | """ 405 | self._require_allow_transfers() 406 | self._require_authentication() 407 | 408 | url = coinbase_url('transactions', 'send_money') 409 | 410 | request_data = { 411 | 'transaction': { 412 | 'to': to_address, 413 | 'notes': notes, 414 | }, 415 | } 416 | 417 | if amount.currency == 'BTC': 418 | request_data['transaction']['amount'] = str(amount.amount) 419 | else: 420 | request_data['transaction']['amount_string'] = str(amount.amount) 421 | request_data['transaction']['amount_currency_iso'] = amount.currency 422 | 423 | if user_fee is not None: 424 | request_data['transaction']['user_fee'] = str(user_fee) 425 | 426 | if idem is not None: 427 | request_data['transaction']['idem'] = str(idem) 428 | 429 | response = self.session.post(url=url, data=json.dumps(request_data)) 430 | response_parsed = response.json() 431 | 432 | if not response_parsed.get('success'): 433 | raise CoinbaseError('Failed to send btc.', 434 | response_parsed.get('errors')) 435 | 436 | return CoinbaseTransaction \ 437 | .from_coinbase_dict(response_parsed['transaction']) 438 | 439 | def transactions(self, count=30): 440 | """ 441 | Retrieve the list of transactions for the current account 442 | :param count: How many transactions to retrieve 443 | :return: List of CoinbaseTransaction objects 444 | """ 445 | self._require_authentication() 446 | 447 | url = coinbase_url('transactions') 448 | pages = int((count - 1) / 30) + 1 449 | transactions = [] 450 | 451 | reached_final_page = False 452 | 453 | for page in range(1, pages + 1): 454 | 455 | if not reached_final_page: 456 | params = {'page': page} 457 | response = self.session.get(url=url, params=params) 458 | parsed_transactions = response.json() 459 | 460 | if parsed_transactions['num_pages'] == page: 461 | reached_final_page = True 462 | 463 | for transaction in parsed_transactions['transactions']: 464 | tx = CoinbaseTransaction \ 465 | .from_coinbase_dict(transaction['transaction']) 466 | transactions.append(tx) 467 | 468 | return transactions 469 | 470 | def transfers(self, count=30): 471 | """ 472 | Retrieve the list of transfers for the current account 473 | :param count: How many transfers to retrieve 474 | :return: List of CoinbaseTransfer objects 475 | """ 476 | self._require_authentication() 477 | 478 | url = coinbase_url('transfers') 479 | pages = int((count - 1) / 30) + 1 480 | transfers = [] 481 | 482 | reached_final_page = False 483 | 484 | for page in range(1, pages + 1): 485 | 486 | if not reached_final_page: 487 | params = {'page': page} 488 | response = self.session.get(url=url, params=params) 489 | parsed_transfers = response.json() 490 | 491 | if parsed_transfers['num_pages'] == page: 492 | reached_final_page = True 493 | 494 | for transfer in parsed_transfers['transfers']: 495 | transfers.append(CoinbaseTransfer 496 | .from_coinbase_dict(transfer['transfer'])) 497 | 498 | return transfers 499 | 500 | def get_transaction(self, transaction_id): 501 | """ 502 | Retrieve a transaction's details 503 | :param transaction_id: Unique transaction identifier 504 | :return: CoinbaseTransaction object with transaction details 505 | """ 506 | self._require_authentication() 507 | 508 | url = coinbase_url('transactions', transaction_id) 509 | response = self.session.get(url) 510 | results = response.json() 511 | 512 | if not results.get('success', True): 513 | pass 514 | #TODO: Add error handling 515 | 516 | return CoinbaseTransaction.from_coinbase_dict(results['transaction']) 517 | 518 | def get_user_details(self): 519 | """ 520 | Retrieve the current user's details 521 | 522 | :return: CoinbaseUser object with user details 523 | """ 524 | self._require_authentication() 525 | 526 | url = coinbase_url('users') 527 | response = self.session.get(url) 528 | results = response.json() 529 | 530 | return CoinbaseUser.from_coinbase_dict(results['users'][0]['user']) 531 | 532 | def generate_receive_address(self, callback_url=None): 533 | """ 534 | Generate a new receive address 535 | :param callback_url: The URL to receive instant payment notifications 536 | :return: The new string address 537 | """ 538 | self._require_authentication() 539 | 540 | url = coinbase_url('account', 'generate_receive_address') 541 | request_data = { 542 | 'address': { 543 | 'callback_url': callback_url 544 | } 545 | } 546 | response = self.session.post(url=url, data=json.dumps(request_data)) 547 | return response.json()['address'] 548 | 549 | def create_button(self, button, account_id=None): 550 | """ 551 | Create a new payment button, page, or iframe. 552 | 553 | See https://coinbase.com/api/doc/1.0/buttons/create.html for details. 554 | 555 | :param button: CoinbasePaymentButton 556 | :param account_id: Specify for which account is the button created. 557 | The default is your primary account. 558 | :return: CoinbasePaymentButton (which should have the same attributes 559 | as the one given, except now it has an ID generated by 560 | Coinbase) 561 | """ 562 | self._require_authentication() 563 | 564 | url = coinbase_url('buttons') 565 | 566 | request_data = { 567 | 'button': button.to_coinbase_dict() 568 | } 569 | 570 | if account_id is not None: 571 | request_data['account_id'] = account_id 572 | 573 | response = self.session.post(url=url, data=json.dumps(request_data)) 574 | resp_data = response.json() 575 | if not resp_data.get('success') or 'button' not in resp_data: 576 | error_msg = 'Error creating button' 577 | error_msg += ': ' + u'\n'.join(resp_data.get('errors',['Unknown'])) 578 | raise RuntimeError(error_msg) 579 | 580 | return CoinbasePaymentButton.from_coinbase_dict(resp_data['button']) 581 | 582 | @property 583 | def exchange_rates(self): 584 | """ 585 | Retrieve BTC to fiat (and vice versus) exchange rates in various 586 | currencies. It has keys for both btc_to_xxx and xxx_to_btc. 587 | :return: Dict with str keys and Decimal values 588 | """ 589 | url = coinbase_url('currencies', 'exchange_rates') 590 | rates = requests.get(url).json() 591 | return dict(((k, Decimal(v)) for k, v in rates.items())) 592 | 593 | def get_exchange_rate(self, from_currency, to_currency): 594 | url = coinbase_url('currencies', 'exchange_rates') 595 | rates = requests.get(url).json() 596 | return Decimal(rates['{}_to_{}'.format( 597 | from_currency.lower(), to_currency.lower() 598 | )]) 599 | 600 | def orders(self, account_id=None, page=None): 601 | """ 602 | Returns a merchant's orders that they have received. 603 | Sorted by created_at in descending order. 604 | 605 | :param account_id: Specify which account is used for fetching data. 606 | The default is your primary account. 607 | :param page: Can be used to page through results. Default is 1. 608 | :return: List of CoinbaseOrder 609 | """ 610 | self._require_authentication() 611 | 612 | url = coinbase_url('orders') 613 | 614 | params = {} 615 | if account_id is not None: 616 | params['account_id'] = account_id 617 | if page is not None: 618 | params['page'] = page 619 | 620 | response = self.session.get(url=url, params=params) 621 | return list(map( 622 | CoinbaseOrder.from_coinbase_dict, 623 | response.json()['orders'] 624 | )) 625 | 626 | def get_order(self, id_or_custom_field, account_id=None): 627 | self._require_authentication() 628 | 629 | url = coinbase_url('orders', id_or_custom_field) 630 | 631 | params = {} 632 | if account_id is not None: 633 | params['account_id'] = account_id 634 | 635 | response = self.session.get(url=url, params=params) 636 | return CoinbaseOrder.from_coinbase_dict(response.json()) 637 | 638 | def create_button_and_order(self, button): 639 | self._require_authentication() 640 | 641 | url = coinbase_url('orders') 642 | 643 | request_data = { 644 | 'button': button.to_coinbase_dict() 645 | } 646 | 647 | response = self.session.post(url=url, data=json.dumps(request_data)) 648 | return CoinbaseOrder.from_coinbase_dict(response.json()) 649 | 650 | def create_order_from_button(self, button_id): 651 | self._require_authentication() 652 | 653 | url = coinbase_url('buttons', button_id, 'create_order') 654 | 655 | response = self.session.post(url=url) 656 | return CoinbaseOrder.from_coinbase_dict(response.json()) 657 | -------------------------------------------------------------------------------- /coinbase/ca_certs.txt: -------------------------------------------------------------------------------- 1 | ## DigiCert High Assurance EV Root CA 2 | -----BEGIN CERTIFICATE----- 3 | MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs 4 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 5 | d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 6 | ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL 7 | MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 8 | LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug 9 | RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm 10 | +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW 11 | PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM 12 | xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB 13 | Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 14 | hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg 15 | EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF 16 | MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA 17 | FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec 18 | nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z 19 | eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF 20 | hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 21 | Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe 22 | vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep 23 | +OkuE6N36B9K 24 | -----END CERTIFICATE----- 25 | 26 | ## DigiCert Assured ID Root CA 27 | -----BEGIN CERTIFICATE----- 28 | MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl 29 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 30 | d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv 31 | b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG 32 | EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl 33 | cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi 34 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c 35 | JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP 36 | mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ 37 | wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 38 | VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ 39 | AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB 40 | AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW 41 | BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun 42 | pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC 43 | dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf 44 | fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm 45 | NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx 46 | H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe 47 | +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== 48 | -----END CERTIFICATE----- 49 | 50 | ## DigiCert Global Root CA 51 | -----BEGIN CERTIFICATE----- 52 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh 53 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 54 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD 55 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT 56 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 57 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 58 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB 59 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 60 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 61 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P 62 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 63 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO 64 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR 65 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw 66 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr 67 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 68 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF 69 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls 70 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk 71 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= 72 | -----END CERTIFICATE----- 73 | 74 | ## DigiCert Assured ID Root G2 75 | -----BEGIN CERTIFICATE----- 76 | MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl 77 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 78 | d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv 79 | b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG 80 | EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl 81 | cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi 82 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA 83 | n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc 84 | biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp 85 | EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA 86 | bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu 87 | YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB 88 | AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW 89 | BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI 90 | QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I 91 | 0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni 92 | lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 93 | B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv 94 | ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo 95 | IhNzbM8m9Yop5w== 96 | -----END CERTIFICATE----- 97 | 98 | ## DigiCert Assured ID Root G3 99 | -----BEGIN CERTIFICATE----- 100 | MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw 101 | CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu 102 | ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg 103 | RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV 104 | UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu 105 | Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq 106 | hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf 107 | Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q 108 | RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ 109 | BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD 110 | AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY 111 | JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv 112 | 6pZjamVFkpUBtA== 113 | -----END CERTIFICATE----- 114 | 115 | ## DigiCert Global Root G2 116 | -----BEGIN CERTIFICATE----- 117 | MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh 118 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 119 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH 120 | MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT 121 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j 122 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG 123 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI 124 | 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx 125 | 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ 126 | q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz 127 | tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ 128 | vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP 129 | BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV 130 | 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY 131 | 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 132 | NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG 133 | Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 134 | 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe 135 | pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl 136 | MrY= 137 | -----END CERTIFICATE----- 138 | 139 | ## DigiCert Global Root G3 140 | -----BEGIN CERTIFICATE----- 141 | MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw 142 | CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu 143 | ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe 144 | Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw 145 | EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x 146 | IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF 147 | K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG 148 | fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO 149 | Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd 150 | BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx 151 | AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ 152 | oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 153 | sycX 154 | -----END CERTIFICATE----- 155 | 156 | ## DigiCert Trusted Root G4 157 | -----BEGIN CERTIFICATE----- 158 | MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi 159 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 160 | d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg 161 | RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV 162 | UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu 163 | Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG 164 | SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y 165 | ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If 166 | xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV 167 | ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO 168 | DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ 169 | jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ 170 | CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi 171 | EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM 172 | fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY 173 | uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK 174 | chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t 175 | 9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB 176 | hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD 177 | ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 178 | SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd 179 | +SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc 180 | fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa 181 | sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N 182 | cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N 183 | 0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie 184 | 4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI 185 | r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 186 | /YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm 187 | gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ 188 | -----END CERTIFICATE----- 189 | 190 | ## VeriSign Class 3 Public Primary CA - G2 191 | -----BEGIN CERTIFICATE----- 192 | MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ 193 | BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh 194 | c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy 195 | MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp 196 | emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X 197 | DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw 198 | FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg 199 | UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo 200 | YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 201 | MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB 202 | AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 203 | pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 204 | 13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID 205 | AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk 206 | U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i 207 | F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY 208 | oJ2daZH9 209 | -----END CERTIFICATE----- 210 | 211 | ## VeriSign Class 3 Public Primary CA 212 | -----BEGIN CERTIFICATE----- 213 | MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG 214 | A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz 215 | cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 216 | MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV 217 | BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt 218 | YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN 219 | ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE 220 | BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is 221 | I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G 222 | CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i 223 | 2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ 224 | 2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ 225 | -----END CERTIFICATE----- 226 | 227 | ## VeriSign Class 3 Primary CA - G5 228 | -----BEGIN CERTIFICATE----- 229 | MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB 230 | yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL 231 | ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp 232 | U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW 233 | ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 234 | aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL 235 | MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW 236 | ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln 237 | biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp 238 | U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y 239 | aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 240 | nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex 241 | t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz 242 | SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG 243 | BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ 244 | rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ 245 | NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E 246 | BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH 247 | BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy 248 | aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv 249 | MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE 250 | p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y 251 | 5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK 252 | WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ 253 | 4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N 254 | hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq 255 | -----END CERTIFICATE----- 256 | 257 | ## VeriSign Class 3 Public Primary CA - G3 258 | -----BEGIN CERTIFICATE----- 259 | MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw 260 | CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl 261 | cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu 262 | LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT 263 | aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp 264 | dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD 265 | VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT 266 | aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ 267 | bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu 268 | IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg 269 | LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b 270 | N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t 271 | KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu 272 | kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm 273 | CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ 274 | Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu 275 | imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te 276 | 2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe 277 | DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC 278 | /Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p 279 | F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt 280 | TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== 281 | -----END CERTIFICATE----- 282 | 283 | ## VeriSign Class 3 Public Primary CA - G4 284 | -----BEGIN CERTIFICATE----- 285 | MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL 286 | MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW 287 | ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln 288 | biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp 289 | U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y 290 | aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG 291 | A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp 292 | U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg 293 | SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln 294 | biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 295 | IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm 296 | GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve 297 | fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw 298 | AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ 299 | aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj 300 | aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW 301 | kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC 302 | 4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga 303 | FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== 304 | -----END CERTIFICATE----- 305 | 306 | ## VeriSign Class 2 Public Primary CA - G3 307 | -----BEGIN CERTIFICATE----- 308 | MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ 309 | BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy 310 | aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s 311 | IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp 312 | Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 313 | eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV 314 | BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp 315 | Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu 316 | Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g 317 | Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt 318 | IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU 319 | J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO 320 | JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY 321 | wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o 322 | koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN 323 | qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E 324 | Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe 325 | xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u 326 | 7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU 327 | sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI 328 | sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP 329 | cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q 330 | -----END CERTIFICATE----- 331 | 332 | ## VeriSign Class 2 Public Primary CA - G2 333 | -----BEGIN CERTIFICATE----- 334 | MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw 335 | CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns 336 | YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH 337 | MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y 338 | aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe 339 | Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX 340 | MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj 341 | IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx 342 | KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s 343 | eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B 344 | AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM 345 | HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw 346 | DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC 347 | AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji 348 | nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX 349 | rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn 350 | jBJ7xUS0rg== 351 | -----END CERTIFICATE----- 352 | 353 | ## VeriSign Class 1 Public Primary CA 354 | -----BEGIN CERTIFICATE----- 355 | MIICPTCCAaYCEQDNun9W8N/kvFT+IqyzcqpVMA0GCSqGSIb3DQEBAgUAMF8xCzAJ 356 | BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xh 357 | c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05 358 | NjAxMjkwMDAwMDBaFw0yODA4MDEyMzU5NTlaMF8xCzAJBgNVBAYTAlVTMRcwFQYD 359 | VQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJp 360 | bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOB 361 | jQAwgYkCgYEA5Rm/baNWYS2ZSHH2Z965jeu3noaACpEO+jglr0aIguVzqKCbJF0N 362 | H8xlbgyw0FaEGIeaBpsQoXPftFg5a27B9hXVqKg/qhIGjTGsf7A01480Z4gJzRQR 363 | 4k5FVmkfeAKA2txHkSm7NsljXMXg1y2He6G3MrB7MLoqLzGq7qNn2tsCAwEAATAN 364 | BgkqhkiG9w0BAQIFAAOBgQBMP7iLxmjf7kMzDl3ppssHhE16M/+SG/Q2rdiVIjZo 365 | EWx8QszznC7EBz8UsA9P/5CSdvnivErpj82ggAr3xSnxgiJduLHdgSOjeyUVRjB5 366 | FvjqBUuUfx3CHMjjt/QQQDwTw18fU+hI5Ia0e6E1sHslurjTjqs/OJ0ANACY89Fx 367 | lA== 368 | -----END CERTIFICATE----- 369 | 370 | ## VeriSign Class 1 Public Primary CA - G3 371 | -----BEGIN CERTIFICATE----- 372 | MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw 373 | CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl 374 | cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu 375 | LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT 376 | aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp 377 | dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD 378 | VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT 379 | aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ 380 | bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu 381 | IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg 382 | LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4 383 | nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO 384 | 8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV 385 | ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb 386 | PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2 387 | 6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr 388 | n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a 389 | qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4 390 | wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 391 | ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs 392 | pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4 393 | E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== 394 | -----END CERTIFICATE----- 395 | 396 | ## VeriSign Universal Root CA 397 | -----BEGIN CERTIFICATE----- 398 | MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB 399 | vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL 400 | ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp 401 | U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W 402 | ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe 403 | Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX 404 | MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 405 | IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y 406 | IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh 407 | bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF 408 | AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF 409 | 9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH 410 | H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H 411 | LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN 412 | /BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT 413 | rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud 414 | EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw 415 | WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs 416 | exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud 417 | DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 418 | sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ 419 | seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz 420 | 4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ 421 | BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR 422 | lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 423 | 7M2CYfE45k+XmCpajQ== 424 | -----END CERTIFICATE----- 425 | 426 | ## VeriSign Class 4 Public Primary CA - G3 427 | -----BEGIN CERTIFICATE----- 428 | MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw 429 | CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl 430 | cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu 431 | LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT 432 | aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp 433 | dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD 434 | VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT 435 | aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ 436 | bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu 437 | IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg 438 | LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 439 | GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ 440 | +mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd 441 | U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm 442 | NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY 443 | ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ 444 | ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 445 | CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq 446 | g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm 447 | fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c 448 | 2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ 449 | bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== 450 | -----END CERTIFICATE----- 451 | 452 | ## GeoTrust Global CA 453 | -----BEGIN CERTIFICATE----- 454 | MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT 455 | MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i 456 | YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG 457 | EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg 458 | R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 459 | 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq 460 | fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv 461 | iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU 462 | 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ 463 | bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW 464 | MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA 465 | ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l 466 | uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn 467 | Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS 468 | tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF 469 | PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un 470 | hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 471 | 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== 472 | -----END CERTIFICATE----- 473 | 474 | ## GeoTrust Primary Certification Authority 475 | -----BEGIN CERTIFICATE----- 476 | MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY 477 | MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo 478 | R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx 479 | MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK 480 | Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp 481 | ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 482 | AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 483 | AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA 484 | ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 485 | 7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W 486 | kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI 487 | mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G 488 | A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ 489 | KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 490 | 6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl 491 | 4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K 492 | oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj 493 | UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU 494 | AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= 495 | -----END CERTIFICATE----- 496 | 497 | ## GeoTrust Primary Certification Authority – G2 498 | -----BEGIN CERTIFICATE----- 499 | MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL 500 | MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj 501 | KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 502 | MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 503 | eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV 504 | BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw 505 | NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV 506 | BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH 507 | MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL 508 | So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal 509 | tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO 510 | BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG 511 | CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT 512 | qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz 513 | rD6ogRLQy7rQkgu2npaqBA+K 514 | -----END CERTIFICATE----- 515 | 516 | ## GeoTrust Primary Certification Authority – G3 517 | -----BEGIN CERTIFICATE----- 518 | MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB 519 | mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT 520 | MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s 521 | eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv 522 | cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ 523 | BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg 524 | MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 525 | BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg 526 | LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz 527 | +uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm 528 | hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn 529 | 5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W 530 | JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL 531 | DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC 532 | huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw 533 | HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB 534 | AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB 535 | zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN 536 | kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD 537 | AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH 538 | SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G 539 | spki4cErx5z481+oghLrGREt 540 | -----END CERTIFICATE----- 541 | 542 | ## GeoTrust Universal CA 543 | -----BEGIN CERTIFICATE----- 544 | MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW 545 | MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy 546 | c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE 547 | BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 548 | IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV 549 | VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 550 | cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT 551 | QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh 552 | F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v 553 | c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w 554 | mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd 555 | VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX 556 | teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ 557 | f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe 558 | Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ 559 | nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB 560 | /wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY 561 | MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG 562 | 9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc 563 | aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX 564 | IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn 565 | ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z 566 | uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN 567 | Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja 568 | QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW 569 | koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 570 | ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt 571 | DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm 572 | bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= 573 | -----END CERTIFICATE----- 574 | 575 | ## GeoTrust Global CA2 576 | -----BEGIN CERTIFICATE----- 577 | MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW 578 | MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs 579 | IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG 580 | EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg 581 | R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A 582 | PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 583 | Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL 584 | TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL 585 | 5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 586 | S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe 587 | 2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE 588 | FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap 589 | EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td 590 | EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv 591 | /NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN 592 | A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 593 | abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF 594 | I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz 595 | 4iIprn2DQKi6bA== 596 | -----END CERTIFICATE----- 597 | 598 | ## GeoTrust Universal CA2 599 | -----BEGIN CERTIFICATE----- 600 | MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW 601 | MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy 602 | c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD 603 | VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 604 | c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC 605 | AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 606 | WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG 607 | FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq 608 | XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL 609 | se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb 610 | KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd 611 | IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 612 | y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt 613 | hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc 614 | QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 615 | Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV 616 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV 617 | HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ 618 | KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z 619 | dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ 620 | L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr 621 | Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo 622 | ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY 623 | T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz 624 | GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m 625 | 1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV 626 | OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH 627 | 6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX 628 | QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS 629 | -----END CERTIFICATE----- 630 | -------------------------------------------------------------------------------- /coinbase/config.py: -------------------------------------------------------------------------------- 1 | __author__ = 'gsibble' 2 | 3 | COINBASE_ENDPOINT = 'https://coinbase.com/api/v1' 4 | 5 | COINBASE_AUTH_URI = 'https://www.coinbase.com/oauth/authorize' 6 | COINBASE_TOKEN_URI = 'https://www.coinbase.com/oauth/token' 7 | 8 | 9 | TEMP_CREDENTIALS = ''' 10 | {"_module": "oauth2client.client", "token_expiry": "2013-03-24T02:37:50Z", "access_token": "2a02d1fc82b1c42d4ea94d6866b5a232b53a3a50ad4ee899ead9afa6144c2ca3", "token_uri": "https://www.coinbase.com/oauth/token", "invalid": false, "token_response": {"access_token": "2a02d1fc82b1c42d4ea94d6866b5a232b53a3a50ad4ee899ead9afa6144c2ca3", "token_type": "bearer", "expires_in": 7200, "refresh_token": "ffec0153da773468c8cb418d07ced54c13ca8deceae813c9be0b90d25e7c3d71", "scope": "all"}, "client_id": "2df06cb383f4ffffac20e257244708c78a1150d128f37d420f11fdc069a914fc", "id_token": null, "client_secret": "7caedd79052d7e29aa0f2700980247e499ce85381e70e4a44de0c08f25bded8a", "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", "_class": "OAuth2Credentials", "refresh_token": "ffec0153da773468c8cb418d07ced54c13ca8deceae813c9be0b90d25e7c3d71", "user_agent": null}''' 11 | -------------------------------------------------------------------------------- /coinbase/errors.py: -------------------------------------------------------------------------------- 1 | class UrlValueError(ValueError): 2 | pass 3 | -------------------------------------------------------------------------------- /coinbase/mock.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import datetime 3 | from decimal import Decimal 4 | import random 5 | import string 6 | 7 | from coinbase.models import * 8 | 9 | 10 | class CoinbaseAccountMock(object): 11 | """ 12 | This class has the same attributes as CoinbaseAccount and mimics its 13 | behavior of Coinbase without actually using Coinbase. Use it to test 14 | an application without requiring real money. 15 | """ 16 | 17 | def __init__(self): 18 | 19 | self._buy_price = Decimal('510') 20 | self._sell_price = Decimal('490') 21 | 22 | self._me = CoinbaseContact(id='2346178248353', 23 | name='Me', email='me@example.com') 24 | 25 | self._transactions = {} # transaction id -> CoinbaseTransaction 26 | self._transaction_ids = [] # transaction ids in creation order 27 | self._transfers = {} # transaction id -> CoinbaseTransfer 28 | self._transfer_ids = [] # transaction ids in creation order 29 | self._buttons = {} # button id -> CoinbasePaymentButton 30 | self._orders = {} # order id -> CoinbaseOrder 31 | self._order_ids = [] # order ids in creation order 32 | self._orders_by_address = {} # receive address -> CoinbaseOrder.id 33 | self._orders_by_custom = {} # button custom string -> CoinbaseOrder.id 34 | 35 | self.authenticated = True 36 | self.auth = None 37 | self.allow_transfers = True 38 | 39 | self.balance = CoinbaseAmount('0', 'BTC') 40 | self.receive_address = random_bitcoin_address() 41 | self.exchange_rates = { 42 | 'usd_to_btc': Decimal('0.002'), 43 | 'btc_to_usd': Decimal('500'), 44 | } 45 | 46 | self.mock = MockControl(account=self) 47 | 48 | def get_exchange_rate(self, from_currency, to_currency): 49 | return self.exchange_rates['{}_to_{}'.format( 50 | from_currency.lower(), 51 | to_currency.lower() 52 | )] 53 | 54 | def contacts(self, page=None, limit=None, query=None): 55 | raise NotImplementedError # todo 56 | 57 | def buy_price(self, qty=1): 58 | return CoinbaseAmount(qty * self._buy_price, 'USD') 59 | 60 | def sell_price(self, qty=1): 61 | return CoinbaseAmount(qty * self._sell_price, 'USD') 62 | 63 | def buy_btc(self, qty, pricevaries=False): 64 | now = get_now() 65 | 66 | transaction = CoinbaseTransaction( 67 | id=random_transaction_id(), 68 | created_at=now, 69 | amount=CoinbaseAmount(qty, 'BTC'), 70 | status=CoinbaseTransaction.Status.pending, 71 | ) 72 | transfer = CoinbaseTransfer( 73 | transaction_id=transaction.id, 74 | created_at=now, 75 | ) 76 | 77 | self.mock.add_transaction(transaction) 78 | self.mock.add_transfer(transfer) 79 | 80 | return transfer 81 | 82 | def sell_btc(self, qty): 83 | return self.buy_btc(qty=-Decimal(qty)) 84 | 85 | def request(self, from_email, amount, notes=''): 86 | raise NotImplementedError # todo 87 | 88 | def send(self, to_address, amount, notes='', user_fee=None, idem=None): 89 | transaction = CoinbaseTransaction( 90 | id=random_transaction_id(), 91 | created_at=get_now(), 92 | notes=notes, 93 | amount=amount, 94 | status=CoinbaseTransaction.Status.pending, 95 | request=False, 96 | sender=self._me, 97 | recipient=None, # todo 98 | recipient_address=to_address, 99 | recipient_type='coinbase' if '@' in to_address else 'bitcoin', 100 | ) 101 | self.mock.add_transaction(transaction) 102 | return transaction 103 | 104 | def transactions(self, count=30): 105 | return [self._transactions[i] for i in 106 | list(reversed(self._transaction_ids))[:count]] 107 | 108 | def transfers(self, count=30): 109 | raise [self._transfers[i] for i in 110 | list(reversed(self._transfer_ids))[:count]] 111 | 112 | def get_transaction(self, transaction_id): 113 | return self._transactions[transaction_id] 114 | 115 | def get_user_details(self): 116 | # todo - this could be mocked better 117 | return CoinbaseUser(email='test@example.com') 118 | 119 | def generate_receive_address(self, callback_url=None): 120 | # todo - this could be mocked better 121 | return '1DzkRzSUqm8jGhT8wp7E8XNMcr9J3nT3SX' 122 | 123 | def create_button(self, button, account_id=None): 124 | id = random_button_id() 125 | button = button._replace( 126 | id=id, 127 | type=button.type or 'buy_now', 128 | style=button.style or 'buy_now_large', 129 | text=button.text or 'Pay With Bitcoin', 130 | custom_secure=bool(button.custom_secure), 131 | ) 132 | self._buttons[id] = button 133 | return button 134 | 135 | def orders(self, account_id=None, page=None): 136 | # todo - paging 137 | return [self._orders[i] for i in 138 | list(reversed(self._order_ids))] 139 | 140 | def get_order(self, id_or_custom_field, account_id=None): 141 | order = self._orders.get(id_or_custom_field) 142 | if order: 143 | return order 144 | order_id = self._orders_by_custom.get(id_or_custom_field) 145 | if order_id: 146 | return self._orders.get(order_id) 147 | 148 | def create_button_and_order(self, button): 149 | button_id = self.create_button(button).id 150 | return self.create_order_from_button(button_id) 151 | 152 | def create_order_from_button(self, button_id): 153 | button = self.mock.get_button(button_id) 154 | order = CoinbaseOrder( 155 | id=random_order_id(), 156 | created_at=get_now(), 157 | status=CoinbaseOrder.Status.pending, 158 | receive_address=random_bitcoin_address(), 159 | button=CoinbaseOrder.Button.from_coinbase_payment_button(button), 160 | custom=button.custom, 161 | total=self.mock.btc_and_native(button.price), 162 | ) 163 | self.mock.add_order(order) 164 | return order 165 | 166 | 167 | class MockControl(namedtuple('CoinbaseAccount_MockControl', 'account')): 168 | 169 | def complete_transaction(self, transaction_id): 170 | 171 | transaction = self.modify_transaction( 172 | transaction_id, status=CoinbaseTransaction.Status.complete) 173 | 174 | if transaction_id in self.account._transfers: 175 | self.modify_transfer(transaction_id, 176 | status=CoinbaseTransfer.Status.complete) 177 | 178 | send = (transaction.sender is not None and 179 | transaction.sender.id == self.account._me.id) 180 | 181 | amount_btc = self.convert_amount(transaction.amount, 'BTC').amount 182 | account_amount = self.account.balance.amount 183 | self.account.balance = self.account.balance._replace( 184 | amount=account_amount + amount_btc * (-1 if send else 1)) 185 | 186 | return transaction 187 | 188 | def create_order_from_button(self, button_id, customer=None, 189 | refund_address=None): 190 | """ 191 | customer - CoinbaseOrder.Customer 192 | refund_address - bitcoin addresss 193 | """ 194 | order_id = self.account.create_order_from_button(button_id).id 195 | return self.modify_order(order_id, customer=customer, 196 | refund_address=refund_address) 197 | 198 | def accept_payment(self, receive_address, amount_btc): 199 | """ 200 | receive_address - bitcoin address 201 | amount_btc - Decimal 202 | 203 | Returns a list of Callback 204 | """ 205 | 206 | callbacks = [] 207 | 208 | now = get_now() 209 | 210 | amount_btc = Decimal(amount_btc) 211 | amount_usd = amount_btc * self.account.exchange_rates['btc_to_usd'] 212 | amount = CoinbaseAmount.BtcAndNative( 213 | btc=CoinbaseAmount(amount_btc, 'BTC'), 214 | native=CoinbaseAmount(amount_usd, 'USD'), 215 | ) 216 | 217 | self.account.balance = self.account.balance._replace( 218 | amount=self.account.balance.amount + amount_btc) 219 | 220 | transaction = CoinbaseTransaction( 221 | id=random_transaction_id(), 222 | created_at=now, 223 | amount=amount.btc, 224 | status=CoinbaseTransaction.Status.complete, 225 | ) 226 | 227 | self.account.mock.add_transaction(transaction) 228 | 229 | order_id = self.account._orders_by_address.get(receive_address) 230 | if order_id is not None: 231 | 232 | order = self.account._orders[order_id] 233 | button = self.account._buttons[order.button.id] 234 | 235 | # I'm not actually sure when the transaction field gets updated. 236 | order = self.modify_order( 237 | order_id, 238 | transaction=CoinbaseOrder.Transaction( 239 | id=transaction.id, 240 | hash=None, 241 | confirmations=0, 242 | ) 243 | ) 244 | 245 | if order.status == CoinbaseOrder.Status.pending: 246 | amount_is_correct = amount.btc == order.total.btc 247 | status = (CoinbaseOrder.Status.complete if amount_is_correct 248 | else CoinbaseOrder.Status.mispaid) 249 | order = self.modify_order(order.id, status=status) 250 | 251 | if order.status in [CoinbaseOrder.Status.mispaid, 252 | CoinbaseOrder.Status.expired]: 253 | order = self.modify_order(order.id, mispaid=amount) 254 | 255 | if button.callback_url is not None: 256 | callbacks.append(Callback( 257 | url=button.callback_url, 258 | body=order.render_callback(), 259 | )) 260 | 261 | return callbacks 262 | 263 | def add_transaction(self, transaction): 264 | self.account._transactions[transaction.id] = transaction 265 | self.account._transaction_ids.append(transaction.id) 266 | 267 | def add_transfer(self, transfer): 268 | self.account._transfers[transfer.transaction_id] = transfer 269 | self.account._transfer_ids.append(transfer.transaction_id) 270 | 271 | def add_order(self, order): 272 | self.account._orders[order.id] = order 273 | self.account._orders_by_address[order.receive_address] = order.id 274 | if order.custom: 275 | self.account._orders_by_custom[order.custom] = order.id 276 | self.account._order_ids.append(order.id) 277 | 278 | def modify_transaction(self, transaction_id, **kwargs): 279 | transaction = self.account._transactions[transaction_id] 280 | transaction = transaction._replace(**kwargs) 281 | self.account._transactions[transaction_id] = transaction 282 | return transaction 283 | 284 | def modify_transfer(self, transaction_id, **kwargs): 285 | transfer = self.account._transfers[transaction_id] 286 | transfer = transfer._replace(**kwargs) 287 | self.account._transfers[transaction_id] = transfer 288 | return transfer 289 | 290 | def modify_order(self, order_id, **kwargs): 291 | order = self.account._orders[order_id] 292 | order = order._replace(**kwargs) 293 | self.account._orders[order_id] = order 294 | return order 295 | 296 | def get_button(self, button_id): 297 | return self.account._buttons[button_id] 298 | 299 | def convert_amount(self, amount, currency): 300 | if amount.currency == currency: 301 | return amount 302 | return amount.convert( 303 | currency=currency, 304 | exchange_rate=self.account.get_exchange_rate( 305 | from_currency=amount.currency, 306 | to_currency=currency 307 | ) 308 | ) 309 | 310 | def btc_and_native(self, amount, preferred_native_currency='USD'): 311 | native_currency = (amount.currency if amount.currency != 'BTC' 312 | else preferred_native_currency) 313 | return CoinbaseAmount.BtcAndNative( 314 | btc=self.convert_amount(amount, 'BTC'), 315 | native=self.convert_amount(amount, native_currency), 316 | ) 317 | 318 | 319 | Callback = namedtuple('Callback', 'url body') 320 | 321 | 322 | def get_now(): 323 | return floor_second(datetime.datetime.now()) 324 | 325 | 326 | def floor_second(x): 327 | return x - datetime.timedelta(microseconds=x.microsecond) 328 | 329 | 330 | def random_string(length, chars): 331 | return ''.join((random.choice(chars) for _ in range(length))) 332 | 333 | 334 | def random_transaction_id(): 335 | return random_string(24, string.hexdigits[:16]) 336 | 337 | 338 | def random_button_id(): 339 | return random_string(32, string.hexdigits[:16]) 340 | 341 | 342 | def random_order_id(): 343 | return random_string(8, string.digits + string.ascii_uppercase) 344 | 345 | 346 | def random_bitcoin_address(): 347 | return random_string(34, string.ascii_letters + string.digits) 348 | -------------------------------------------------------------------------------- /coinbase/models/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'gsibble' 2 | 3 | from .amount import CoinbaseAmount 4 | from .transaction import CoinbaseTransaction 5 | from .transfer import CoinbaseTransfer 6 | from .contact import CoinbaseContact 7 | from .user import CoinbaseUser 8 | from .error import CoinbaseError 9 | from .button import CoinbasePaymentButton 10 | from .order import CoinbaseOrder 11 | -------------------------------------------------------------------------------- /coinbase/models/amount.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from .util import namedtuple 4 | 5 | SATOSHIS_IN_A_BITCOIN = Decimal('100,000,000'.replace(',', '')) 6 | 7 | 8 | class CoinbaseAmount(namedtuple( 9 | 'CoinbaseAmount', 10 | 'amount currency' 11 | )): 12 | 13 | def __new__(cls, amount, currency): 14 | return super(CoinbaseAmount, cls).__new__( 15 | cls, 16 | Decimal(amount), 17 | currency, 18 | ) 19 | 20 | @classmethod 21 | def from_coinbase_dict(cls, x): 22 | if 'amount' in x: 23 | return CoinbaseAmount(x['amount'], x['currency']) 24 | elif 'cents' in x: 25 | currency = x['currency_iso'] 26 | amount = x['cents'] / (SATOSHIS_IN_A_BITCOIN if currency == 'BTC' 27 | else Decimal('100')) 28 | return CoinbaseAmount(amount, currency) 29 | else: 30 | raise Exception 31 | 32 | def to_coinbase_dict(self): 33 | return { 34 | 'amount': str(self.amount), 35 | 'currency': self.currency, 36 | } 37 | 38 | def convert(self, currency, exchange_rate): 39 | return CoinbaseAmount(self.amount * exchange_rate, currency) 40 | 41 | class BtcAndNative(namedtuple('CoinbaseAmount_BtcAndNative', 'btc native')): 42 | 43 | @classmethod 44 | def from_coinbase_dict(cls, x, prefix): 45 | 46 | btc_key = prefix + '_btc' 47 | native_key = prefix + '_native' 48 | 49 | if btc_key not in x: 50 | return None 51 | 52 | return CoinbaseAmount.BtcAndNative( 53 | btc=CoinbaseAmount.from_coinbase_dict(x[btc_key]), 54 | native=CoinbaseAmount.from_coinbase_dict(x[native_key]) 55 | ) 56 | -------------------------------------------------------------------------------- /coinbase/models/button.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from .util import namedtuple, optional 4 | 5 | from . import CoinbaseAmount 6 | 7 | 8 | class CoinbasePaymentButton(namedtuple( 9 | 'CoinbasePaymentButton', 10 | optional='id type repeat style text name description custom custom_secure ' 11 | 'callback_url success_url cancel_url info_url ' 12 | 'price auto_redirect suggested_prices include_address ' 13 | 'include_email variable_price' 14 | )): 15 | """ 16 | name 17 | The name of the item for which you are collecting bitcoin. 18 | For example, "Acme Order #123" or "Annual Pledge Drive" 19 | price 20 | Price as CoinbaseAmount 21 | type 22 | One of "buy_now", "donation", and "subscription". Default is 23 | "buy_now" 24 | repeat 25 | Required if `type` is subscription. Must be one of: never, 26 | daily, weekly, every_two_weeks, monthly, quarterly, yearly 27 | style 28 | One of: buy_now_large, buy_now_small, donation_large, 29 | donation_small, subscription_large, subscription_small, 30 | custom_large, custom_small, none. Default is buy_now_large 31 | text 32 | Allows you to customize the button text on custom_large and 33 | custom_small styles. Default is "Pay With Bitcoin" 34 | description 35 | Longer description of the item in case you want it added to the 36 | user's transaction notes 37 | custom 38 | An optional custom parameter. Usually an Order, User, or Product 39 | ID corresponding to a record in your database 40 | custom_secure 41 | Set this to true to prevent the custom parameter from being 42 | viewed or modified after the button has been created. Defaults 43 | to false 44 | callback_url 45 | A custom callback URL specific to this button 46 | success_url 47 | A custom success URL specific to this button. The user will 48 | be redirected to this URL after a successful payment 49 | cancel_url 50 | A custom cancel URL specific to this button. The user will be 51 | redirected to this URL after a canceled order 52 | info_url 53 | A custom info URL specific to this button. Displayed to the 54 | user after a successful purchase for sharing 55 | auto_redirect 56 | Auto-redirect users to success or cancel url after payment 57 | (cancel url if the user pays the wrong amount) 58 | variable_price 59 | Allow users to change the price on the generated button 60 | include_address 61 | Collect shipping address from customer (not for use with inline 62 | iframes) 63 | include_email 64 | Collect email address from customer (not for use with inline 65 | iframes) 66 | suggested_prices 67 | Some suggested prices to show (Decimal, at most 5) 68 | """ 69 | 70 | @classmethod 71 | def from_coinbase_dict(cls, x): 72 | 73 | kwargs = { 74 | 'id': x.get('code'), 75 | } 76 | 77 | for key in ['auto_redirect', 'callback_url', 'cancel_url', 'custom', 78 | 'custom_secure', 'description', 'info_url', 'name', 'style', 79 | 'success_url', 'text', 'type', 'repeat', 'variable_price', 80 | 'include_email', 'include_address']: 81 | kwargs[key] = x.get(key) 82 | 83 | if x.get('choose_price'): 84 | prices = [] 85 | for i in range(1, 6): 86 | s = x.get('price' + str(i)) 87 | if s is not None: 88 | prices.append(Decimal(s)) 89 | kwargs['suggested_prices'] = prices 90 | 91 | kwargs['price'] = optional(CoinbaseAmount.from_coinbase_dict)( 92 | x.get('price')) 93 | 94 | return CoinbasePaymentButton(**kwargs) 95 | 96 | def to_coinbase_dict(self): 97 | 98 | x = {} 99 | 100 | if self.id is not None: 101 | x['code'] = str(self.id) 102 | 103 | for key in ['type', 'style', 'text', 'name', 'description', 'custom', 104 | 'callback_url', 'success_url', 'cancel_url', 'info_url', 105 | 'repeat']: 106 | value = getattr(self, key) 107 | if value is not None: 108 | x[key] = str(value) 109 | 110 | for key in ['auto_redirect', 'include_email', 'include_address', 111 | 'variable_price', 'custom_secure']: 112 | value = getattr(self, key) 113 | if value is not None: 114 | x[key] = bool(value) 115 | 116 | if self.price is not None: 117 | x['price_string'] = str(self.price.amount) 118 | x['price_currency_iso'] = str(self.price.currency) 119 | 120 | if self.suggested_prices is not None: 121 | x['choose_price'] = True 122 | for i, price in zip(range(1, 6), self.suggested_prices): 123 | x['price{}'.format(i)] = str(price) 124 | 125 | return x 126 | -------------------------------------------------------------------------------- /coinbase/models/contact.py: -------------------------------------------------------------------------------- 1 | __author__ = 'gsibble' 2 | 3 | from .util import namedtuple 4 | 5 | 6 | class CoinbaseContact(namedtuple( 7 | 'CoinbaseContact', 8 | optional='id name email' 9 | )): 10 | @classmethod 11 | def from_coinbase_dict(cls, x): 12 | return CoinbaseContact( 13 | id=x.get('id'), 14 | name=x.get('name'), 15 | email=x.get('email'), 16 | ) 17 | -------------------------------------------------------------------------------- /coinbase/models/error.py: -------------------------------------------------------------------------------- 1 | __author__ = 'kroberts' 2 | 3 | 4 | class CoinbaseError(Exception): 5 | 6 | def __init__(self, message, errors=None): 7 | super(CoinbaseError, self).__init__(self, ' '.join([message] + (errors or []))) 8 | -------------------------------------------------------------------------------- /coinbase/models/order.py: -------------------------------------------------------------------------------- 1 | import dateutil.parser 2 | from enum import Enum 3 | import json 4 | 5 | from .util import namedtuple, optional 6 | from . import CoinbaseAmount 7 | 8 | 9 | class CoinbaseOrder(namedtuple( 10 | 'CoinbaseOrder', 11 | optional='id created_at status receive_address button ' 12 | 'transaction custom total mispaid customer refund_address' 13 | )): 14 | """ 15 | Orders are be created through the API, or by a user clicking a 16 | payment button. In the latter case, the order is only behind 17 | the scenes; it is hidden from the API until a payment is made. 18 | 19 | status 20 | CoinbaseOrder.Status 21 | total 22 | CoinbaseAmount.BtcAndNative. This is the order's price; in other 23 | words, the amount that Coinbase expects to receive for the order's 24 | status to become `complete`. 25 | mispaid 26 | CoinbaseAmount.BtcAndNative. This field is present if the order's 27 | status is `mispaid` or `expired`. Its value is the amount of the 28 | most recent payment made on this order. 29 | refund_address 30 | A refund address on off-blockchain order payments. 31 | "This is an experimental feature." 32 | button 33 | CoinbaseOrder.Button 34 | transaction 35 | CoinbaseOrder.Transaction 36 | customer 37 | CoinbaseOrder.Customer 38 | """ 39 | 40 | class Status(Enum): 41 | """ 42 | Enumeration of values for `CoinbaseOrder.status`. 43 | """ 44 | 45 | """ 46 | All orders have an initial status of `pending`. 47 | """ 48 | pending = 'new' 49 | 50 | """ 51 | When a `pending` order receives a payment in the correct amount, 52 | its status permanently becomes `complete`. 53 | """ 54 | complete = 'completed' 55 | 56 | """ 57 | When a `pending` order receives a payment in an incorrect amount, 58 | its status permanently becomes `mispaid`. 59 | """ 60 | mispaid = 'mispaid' 61 | 62 | """ 63 | When a `pending` order's time runs out, its status permanently 64 | becomes `expired`. 65 | """ 66 | expired = 'expired' 67 | 68 | @classmethod 69 | def parse_callback(cls, s): 70 | return CoinbaseOrder.from_coinbase_dict(json.loads(s)) 71 | 72 | def render_callback(self): 73 | return json.dumps(self.to_coinbase_dict()) 74 | 75 | @classmethod 76 | def from_coinbase_dict(cls, x): 77 | 78 | return CoinbaseOrder( 79 | id=x['order']['id'], 80 | created_at=dateutil.parser.parse( 81 | x['order']['created_at']), 82 | status=CoinbaseOrder.Status(x['order']['status']), 83 | receive_address=x['order']['receive_address'], 84 | button=CoinbaseOrder.Button.from_coinbase_dict( 85 | x['order']['button']), 86 | transaction=optional(CoinbaseOrder.Transaction.from_coinbase_dict)( 87 | x['order']['transaction']), 88 | custom=x['order'].get('custom'), 89 | total=CoinbaseAmount.BtcAndNative.from_coinbase_dict( 90 | x['order'], prefix='total'), 91 | mispaid=CoinbaseAmount.BtcAndNative.from_coinbase_dict( 92 | x['order'], prefix='mispaid'), 93 | customer=optional(CoinbaseOrder.Customer.from_coinbase_dict)( 94 | x.get('customer')), 95 | refund_address=x['order'].get('refund_address'), 96 | ) 97 | 98 | def to_coinbase_dict(self): 99 | x = { 100 | 'order': { 101 | 'id': self.id, 102 | 'created_at': self.created_at.strftime('%Y-%m-%dT%H:%M:%S%z'), 103 | 'status': self.status.value, 104 | 'total_btc': self.total.btc.to_coinbase_dict(), 105 | 'total_native': self.total.native.to_coinbase_dict(), 106 | 'custom': self.custom, 107 | 'receive_address': self.receive_address, 108 | 'button': self.button.to_coinbase_dict(), 109 | } 110 | } 111 | if self.transaction is not None: 112 | x['order']['transaction'] = self.transaction.to_coinbase_dict() 113 | if self.customer is not None: 114 | x['customer'] = self.customer.to_coinbase_dict() 115 | return x 116 | 117 | class Button(namedtuple( 118 | 'CoinbaseOrder_Button', 119 | 'id type', 120 | optional='name description' 121 | )): 122 | 123 | @classmethod 124 | def from_coinbase_dict(cls, x): 125 | kwargs = {} 126 | for key in ['id', 'type', 'name', 'description']: 127 | kwargs[key] = x[key] 128 | return CoinbaseOrder.Button(**kwargs) 129 | 130 | def to_coinbase_dict(self): 131 | x = {} 132 | for key in ['id', 'type', 'name', 'description']: 133 | x[key] = getattr(self, key) or '' 134 | return x 135 | 136 | @classmethod 137 | def from_coinbase_payment_button(cls, button): 138 | """ 139 | button - CoinbasePaymentButton 140 | """ 141 | kwargs = {} 142 | for key in ['id', 'type', 'name', 'description']: 143 | kwargs[key] = getattr(button, key) or '' 144 | return CoinbaseOrder.Button(**kwargs) 145 | 146 | class Transaction(namedtuple( 147 | 'CoinbaseOrder_Transaction', 148 | 'id hash confirmations' 149 | )): 150 | 151 | @classmethod 152 | def from_coinbase_dict(cls, x): 153 | kwargs = {} 154 | for key in ['id', 'hash', 'confirmations']: 155 | kwargs[key] = x[key] 156 | return CoinbaseOrder.Transaction(**kwargs) 157 | 158 | def to_coinbase_dict(self): 159 | x = {} 160 | for key in ['id', 'hash', 'confirmations']: 161 | x[key] = getattr(self, key) 162 | return x 163 | 164 | class Customer(namedtuple( 165 | 'CoinbaseOrder_Customer', 166 | optional='email shipping_address' 167 | )): 168 | 169 | @classmethod 170 | def from_coinbase_dict(cls, x): 171 | kwargs = {} 172 | for key in ['email', 'shipping_address']: 173 | kwargs[key] = x.get(key) 174 | return CoinbaseOrder.Customer(**kwargs) 175 | 176 | def to_coinbase_dict(self): 177 | x = {} 178 | for key in ['email', 'shipping_address']: 179 | value = getattr(self, key) 180 | if value is not None: 181 | x[key] = value 182 | return x 183 | -------------------------------------------------------------------------------- /coinbase/models/transaction.py: -------------------------------------------------------------------------------- 1 | import dateutil.parser 2 | from enum import Enum 3 | 4 | from .util import namedtuple 5 | 6 | from .amount import CoinbaseAmount 7 | from .contact import CoinbaseContact 8 | 9 | 10 | class CoinbaseTransaction(namedtuple( 11 | 'CoinbaseTransaction', 12 | optional='id created_at notes amount status request hash idem ' 13 | 'sender recipient recipient_address recipient_type' 14 | )): 15 | """ 16 | status = CoinbaseTransaction.Status 17 | request - bool 18 | idem - str 19 | sender - CoinbaseContact 20 | recipient - CoinbaseContact 21 | recipient_type - 'coinbase' or 'bitcoin' 22 | """ 23 | 24 | class Status(Enum): 25 | """ 26 | Enumeration of values for `CoinbaseTransaction.status`. 27 | """ 28 | 29 | pending = 'pending' 30 | 31 | complete = 'complete' 32 | 33 | @classmethod 34 | def from_coinbase_dict(cls, x): 35 | 36 | t = CoinbaseTransaction( 37 | id=x['id'], 38 | created_at=dateutil.parser.parse(x['created_at']), 39 | notes=x['notes'], 40 | amount=CoinbaseAmount.from_coinbase_dict(x['amount']), 41 | status=CoinbaseTransaction.Status(x['status']), 42 | request=x['request'], 43 | hash=x.get('hsh'), 44 | recipient_type=('coinbase' if ('recipient' in x) else 'bitcoin'), 45 | idem=(x.get('idem') or None), 46 | ) 47 | 48 | if 'sender' in x: 49 | t = t._replace(sender=CoinbaseContact.from_coinbase_dict( 50 | x['sender'])) 51 | 52 | if 'recipient' in x: 53 | t = t._replace( 54 | recipient=CoinbaseContact.from_coinbase_dict(x['recipient']), 55 | ) 56 | 57 | if 'recipient_address' in x: 58 | t = t._replace( 59 | recipient_address=x['recipient_address'], 60 | ) 61 | 62 | return t 63 | -------------------------------------------------------------------------------- /coinbase/models/transfer.py: -------------------------------------------------------------------------------- 1 | import dateutil.parser 2 | from enum import Enum 3 | 4 | from .util import namedtuple 5 | from .amount import CoinbaseAmount 6 | 7 | 8 | class CoinbaseTransfer(namedtuple( 9 | 'CoinbaseTransfer', 10 | optional='type code created_at fees_coinbase fees_bank ' 11 | 'payout_date transaction_id status btc_amount ' 12 | 'subtotal_amount total_amount description' 13 | )): 14 | """ 15 | status - CoinbaseTransfer.Status 16 | """ 17 | 18 | class Status(Enum): 19 | """ 20 | Enumeration of values for `CoinbaseTransfer.status`. 21 | """ 22 | 23 | pending = 'Pending' 24 | 25 | complete = 'Complete' 26 | 27 | canceled = 'Canceled' 28 | 29 | reversed = 'Reversed' 30 | 31 | @classmethod 32 | def from_coinbase_dict(cls, transfer): 33 | return CoinbaseTransfer( 34 | type=transfer['type'], 35 | code=transfer['code'], 36 | created_at=dateutil.parser.parse( 37 | transfer['created_at']), 38 | fees_coinbase=CoinbaseAmount.from_coinbase_dict( 39 | transfer['fees']['coinbase']), 40 | fees_bank=CoinbaseAmount.from_coinbase_dict( 41 | transfer['fees']['bank']), 42 | payout_date=dateutil.parser.parse( 43 | transfer['payout_date']), 44 | transaction_id=transfer.get('transaction_id', ''), 45 | status=CoinbaseTransfer.Status(transfer['status']), 46 | btc_amount=CoinbaseAmount.from_coinbase_dict( 47 | transfer['btc']), 48 | subtotal_amount=CoinbaseAmount.from_coinbase_dict( 49 | transfer['subtotal']), 50 | total_amount=CoinbaseAmount.from_coinbase_dict( 51 | transfer['total']), 52 | description=transfer.get('description', ''), 53 | ) 54 | -------------------------------------------------------------------------------- /coinbase/models/user.py: -------------------------------------------------------------------------------- 1 | __author__ = 'gsibble' 2 | 3 | from .util import namedtuple 4 | 5 | from .amount import CoinbaseAmount 6 | 7 | 8 | class CoinbaseUser(namedtuple( 9 | 'CoinbaseUser', 10 | optional='id name email time_zone native_currency balance ' 11 | 'buy_level sell_level buy_limit sell_limit' 12 | )): 13 | 14 | @classmethod 15 | def from_coinbase_dict(cls, user): 16 | return CoinbaseUser( 17 | id=user['id'], 18 | name=user['name'], 19 | email=user['email'], 20 | time_zone=user['time_zone'], 21 | native_currency=user['native_currency'], 22 | balance=CoinbaseAmount.from_coinbase_dict( 23 | user['balance']), 24 | buy_level=user['buy_level'], 25 | sell_level=user['sell_level'], 26 | buy_limit=CoinbaseAmount.from_coinbase_dict( 27 | user['buy_limit']), 28 | sell_limit=CoinbaseAmount.from_coinbase_dict( 29 | user['sell_limit']), 30 | ) 31 | -------------------------------------------------------------------------------- /coinbase/models/util.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | try: 4 | stringtype = basestring # python 2 5 | except: 6 | stringtype = str # python 3 7 | 8 | 9 | def coerce_to_list(x): 10 | if isinstance(x, stringtype): 11 | return x.replace(',', ' ').split() 12 | return x or [] 13 | 14 | 15 | def namedtuple(name, args=None, optional=None): 16 | args = coerce_to_list(args) 17 | optional = coerce_to_list(optional) 18 | x = collections.namedtuple(name, args + optional) 19 | if hasattr(x.__new__, 'func_defaults'): # python 2 20 | x.__new__.func_defaults = tuple([None] * len(optional)) 21 | elif hasattr(x.__new__, '__defaults__'): # python 3 22 | x.__new__.__defaults__ = tuple([None] * len(optional)) 23 | else: 24 | raise Exception('???') 25 | return x 26 | 27 | 28 | def optional(fn): 29 | def opt(x): 30 | if x is not None: 31 | return fn(x) 32 | return opt 33 | -------------------------------------------------------------------------------- /coinbase/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sibblegp/coinbase_python/1e0d5d162cb40c1094775ceb5c267a5bdedf0949/coinbase/tests/__init__.py -------------------------------------------------------------------------------- /coinbase/tests/account_setup.py: -------------------------------------------------------------------------------- 1 | from coinbase import CoinbaseAccount 2 | 3 | 4 | def without_auth(): 5 | return CoinbaseAccount() 6 | 7 | 8 | def with_key(): 9 | return CoinbaseAccount(api_key=api_key) 10 | 11 | 12 | def with_oauth(): 13 | # Don't actually set up oauth2 credentials, because this will fail if 14 | # we're testing under python3. Some day when we have an oauth2 client 15 | # that supports python 3, we can change this. 16 | a = CoinbaseAccount() 17 | a.authenticated = True 18 | a.auth_params = {} 19 | return a 20 | 21 | 22 | api_key = ('f64223978e5fd99d07cded069db2189a' 23 | '38c17142fee35625f6ab3635585f61ab') 24 | 25 | oauth_json = """ 26 | { 27 | "_class": "OAuth2Credentials", 28 | "_module": "oauth2client.client", 29 | "access_token": 30 | "c15a9f84e471db9b0b8fb94f3cb83f08867b4e00cb823f49ead771e928af5c79", 31 | "client_id": 32 | "2df06cb383f4ffffac20e257244708c78a1150d128f37d420f11fdc069a914fc", 33 | "client_secret": 34 | "7caedd79052d7e29aa0f2700980247e499ce85381e70e4a44de0c08f25bded8a", 35 | "id_token": null, 36 | "invalid": false, 37 | "refresh_token": 38 | "90cb2424ddc39f6668da41a7b46dfd5a729ac9030e19e05fd95bb1880ad07e65", 39 | "revoke_uri": "https://accounts.google.com/o/oauth2/revoke", 40 | "token_expiry": "2014-03-31T23:27:40Z", 41 | "token_response": { 42 | "access_token": 43 | "c15a9f84e471db9b0b8fb94f3cb83f08867b4e00cb823f49ead771e928af5c79", 44 | "expires_in": 7200, 45 | "refresh_token": 46 | "90cb2424ddc39f6668da41a7b46dfd5a729ac9030e19e05fd95bb1880ad07e65", 47 | "scope": "all", 48 | "token_type": "bearer" 49 | }, 50 | "token_uri": "https://www.coinbase.com/oauth/token", 51 | "user_agent": null 52 | } 53 | """ 54 | -------------------------------------------------------------------------------- /coinbase/tests/http_mocking.py: -------------------------------------------------------------------------------- 1 | import httpretty 2 | import json 3 | import unittest 4 | 5 | 6 | def setUp_method_with_http_mocking(test_class): 7 | 8 | original_setUp = test_class.setUp if hasattr(test_class, 'setUp') else None 9 | 10 | def new_setUp(self): 11 | httpretty.enable() 12 | self.addCleanup(httpretty.disable) 13 | if original_setUp: 14 | original_setUp(self) 15 | 16 | test_class.setUp = new_setUp 17 | 18 | return test_class 19 | 20 | 21 | def is_TstCase(x): 22 | try: 23 | return issubclass(x, unittest.TestCase) 24 | except TypeError: 25 | return False 26 | 27 | 28 | def with_http_mocking(x): 29 | 30 | if is_TstCase(x): 31 | return setUp_method_with_http_mocking(x) 32 | 33 | return httpretty.httprettified(x) 34 | 35 | 36 | def last_request_body(): 37 | return httpretty.last_request().body 38 | 39 | 40 | def last_request_json(): 41 | return json.loads(last_request_body().decode('UTF-8')) 42 | 43 | 44 | def last_request_params(): 45 | return httpretty.last_request().querystring 46 | 47 | 48 | def mock_http(header, response_body, content_type='text/json'): 49 | 50 | method, uri = header.split(' ', 1) 51 | httpretty.register_uri( 52 | method=method, 53 | uri=uri, 54 | body=response_body, 55 | content_type=content_type, 56 | ) 57 | -------------------------------------------------------------------------------- /coinbase/tests/test_amount.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from decimal import Decimal 3 | from coinbase.models import CoinbaseAmount 4 | 5 | 6 | def test_amount(): 7 | this(CoinbaseAmount('1.063', 'BTC').amount).should.equal(Decimal('1.063')) 8 | 9 | 10 | def test_currency(): 11 | this(CoinbaseAmount('1.063', 'BTC').currency).should.equal('BTC') 12 | 13 | 14 | def test_str_or_decimal(): 15 | this(CoinbaseAmount(Decimal('1.063'), 'BTC')) \ 16 | .should.equal(CoinbaseAmount('1.063', 'BTC')) 17 | -------------------------------------------------------------------------------- /coinbase/tests/test_balance.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from coinbase.models import CoinbaseAmount 5 | from coinbase.errors import UrlValueError 6 | 7 | from . import account_setup 8 | from .http_mocking import * 9 | 10 | 11 | @with_http_mocking 12 | class BalanceTest(TestCase): 13 | 14 | def setUp(self): 15 | mock_http('GET https://coinbase.com/api/v1/account/balance', 16 | response_body) 17 | 18 | def test_balance_with_key(self): 19 | account = account_setup.with_key() 20 | this(account.balance).should.equal(expected_balance) 21 | this(last_request_params()).should.equal({ 22 | 'api_key': [account_setup.api_key] 23 | }) 24 | 25 | def test_balance_with_oauth(self): 26 | account = account_setup.with_oauth() 27 | this(account.balance).should.equal(expected_balance) 28 | this(last_request_params()).should.equal({}) 29 | 30 | 31 | response_body = """ 32 | { 33 | "amount": "1.00000000", 34 | "currency": "BTC" 35 | } 36 | """ 37 | 38 | 39 | expected_balance = CoinbaseAmount('1.0', 'BTC') 40 | 41 | 42 | def test_url_injection_attempt(): 43 | account = account_setup.with_oauth() 44 | this(lambda: account.get_order('../account/balance')) \ 45 | .should.throw(UrlValueError) 46 | -------------------------------------------------------------------------------- /coinbase/tests/test_button_1.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from coinbase import CoinbaseAmount, CoinbasePaymentButton 5 | from . import account_setup 6 | from .http_mocking import * 7 | 8 | 9 | @with_http_mocking 10 | class ButtonTest1(TestCase): 11 | """ 12 | Create a button using all of the default values. 13 | """ 14 | 15 | def setUp(self): 16 | mock_http('POST https://coinbase.com/api/v1/buttons', 17 | response_body) 18 | 19 | def test_creating_a_button_with_key(self): 20 | account = account_setup.with_key() 21 | button = account.create_button(button_spec) 22 | this(last_request_json()).should.equal(expected_request_json) 23 | this(last_request_params()).should.equal({ 24 | 'api_key': [account_setup.api_key], 25 | }) 26 | this(button).should.equal(expected_button) 27 | 28 | def test_creating_a_button_with_oauth(self): 29 | account = account_setup.with_oauth() 30 | button = account.create_button(button_spec) 31 | this(last_request_json()).should.equal(expected_request_json) 32 | this(last_request_params()).should.equal({}) 33 | this(button).should.equal(expected_button) 34 | 35 | 36 | button_spec = CoinbasePaymentButton( 37 | name='a', 38 | price=CoinbaseAmount('5', 'USD'), 39 | ) 40 | 41 | 42 | expected_request_json = { 43 | 'button': { 44 | 'name': 'a', 45 | 'price_string': '5', 46 | 'price_currency_iso': 'USD', 47 | } 48 | } 49 | 50 | 51 | response_body = """ 52 | { 53 | "button": { 54 | "auto_redirect": false, 55 | "callback_url": null, 56 | "cancel_url": null, 57 | "choose_price": false, 58 | "code": "f68a5c68d0a68679a6c6f569e651d695", 59 | "custom": "", 60 | "description": "", 61 | "include_address": false, 62 | "include_email": false, 63 | "info_url": null, 64 | "name": "a", 65 | "price": { 66 | "cents": 500, 67 | "currency_iso": "USD" 68 | }, 69 | "style": "buy_now_large", 70 | "success_url": null, 71 | "text": "Pay With Bitcoin", 72 | "type": "buy_now", 73 | "variable_price": false 74 | }, 75 | "success": true 76 | } 77 | """ 78 | 79 | 80 | expected_button = CoinbasePaymentButton( 81 | id='f68a5c68d0a68679a6c6f569e651d695', 82 | name='a', 83 | price=CoinbaseAmount('5', 'USD'), 84 | auto_redirect=False, 85 | custom='', 86 | description='', 87 | include_address=False, 88 | include_email=False, 89 | style='buy_now_large', 90 | text='Pay With Bitcoin', 91 | type='buy_now', 92 | variable_price=False, 93 | ) 94 | -------------------------------------------------------------------------------- /coinbase/tests/test_button_2.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from coinbase import CoinbaseAmount, CoinbasePaymentButton 5 | from . import account_setup 6 | from .http_mocking import * 7 | 8 | 9 | @with_http_mocking 10 | class ButtonTest2(TestCase): 11 | """ 12 | Create a subscription button with suggested prices. 13 | """ 14 | 15 | def setUp(self): 16 | mock_http('POST https://coinbase.com/api/v1/buttons', 17 | response_body) 18 | 19 | def test_creating_a_button_with_key(self): 20 | account = account_setup.with_key() 21 | button = account.create_button(button_spec) 22 | this(last_request_json()).should.equal(expected_request_json) 23 | this(last_request_params()).should.equal({ 24 | 'api_key': [account_setup.api_key], 25 | }) 26 | this(button).should.equal(expected_button) 27 | 28 | def test_creating_a_button_with_oauth(self): 29 | 30 | account = account_setup.with_oauth() 31 | button = account.create_button(button_spec) 32 | this(last_request_json()).should.equal(expected_request_json) 33 | this(last_request_params()).should.equal({}) 34 | this(button).should.equal(expected_button) 35 | 36 | 37 | button_spec = CoinbasePaymentButton( 38 | name='abc def', 39 | description='ghi jkl', 40 | text='lol', 41 | custom='12345x', 42 | custom_secure=True, 43 | price=CoinbaseAmount('102.76', 'USD'), 44 | type='subscription', 45 | repeat='monthly', 46 | style='subscription_small', 47 | callback_url='https://example.com/callback', 48 | success_url='https://example.com/success', 49 | cancel_url='https://example.com/cancel', 50 | info_url='https://example.com/info', 51 | auto_redirect=True, 52 | suggested_prices=['5', '20.25', '250'], 53 | include_address=True, 54 | include_email=True, 55 | variable_price=True, 56 | ) 57 | 58 | 59 | expected_request_json = { 60 | 'button': { 61 | 'name': 'abc def', 62 | 'description': 'ghi jkl', 63 | 'text': 'lol', 64 | 'custom': '12345x', 65 | 'custom_secure': True, 66 | 'price_string': '102.76', 67 | 'price_currency_iso': 'USD', 68 | 'type': 'subscription', 69 | 'repeat': 'monthly', 70 | 'style': 'subscription_small', 71 | 'callback_url': 'https://example.com/callback', 72 | 'success_url': 'https://example.com/success', 73 | 'cancel_url': 'https://example.com/cancel', 74 | 'info_url': 'https://example.com/info', 75 | 'auto_redirect': True, 76 | 'choose_price': True, 77 | 'price1': '5', 78 | 'price2': '20.25', 79 | 'price3': '250', 80 | 'include_address': True, 81 | 'include_email': True, 82 | 'variable_price': True, 83 | } 84 | } 85 | 86 | 87 | response_body = """ 88 | { 89 | "button": { 90 | "auto_redirect": true, 91 | "callback_url": "https://example.com/callback", 92 | "cancel_url": "https://example.com/cancel", 93 | "choose_price": true, 94 | "code": "089e75679117f0a59524fa0c2c2aae59", 95 | "custom": "12345x", 96 | "description": "ghi jkl", 97 | "include_address": true, 98 | "include_email": true, 99 | "info_url": "https://example.com/info", 100 | "name": "abc def", 101 | "price": { 102 | "cents": 10276, 103 | "currency_iso": "USD" 104 | }, 105 | "style": "subscription_small", 106 | "success_url": "https://example.com/success", 107 | "text": "lol", 108 | "type": "subscription", 109 | "variable_price": true 110 | }, 111 | "success": true 112 | } 113 | """ 114 | 115 | 116 | expected_button = button_spec._replace( 117 | id='089e75679117f0a59524fa0c2c2aae59', 118 | # Coinbase's response doesn't include "price1", "price2", "price3", 119 | # "repeat", "custom_secure". It's unclear if that's intentional. 120 | suggested_prices=[], 121 | custom_secure=None, 122 | repeat=None, 123 | ) 124 | -------------------------------------------------------------------------------- /coinbase/tests/test_buy_price.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from coinbase import CoinbaseAmount 5 | from . import account_setup 6 | from .http_mocking import * 7 | 8 | 9 | @with_http_mocking 10 | class BuyPriceTest1(TestCase): 11 | 12 | def setUp(self): 13 | mock_http('GET https://coinbase.com/api/v1/prices/buy', 14 | self.response_body) 15 | 16 | def test_buy_price_without_auth(self): 17 | self.go(account_setup.without_auth()) 18 | 19 | def test_buy_price_with_key(self): 20 | self.go(account_setup.with_key()) 21 | 22 | def test_buy_price_with_oauth(self): 23 | self.go(account_setup.with_oauth()) 24 | 25 | def go(self, account): 26 | this(account.buy_price()).should.equal(self.expected_price) 27 | params = last_request_params() 28 | params.pop('api_key', None) 29 | this(params).should.equal({'qty': ['1']}) 30 | 31 | response_body = """ 32 | { 33 | "amount": "63.31", 34 | "currency": "USD" 35 | } 36 | """ 37 | 38 | expected_price = CoinbaseAmount('63.31', 'USD') 39 | 40 | 41 | @with_http_mocking 42 | class BuyPriceTest2(TestCase): 43 | 44 | def setUp(self): 45 | mock_http('GET https://coinbase.com/api/v1/prices/buy', 46 | self.response_body) 47 | 48 | def test_buy_price_without_auth(self): 49 | self.go(account_setup.without_auth()) 50 | 51 | def test_buy_price_with_key(self): 52 | self.go(account_setup.with_key()) 53 | 54 | def test_buy_price_with_oauth(self): 55 | self.go(account_setup.with_oauth()) 56 | 57 | def go(self, account): 58 | this(account.buy_price('10')).should.equal(self.expected_price) 59 | params = last_request_params() 60 | params.pop('api_key', None) 61 | this(params).should.equal({'qty': ['10']}) 62 | 63 | response_body = """ 64 | { 65 | "amount": "633.25", 66 | "currency": "USD" 67 | } 68 | """ 69 | 70 | expected_price = CoinbaseAmount('633.25', 'USD') 71 | -------------------------------------------------------------------------------- /coinbase/tests/test_contacts.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from coinbase import CoinbaseContact 5 | from . import account_setup 6 | from .http_mocking import * 7 | 8 | 9 | @with_http_mocking 10 | class ContactsTest(TestCase): 11 | 12 | def setUp(self): 13 | mock_http('GET https://coinbase.com/api/v1/contacts', 14 | response_body) 15 | 16 | def test_contacts_with_key(self): 17 | account = account_setup.with_key() 18 | this(account.contacts()).should.equal(expected_contacts) 19 | this(last_request_params()).should.equal({ 20 | 'api_key': [account_setup.api_key], 21 | }) 22 | 23 | def test_contacts_with_oauth(self): 24 | account = account_setup.with_oauth() 25 | this(account.contacts()).should.equal(expected_contacts) 26 | this(last_request_params()).should.equal({}) 27 | 28 | 29 | response_body = """ 30 | { 31 | "contacts": [ 32 | { 33 | "contact": { 34 | "email": "alice@example.com" 35 | } 36 | }, 37 | { 38 | "contact": { 39 | "email": "bob@example.com" 40 | } 41 | }, 42 | { 43 | "contact": { 44 | "email": "eve@example.com" 45 | } 46 | } 47 | ], 48 | "current_page": 1, 49 | "num_pages": 1, 50 | "total_count": 3 51 | } 52 | """ 53 | 54 | 55 | expected_contacts = [ 56 | CoinbaseContact(email='alice@example.com'), 57 | CoinbaseContact(email='bob@example.com'), 58 | CoinbaseContact(email='eve@example.com'), 59 | ] 60 | -------------------------------------------------------------------------------- /coinbase/tests/test_error.py: -------------------------------------------------------------------------------- 1 | from coinbase.models import CoinbaseError 2 | 3 | 4 | def test_coinbase_error_instantiation(): 5 | CoinbaseError("message") 6 | CoinbaseError("message", ["abc", "def"]) 7 | -------------------------------------------------------------------------------- /coinbase/tests/test_exchange_rate.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from decimal import Decimal 5 | 6 | from . import account_setup 7 | from .http_mocking import * 8 | 9 | 10 | @with_http_mocking 11 | class ExchangeRateTest(TestCase): 12 | 13 | def setUp(self): 14 | mock_http('GET https://coinbase.com/api/v1/currencies/exchange_rates', 15 | response_body) 16 | 17 | def test_exchange_rates_without_auth(self): 18 | self.go(account_setup.without_auth()) 19 | 20 | def test_exchange_rates_with_key(self): 21 | self.go(account_setup.with_key()) 22 | 23 | def test_exchange_rates_with_oauth(self): 24 | self.go(account_setup.with_oauth()) 25 | 26 | def go(self, account): 27 | rates = account.exchange_rates 28 | this(last_request_params()).should.equal({}) 29 | this(rates['gbp_to_usd']).should.be.equal(Decimal('1.648093')) 30 | this(rates['usd_to_btc']).should.be.equal(Decimal('0.002')) 31 | this(rates['btc_to_usd']).should.be.equal(Decimal('499.998')) 32 | this(rates['bdt_to_btc']).should.be.equal(Decimal('0.000026')) 33 | 34 | def test_get_exchange_rate(self): 35 | account = account_setup.without_auth() 36 | this(account.get_exchange_rate( 37 | from_currency='BTC', 38 | to_currency='USD' 39 | )).should.be.equal(Decimal('499.998')) 40 | 41 | 42 | response_body = """ 43 | { 44 | "aed_to_btc": "0.000545", 45 | "aed_to_usd": "0.272258", 46 | "afn_to_btc": "3.5e-05", 47 | "afn_to_usd": "0.017592", 48 | "all_to_btc": "2.0e-05", 49 | "all_to_usd": "0.009832", 50 | "amd_to_btc": "5.0e-06", 51 | "amd_to_usd": "0.002395", 52 | "ang_to_btc": "0.00112", 53 | "ang_to_usd": "0.559779", 54 | "aoa_to_btc": "2.0e-05", 55 | "aoa_to_usd": "0.010245", 56 | "ars_to_btc": "0.000251", 57 | "ars_to_usd": "0.125655", 58 | "aud_to_btc": "0.001814", 59 | "aud_to_usd": "0.907072", 60 | "awg_to_btc": "0.001117", 61 | "awg_to_usd": "0.558659", 62 | "azn_to_btc": "0.002551", 63 | "azn_to_usd": "1.275619", 64 | "bam_to_btc": "0.00141", 65 | "bam_to_usd": "0.704946", 66 | "bbd_to_btc": "0.0", 67 | "bbd_to_usd": "0.0", 68 | "bdt_to_btc": "2.6e-05", 69 | "bdt_to_usd": "0.012868", 70 | "bgn_to_btc": "0.001409", 71 | "bgn_to_usd": "0.704612", 72 | "bhd_to_btc": "0.005304", 73 | "bhd_to_usd": "2.652189", 74 | "bif_to_btc": "1.0e-06", 75 | "bif_to_usd": "0.000645", 76 | "bmd_to_btc": "0.002", 77 | "bmd_to_usd": "1.0", 78 | "bnd_to_btc": "0.00157", 79 | "bnd_to_usd": "0.784899", 80 | "bob_to_btc": "0.000289", 81 | "bob_to_usd": "0.144702", 82 | "brl_to_btc": "0.00086", 83 | "brl_to_usd": "0.430017", 84 | "bsd_to_btc": "0.002", 85 | "bsd_to_usd": "1.0", 86 | "btc_to_aed": "1836.482654", 87 | "btc_to_afn": "28422.686309", 88 | "btc_to_all": "50852.166591", 89 | "btc_to_amd": "208768.664422", 90 | "btc_to_ang": "893.206427", 91 | "btc_to_aoa": "48806.104775", 92 | "btc_to_ars": "3979.136083", 93 | "btc_to_aud": "551.221795", 94 | "btc_to_awg": "894.99642", 95 | "btc_to_azn": "391.964932", 96 | "btc_to_bam": "709.271663", 97 | "btc_to_bbd": "999.996", 98 | "btc_to_bdt": "38856.084575", 99 | "btc_to_bgn": "709.607162", 100 | "btc_to_bhd": "188.522746", 101 | "btc_to_bif": "775691.89722", 102 | "btc_to_bmd": "499.998", 103 | "btc_to_bnd": "637.022452", 104 | "btc_to_bob": "3455.361179", 105 | "btc_to_brl": "1162.740349", 106 | "btc_to_bsd": "499.998", 107 | "btc_to_btc": "0.999996", 108 | "btc_to_btn": "30489.228043", 109 | "btc_to_bwp": "4461.668653", 110 | "btc_to_byr": "4926730.293", 111 | "btc_to_bzd": "992.49603", 112 | "btc_to_cad": "561.475754", 113 | "btc_to_cdf": "460671.490807", 114 | "btc_to_chf": "441.508234", 115 | "btc_to_clp": "280911.877348", 116 | "btc_to_cny": "3105.265579", 117 | "btc_to_cop": "997247.677493", 118 | "btc_to_crc": "269637.441446", 119 | "btc_to_cup": "500.1045", 120 | "btc_to_cve": "39726.711093", 121 | "btc_to_czk": "9943.845224", 122 | "btc_to_djf": "89021.643912", 123 | "btc_to_dkk": "2705.332179", 124 | "btc_to_dop": "21548.533806", 125 | "btc_to_dzd": "39187.827748", 126 | "btc_to_eek": "5817.52673", 127 | "btc_to_egp": "3479.837581", 128 | "btc_to_ern": "7501.382494", 129 | "btc_to_etb": "9605.451578", 130 | "btc_to_eur": "362.42405", 131 | "btc_to_fjd": "930.605278", 132 | "btc_to_fkp": "303.379786", 133 | "btc_to_gbp": "303.379786", 134 | "btc_to_gel": "876.376494", 135 | "btc_to_ghs": "1306.314775", 136 | "btc_to_gip": "303.379786", 137 | "btc_to_gmd": "19049.9238", 138 | "btc_to_gnf": "3447444.543667", 139 | "btc_to_gtq": "3862.44955", 140 | "btc_to_gyd": "101987.09155", 141 | "btc_to_hkd": "3879.580482", 142 | "btc_to_hnl": "9620.121519", 143 | "btc_to_hrk": "2777.51889", 144 | "btc_to_htg": "21825.822696", 145 | "btc_to_huf": "113512.745447", 146 | "btc_to_idr": "5702777.1888", 147 | "btc_to_ils": "1739.172043", 148 | "btc_to_inr": "30474.8081", 149 | "btc_to_iqd": "581882.67246", 150 | "btc_to_irr": "12503616.651833", 151 | "btc_to_isk": "56876.772492", 152 | "btc_to_jmd": "54665.281338", 153 | "btc_to_jod": "353.415586", 154 | "btc_to_jpy": "51235.944555", 155 | "btc_to_kes": "43284.196863", 156 | "btc_to_kgs": "26892.992428", 157 | "btc_to_khr": "2001122.828977", 158 | "btc_to_kmf": "178432.387268", 159 | "btc_to_kpw": "449998.2", 160 | "btc_to_krw": "540394.513413", 161 | "btc_to_kwd": "140.829437", 162 | "btc_to_kyd": "413.296347", 163 | "btc_to_kzt": "91148.135406", 164 | "btc_to_lak": "4018659.758797", 165 | "btc_to_lbp": "753518.652413", 166 | "btc_to_lkr": "65313.238746", 167 | "btc_to_lrd": "39999.989999", 168 | "btc_to_lsl": "5434.773261", 169 | "btc_to_ltl": "1251.334995", 170 | "btc_to_lvl": "254.633981", 171 | "btc_to_lyd": "622.258011", 172 | "btc_to_mad": "4070.233719", 173 | "btc_to_mdl": "6717.933128", 174 | "btc_to_mga": "1177245.291", 175 | "btc_to_mkd": "22347.45061", 176 | "btc_to_mmk": "482130.061472", 177 | "btc_to_mnt": "881663.139833", 178 | "btc_to_mop": "3996.752513", 179 | "btc_to_mro": "146110.665555", 180 | "btc_to_mur": "15072.41971", 181 | "btc_to_mvr": "7716.919132", 182 | "btc_to_mwk": "206164.674838", 183 | "btc_to_mxn": "6615.508538", 184 | "btc_to_myr": "1652.748389", 185 | "btc_to_mzn": "15823.270207", 186 | "btc_to_nad": "5437.673249", 187 | "btc_to_ngn": "82525.720396", 188 | "btc_to_nio": "12832.918668", 189 | "btc_to_nok": "3027.074892", 190 | "btc_to_npr": "48899.004403", 191 | "btc_to_nzd": "586.265155", 192 | "btc_to_omr": "192.51823", 193 | "btc_to_pab": "499.998", 194 | "btc_to_pen": "1406.234375", 195 | "btc_to_pgk": "1257.856469", 196 | "btc_to_php": "22603.559585", 197 | "btc_to_pkr": "49015.603937", 198 | "btc_to_pln": "1521.316415", 199 | "btc_to_pyg": "2215191.969197", 200 | "btc_to_qar": "1820.732717", 201 | "btc_to_ron": "1625.253499", 202 | "btc_to_rsd": "41909.397362", 203 | "btc_to_rub": "18149.087403", 204 | "btc_to_rwf": "339557.941763", 205 | "btc_to_sar": "1875.314999", 206 | "btc_to_sbd": "3637.183451", 207 | "btc_to_scr": "6078.005688", 208 | "btc_to_sdg": "2842.755129", 209 | "btc_to_sek": "3209.326163", 210 | "btc_to_sgd": "636.429954", 211 | "btc_to_shp": "303.379786", 212 | "btc_to_sll": "2162949.681667", 213 | "btc_to_sos": "502396.565406", 214 | "btc_to_srd": "1641.659933", 215 | "btc_to_std": "8895714.417", 216 | "btc_to_svc": "4374.612501", 217 | "btc_to_syp": "71706.300174", 218 | "btc_to_szl": "5441.033236", 219 | "btc_to_thb": "16208.235167", 220 | "btc_to_tjs": "2400.277899", 221 | "btc_to_tmm": "0.0", 222 | "btc_to_tnd": "790.936836", 223 | "btc_to_top": "928.581786", 224 | "btc_to_try": "1118.457026", 225 | "btc_to_ttd": "3199.9022", 226 | "btc_to_twd": "15300.198799", 227 | "btc_to_tzs": "818663.391833", 228 | "btc_to_uah": "5270.178919", 229 | "btc_to_ugx": "1278936.550733", 230 | "btc_to_usd": "499.998", 231 | "btc_to_uyu": "11420.404318", 232 | "btc_to_uzs": "1123215.49262", 233 | "btc_to_vef": "3145.08742", 234 | "btc_to_vnd": "10545424.484633", 235 | "btc_to_vuv": "47776.058895", 236 | "btc_to_wst": "1162.334851", 237 | "btc_to_xaf": "237802.673286", 238 | "btc_to_xcd": "1350.774597", 239 | "btc_to_xof": "237965.359135", 240 | "btc_to_xpf": "43242.741528", 241 | "btc_to_yer": "107488.570544", 242 | "btc_to_zar": "5448.698205", 243 | "btc_to_zmk": "2626527.121349", 244 | "btc_to_zwl": "161176.85829", 245 | "btn_to_btc": "3.3e-05", 246 | "btn_to_usd": "0.016399", 247 | "bwp_to_btc": "0.000224", 248 | "bwp_to_usd": "0.112065", 249 | "byr_to_btc": "0.0", 250 | "byr_to_usd": "0.000101", 251 | "bzd_to_btc": "0.001008", 252 | "bzd_to_usd": "0.503778", 253 | "cad_to_btc": "0.001781", 254 | "cad_to_usd": "0.890507", 255 | "cdf_to_btc": "2.0e-06", 256 | "cdf_to_usd": "0.001085", 257 | "chf_to_btc": "0.002265", 258 | "chf_to_usd": "1.132477", 259 | "clp_to_btc": "4.0e-06", 260 | "clp_to_usd": "0.00178", 261 | "cny_to_btc": "0.000322", 262 | "cny_to_usd": "0.161016", 263 | "cop_to_btc": "1.0e-06", 264 | "cop_to_usd": "0.000501", 265 | "crc_to_btc": "4.0e-06", 266 | "crc_to_usd": "0.001854", 267 | "cup_to_btc": "0.002", 268 | "cup_to_usd": "0.999787", 269 | "cve_to_btc": "2.5e-05", 270 | "cve_to_usd": "0.012586", 271 | "czk_to_btc": "0.000101", 272 | "czk_to_usd": "0.050282", 273 | "djf_to_btc": "1.1e-05", 274 | "djf_to_usd": "0.005617", 275 | "dkk_to_btc": "0.00037", 276 | "dkk_to_usd": "0.184819", 277 | "dop_to_btc": "4.6e-05", 278 | "dop_to_usd": "0.023203", 279 | "dzd_to_btc": "2.6e-05", 280 | "dzd_to_usd": "0.012759", 281 | "eek_to_btc": "0.000172", 282 | "eek_to_usd": "0.085947", 283 | "egp_to_btc": "0.000287", 284 | "egp_to_usd": "0.143684", 285 | "ern_to_btc": "0.000133", 286 | "ern_to_usd": "0.066654", 287 | "etb_to_btc": "0.000104", 288 | "etb_to_usd": "0.052054", 289 | "eur_to_btc": "0.002759", 290 | "eur_to_usd": "1.379594", 291 | "fjd_to_btc": "0.001075", 292 | "fjd_to_usd": "0.537283", 293 | "fkp_to_btc": "0.003296", 294 | "fkp_to_usd": "1.648093", 295 | "gbp_to_btc": "0.003296", 296 | "gbp_to_usd": "1.648093", 297 | "gel_to_btc": "0.001141", 298 | "gel_to_usd": "0.570529", 299 | "ghs_to_btc": "0.000766", 300 | "ghs_to_usd": "0.382755", 301 | "gip_to_btc": "0.003296", 302 | "gip_to_usd": "1.648093", 303 | "gmd_to_btc": "5.2e-05", 304 | "gmd_to_usd": "0.026247", 305 | "gnf_to_btc": "0.0", 306 | "gnf_to_usd": "0.000145", 307 | "gtq_to_btc": "0.000259", 308 | "gtq_to_usd": "0.129451", 309 | "gyd_to_btc": "1.0e-05", 310 | "gyd_to_usd": "0.004903", 311 | "hkd_to_btc": "0.000258", 312 | "hkd_to_usd": "0.128879", 313 | "hnl_to_btc": "0.000104", 314 | "hnl_to_usd": "0.051974", 315 | "hrk_to_btc": "0.00036", 316 | "hrk_to_usd": "0.180016", 317 | "htg_to_btc": "4.6e-05", 318 | "htg_to_usd": "0.022909", 319 | "huf_to_btc": "9.0e-06", 320 | "huf_to_usd": "0.004405", 321 | "idr_to_btc": "0.0", 322 | "idr_to_usd": "8.8e-05", 323 | "ils_to_btc": "0.000575", 324 | "ils_to_usd": "0.287492", 325 | "inr_to_btc": "3.3e-05", 326 | "inr_to_usd": "0.016407", 327 | "iqd_to_btc": "2.0e-06", 328 | "iqd_to_usd": "0.000859", 329 | "irr_to_btc": "0.0", 330 | "irr_to_usd": "4.0e-05", 331 | "isk_to_btc": "1.8e-05", 332 | "isk_to_usd": "0.008791", 333 | "jmd_to_btc": "1.8e-05", 334 | "jmd_to_usd": "0.009147", 335 | "jod_to_btc": "0.00283", 336 | "jod_to_usd": "1.414759", 337 | "jpy_to_btc": "2.0e-05", 338 | "jpy_to_usd": "0.009759", 339 | "kes_to_btc": "2.3e-05", 340 | "kes_to_usd": "0.011552", 341 | "kgs_to_btc": "3.7e-05", 342 | "kgs_to_usd": "0.018592", 343 | "khr_to_btc": "1.0e-06", 344 | "khr_to_usd": "0.00025", 345 | "kmf_to_btc": "6.0e-06", 346 | "kmf_to_usd": "0.002802", 347 | "kpw_to_btc": "0.0", 348 | "kpw_to_usd": "0.0", 349 | "krw_to_btc": "2.0e-06", 350 | "krw_to_usd": "0.000925", 351 | "kwd_to_btc": "0.007101", 352 | "kwd_to_usd": "3.55038", 353 | "kyd_to_btc": "0.00242", 354 | "kyd_to_usd": "1.209781", 355 | "kzt_to_btc": "1.1e-05", 356 | "kzt_to_usd": "0.005486", 357 | "lak_to_btc": "0.0", 358 | "lak_to_usd": "0.000124", 359 | "lbp_to_btc": "1.0e-06", 360 | "lbp_to_usd": "0.000664", 361 | "lkr_to_btc": "1.5e-05", 362 | "lkr_to_usd": "0.007655", 363 | "lrd_to_btc": "2.5e-05", 364 | "lrd_to_usd": "0.0125", 365 | "lsl_to_btc": "0.000184", 366 | "lsl_to_usd": "0.092", 367 | "ltl_to_btc": "0.000799", 368 | "ltl_to_usd": "0.399572", 369 | "lvl_to_btc": "0.003927", 370 | "lvl_to_usd": "1.963595", 371 | "lyd_to_btc": "0.001607", 372 | "lyd_to_usd": "0.803522", 373 | "mad_to_btc": "0.000246", 374 | "mad_to_usd": "0.122843", 375 | "mdl_to_btc": "0.000149", 376 | "mdl_to_usd": "0.074427", 377 | "mga_to_btc": "1.0e-06", 378 | "mga_to_usd": "0.000425", 379 | "mkd_to_btc": "4.5e-05", 380 | "mkd_to_usd": "0.022374", 381 | "mmk_to_btc": "2.0e-06", 382 | "mmk_to_usd": "0.001037", 383 | "mnt_to_btc": "1.0e-06", 384 | "mnt_to_usd": "0.000567", 385 | "mop_to_btc": "0.00025", 386 | "mop_to_usd": "0.125101", 387 | "mro_to_btc": "7.0e-06", 388 | "mro_to_usd": "0.003422", 389 | "mur_to_btc": "6.6e-05", 390 | "mur_to_usd": "0.033173", 391 | "mvr_to_btc": "0.00013", 392 | "mvr_to_usd": "0.064792", 393 | "mwk_to_btc": "5.0e-06", 394 | "mwk_to_usd": "0.002425", 395 | "mxn_to_btc": "0.000151", 396 | "mxn_to_usd": "0.07558", 397 | "myr_to_btc": "0.000605", 398 | "myr_to_usd": "0.302525", 399 | "mzn_to_btc": "6.3e-05", 400 | "mzn_to_usd": "0.031599", 401 | "nad_to_btc": "0.000184", 402 | "nad_to_usd": "0.091951", 403 | "ngn_to_btc": "1.2e-05", 404 | "ngn_to_usd": "0.006059", 405 | "nio_to_btc": "7.8e-05", 406 | "nio_to_usd": "0.038962", 407 | "nok_to_btc": "0.00033", 408 | "nok_to_usd": "0.165175", 409 | "npr_to_btc": "2.0e-05", 410 | "npr_to_usd": "0.010225", 411 | "nzd_to_btc": "0.001706", 412 | "nzd_to_usd": "0.852853", 413 | "omr_to_btc": "0.005194", 414 | "omr_to_usd": "2.597146", 415 | "pab_to_btc": "0.002", 416 | "pab_to_usd": "1.0", 417 | "pen_to_btc": "0.000711", 418 | "pen_to_usd": "0.355558", 419 | "pgk_to_btc": "0.000795", 420 | "pgk_to_usd": "0.3975", 421 | "php_to_btc": "4.4e-05", 422 | "php_to_usd": "0.02212", 423 | "pkr_to_btc": "2.0e-05", 424 | "pkr_to_usd": "0.010201", 425 | "pln_to_btc": "0.000657", 426 | "pln_to_usd": "0.328661", 427 | "pyg_to_btc": "0.0", 428 | "pyg_to_usd": "0.000226", 429 | "qar_to_btc": "0.000549", 430 | "qar_to_usd": "0.274614", 431 | "ron_to_btc": "0.000615", 432 | "ron_to_usd": "0.307643", 433 | "rsd_to_btc": "2.4e-05", 434 | "rsd_to_usd": "0.01193", 435 | "rub_to_btc": "5.5e-05", 436 | "rub_to_usd": "0.027549", 437 | "rwf_to_btc": "3.0e-06", 438 | "rwf_to_usd": "0.001472", 439 | "sar_to_btc": "0.000533", 440 | "sar_to_usd": "0.266621", 441 | "sbd_to_btc": "0.000275", 442 | "sbd_to_usd": "0.137468", 443 | "scr_to_btc": "0.000165", 444 | "scr_to_usd": "0.082263", 445 | "sdg_to_btc": "0.000352", 446 | "sdg_to_usd": "0.175885", 447 | "sek_to_btc": "0.000312", 448 | "sek_to_usd": "0.155795", 449 | "sgd_to_btc": "0.001571", 450 | "sgd_to_usd": "0.785629", 451 | "shp_to_btc": "0.003296", 452 | "shp_to_usd": "1.648093", 453 | "sll_to_btc": "0.0", 454 | "sll_to_usd": "0.000231", 455 | "sos_to_btc": "2.0e-06", 456 | "sos_to_usd": "0.000995", 457 | "srd_to_btc": "0.000609", 458 | "srd_to_usd": "0.304569", 459 | "std_to_btc": "0.0", 460 | "std_to_usd": "5.6e-05", 461 | "svc_to_btc": "0.000229", 462 | "svc_to_usd": "0.114295", 463 | "syp_to_btc": "1.4e-05", 464 | "syp_to_usd": "0.006973", 465 | "szl_to_btc": "0.000184", 466 | "szl_to_usd": "0.091894", 467 | "thb_to_btc": "6.2e-05", 468 | "thb_to_usd": "0.030848", 469 | "tjs_to_btc": "0.000417", 470 | "tjs_to_usd": "0.208308", 471 | "tmm_to_btc": "0.0", 472 | "tmm_to_usd": "0.350865", 473 | "tnd_to_btc": "0.001264", 474 | "tnd_to_usd": "0.632159", 475 | "top_to_btc": "0.001077", 476 | "top_to_usd": "0.538453", 477 | "try_to_btc": "0.000894", 478 | "try_to_usd": "0.447043", 479 | "ttd_to_btc": "0.000313", 480 | "ttd_to_usd": "0.156254", 481 | "twd_to_btc": "6.5e-05", 482 | "twd_to_usd": "0.032679", 483 | "tzs_to_btc": "1.0e-06", 484 | "tzs_to_usd": "0.000611", 485 | "uah_to_btc": "0.00019", 486 | "uah_to_usd": "0.094873", 487 | "ugx_to_btc": "1.0e-06", 488 | "ugx_to_usd": "0.000391", 489 | "usd_to_aed": "3.67298", 490 | "usd_to_afn": "56.8456", 491 | "usd_to_all": "101.70474", 492 | "usd_to_amd": "417.538999", 493 | "usd_to_ang": "1.78642", 494 | "usd_to_aoa": "97.6126", 495 | "usd_to_ars": "7.958304", 496 | "usd_to_aud": "1.102448", 497 | "usd_to_awg": "1.79", 498 | "usd_to_azn": "0.783933", 499 | "usd_to_bam": "1.418549", 500 | "usd_to_bbd": "2", 501 | "usd_to_bdt": "77.71248", 502 | "usd_to_bgn": "1.41922", 503 | "usd_to_bhd": "0.377047", 504 | "usd_to_bif": "1551.39", 505 | "usd_to_bmd": "1", 506 | "usd_to_bnd": "1.27405", 507 | "usd_to_bob": "6.91075", 508 | "usd_to_brl": "2.32549", 509 | "usd_to_bsd": "1", 510 | "usd_to_btc": "0.002", 511 | "usd_to_btn": "60.9787", 512 | "usd_to_bwp": "8.923373", 513 | "usd_to_byr": "9853.5", 514 | "usd_to_bzd": "1.985", 515 | "usd_to_cad": "1.122956", 516 | "usd_to_cdf": "921.346667", 517 | "usd_to_chf": "0.88302", 518 | "usd_to_clp": "561.826002", 519 | "usd_to_cny": "6.210556", 520 | "usd_to_cop": "1994.503333", 521 | "usd_to_crc": "539.27704", 522 | "usd_to_cup": "1.000213", 523 | "usd_to_cve": "79.45374", 524 | "usd_to_czk": "19.88777", 525 | "usd_to_djf": "178.044", 526 | "usd_to_dkk": "5.410686", 527 | "usd_to_dop": "43.09724", 528 | "usd_to_dzd": "78.375969", 529 | "usd_to_eek": "11.6351", 530 | "usd_to_egp": "6.959703", 531 | "usd_to_ern": "15.002825", 532 | "usd_to_etb": "19.21098", 533 | "usd_to_eur": "0.724851", 534 | "usd_to_fjd": "1.861218", 535 | "usd_to_fkp": "0.606762", 536 | "usd_to_gbp": "0.606762", 537 | "usd_to_gel": "1.75276", 538 | "usd_to_ghs": "2.61264", 539 | "usd_to_gip": "0.606762", 540 | "usd_to_gmd": "38.1", 541 | "usd_to_gnf": "6894.916667", 542 | "usd_to_gtq": "7.72493", 543 | "usd_to_gyd": "203.974999", 544 | "usd_to_hkd": "7.759192", 545 | "usd_to_hnl": "19.24032", 546 | "usd_to_hrk": "5.55506", 547 | "usd_to_htg": "43.65182", 548 | "usd_to_huf": "227.026399", 549 | "usd_to_idr": "11405.6", 550 | "usd_to_ils": "3.478358", 551 | "usd_to_inr": "60.94986", 552 | "usd_to_iqd": "1163.77", 553 | "usd_to_irr": "25007.333333", 554 | "usd_to_isk": "113.754", 555 | "usd_to_jmd": "109.331", 556 | "usd_to_jod": "0.706834", 557 | "usd_to_jpy": "102.472299", 558 | "usd_to_kes": "86.56874", 559 | "usd_to_kgs": "53.7862", 560 | "usd_to_khr": "4002.261667", 561 | "usd_to_kmf": "356.866202", 562 | "usd_to_kpw": "900", 563 | "usd_to_krw": "1080.79335", 564 | "usd_to_kwd": "0.28166", 565 | "usd_to_kyd": "0.826596", 566 | "usd_to_kzt": "182.297", 567 | "usd_to_lak": "8037.351667", 568 | "usd_to_lbp": "1507.043333", 569 | "usd_to_lkr": "130.627", 570 | "usd_to_lrd": "80.0003", 571 | "usd_to_lsl": "10.86959", 572 | "usd_to_ltl": "2.50268", 573 | "usd_to_lvl": "0.50927", 574 | "usd_to_lyd": "1.244521", 575 | "usd_to_mad": "8.1405", 576 | "usd_to_mdl": "13.43592", 577 | "usd_to_mga": "2354.5", 578 | "usd_to_mkd": "44.69508", 579 | "usd_to_mmk": "964.26398", 580 | "usd_to_mnt": "1763.333333", 581 | "usd_to_mop": "7.993537", 582 | "usd_to_mro": "292.2225", 583 | "usd_to_mur": "30.14496", 584 | "usd_to_mvr": "15.4339", 585 | "usd_to_mwk": "412.330999", 586 | "usd_to_mxn": "13.23107", 587 | "usd_to_myr": "3.30551", 588 | "usd_to_mzn": "31.646667", 589 | "usd_to_nad": "10.87539", 590 | "usd_to_ngn": "165.052101", 591 | "usd_to_nio": "25.66594", 592 | "usd_to_nok": "6.054174", 593 | "usd_to_npr": "97.7984", 594 | "usd_to_nzd": "1.172535", 595 | "usd_to_omr": "0.385038", 596 | "usd_to_pab": "1", 597 | "usd_to_pen": "2.81248", 598 | "usd_to_pgk": "2.515723", 599 | "usd_to_php": "45.2073", 600 | "usd_to_pkr": "98.0316", 601 | "usd_to_pln": "3.042645", 602 | "usd_to_pyg": "4430.40166", 603 | "usd_to_qar": "3.64148", 604 | "usd_to_ron": "3.25052", 605 | "usd_to_rsd": "83.81913", 606 | "usd_to_rub": "36.29832", 607 | "usd_to_rwf": "679.1186", 608 | "usd_to_sar": "3.750645", 609 | "usd_to_sbd": "7.274396", 610 | "usd_to_scr": "12.15606", 611 | "usd_to_sdg": "5.685533", 612 | "usd_to_sek": "6.418678", 613 | "usd_to_sgd": "1.272865", 614 | "usd_to_shp": "0.606762", 615 | "usd_to_sll": "4325.916667", 616 | "usd_to_sos": "1004.79715", 617 | "usd_to_srd": "3.283333", 618 | "usd_to_std": "17791.5", 619 | "usd_to_svc": "8.74926", 620 | "usd_to_syp": "143.413174", 621 | "usd_to_szl": "10.88211", 622 | "usd_to_thb": "32.4166", 623 | "usd_to_tjs": "4.800575", 624 | "usd_to_tmm": "2.8501", 625 | "usd_to_tnd": "1.58188", 626 | "usd_to_top": "1.857171", 627 | "usd_to_try": "2.236923", 628 | "usd_to_ttd": "6.39983", 629 | "usd_to_twd": "30.60052", 630 | "usd_to_tzs": "1637.333333", 631 | "usd_to_uah": "10.5404", 632 | "usd_to_ugx": "2557.883333", 633 | "usd_to_usd": "1.0", 634 | "usd_to_uyu": "22.8409", 635 | "usd_to_uzs": "2246.439971", 636 | "usd_to_vef": "6.2902", 637 | "usd_to_vnd": "21090.933333", 638 | "usd_to_vuv": "95.5525", 639 | "usd_to_wst": "2.324679", 640 | "usd_to_xaf": "475.607249", 641 | "usd_to_xcd": "2.70156", 642 | "usd_to_xof": "475.932622", 643 | "usd_to_xpf": "86.485829", 644 | "usd_to_yer": "214.978001", 645 | "usd_to_zar": "10.89744", 646 | "usd_to_zmk": "5253.075255", 647 | "usd_to_zwl": "322.355006", 648 | "uyu_to_btc": "8.8e-05", 649 | "uyu_to_usd": "0.043781", 650 | "uzs_to_btc": "1.0e-06", 651 | "uzs_to_usd": "0.000445", 652 | "vef_to_btc": "0.000318", 653 | "vef_to_usd": "0.158977", 654 | "vnd_to_btc": "0.0", 655 | "vnd_to_usd": "4.7e-05", 656 | "vuv_to_btc": "2.1e-05", 657 | "vuv_to_usd": "0.010465", 658 | "wst_to_btc": "0.00086", 659 | "wst_to_usd": "0.430167", 660 | "xaf_to_btc": "4.0e-06", 661 | "xaf_to_usd": "0.002103", 662 | "xcd_to_btc": "0.00074", 663 | "xcd_to_usd": "0.370157", 664 | "xof_to_btc": "4.0e-06", 665 | "xof_to_usd": "0.002101", 666 | "xpf_to_btc": "2.3e-05", 667 | "xpf_to_usd": "0.011563", 668 | "yer_to_btc": "9.0e-06", 669 | "yer_to_usd": "0.004652", 670 | "zar_to_btc": "0.000184", 671 | "zar_to_usd": "0.091765", 672 | "zmk_to_btc": "0.0", 673 | "zmk_to_usd": "0.00019", 674 | "zwl_to_btc": "6.0e-06", 675 | "zwl_to_usd": "0.003102" 676 | } 677 | """ 678 | -------------------------------------------------------------------------------- /coinbase/tests/test_mocking.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | import inspect 3 | import unittest 4 | from sure import this 5 | 6 | from coinbase import * 7 | from coinbase.mock import * 8 | 9 | 10 | class CoinbaseAccountMockTests(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.account = CoinbaseAccountMock() 14 | this(len(self.account.transactions())).should.equal(0) 15 | 16 | def test_buy(self): 17 | 18 | # Buy some bitcoin 19 | transaction_id = self.account.buy_btc('3.5').transaction_id 20 | this(self.account.get_transaction(transaction_id).status) \ 21 | .should.equal(CoinbaseTransaction.Status.pending) 22 | this(len(self.account.transactions()),).should.equal(1) 23 | this(self.account.balance).should.equal(CoinbaseAmount('0', 'BTC')) 24 | 25 | # Complete the transaction 26 | self.account.mock.complete_transaction(transaction_id) 27 | this(self.account.get_transaction(transaction_id).status) \ 28 | .should.equal(CoinbaseTransaction.Status.complete) 29 | this(len(self.account.transactions())).should.equal(1) 30 | this(self.account.balance).should.equal(CoinbaseAmount('3.5', 'BTC')) 31 | 32 | def test_sell(self): 33 | 34 | self.account.balance = CoinbaseAmount('10', 'BTC') 35 | 36 | # Sell some bitcoin 37 | transaction_id = self.account.sell_btc('3.5').transaction_id 38 | this(self.account.get_transaction(transaction_id).status) \ 39 | .should.equal(CoinbaseTransaction.Status.pending) 40 | this(len(self.account.transactions())).should.equal(1) 41 | this(self.account.balance).should.equal(CoinbaseAmount('10', 'BTC')) 42 | 43 | # Complete the transaction 44 | self.account.mock.complete_transaction(transaction_id) 45 | this(self.account.get_transaction(transaction_id).status) \ 46 | .should.equal(CoinbaseTransaction.Status.complete) 47 | this(len(self.account.transactions())).should.equal(1) 48 | this(self.account.balance).should.equal(CoinbaseAmount('6.5', 'BTC')) 49 | 50 | def test_payment_via_button(self): 51 | 52 | self.account.balance = CoinbaseAmount('.02', 'BTC') 53 | 54 | # Set up a button 55 | button_id = self.account.create_button(CoinbasePaymentButton( 56 | name='Fuzzy slippers', 57 | price=CoinbaseAmount('5', 'USD'), 58 | callback_url='https://example.com/cb', 59 | custom='pqxrt', 60 | custom_secure=True, 61 | )).id 62 | 63 | # Create an order 64 | order = self.account.mock.create_order_from_button( 65 | button_id, 66 | customer=CoinbaseOrder.Customer(email='alice@example.com'), 67 | ) 68 | 69 | # Pay the order, complete the transaction 70 | callbacks = self.account.mock.accept_payment( 71 | order.receive_address, '.01') 72 | order = self.account.get_order(order.id) 73 | transaction = self.account.get_transaction(order.transaction.id) 74 | 75 | # Verify the resulting state 76 | this(self.account.balance).should.equal(CoinbaseAmount('.03', 'BTC')) 77 | this(order.status).should.equal(CoinbaseOrder.Status.complete) 78 | this(transaction.status).should.equal( 79 | CoinbaseTransaction.Status.complete) 80 | 81 | # Verify the resulting callback 82 | this(len(callbacks)).should.equal(1) 83 | callback_url, callback_body = callbacks[0] 84 | this(callback_url).should.equal('https://example.com/cb') 85 | this(CoinbaseOrder.parse_callback(callback_body)).should.equal(order) 86 | 87 | def test_send(self): 88 | 89 | self.account.balance = CoinbaseAmount('.02', 'BTC') 90 | 91 | # Send someone money 92 | transaction = self.account.send( 93 | to_address='bob@example.com', 94 | amount=CoinbaseAmount('5', 'USD'), 95 | notes='Your refund', 96 | ) 97 | this(transaction.status).should.equal( 98 | CoinbaseTransaction.Status.pending) 99 | this(len(self.account.transactions()),).should.equal(1) 100 | this(self.account.balance).should.equal(CoinbaseAmount('.02', 'BTC')) 101 | 102 | # Complete the transaction 103 | transaction = self.account.mock.complete_transaction(transaction.id) 104 | this(transaction.status).should.equal( 105 | CoinbaseTransaction.Status.complete) 106 | this(len(self.account.transactions())).should.equal(1) 107 | this(self.account.balance).should.equal(CoinbaseAmount('.01', 'BTC')) 108 | 109 | def test_get_exchange_rate(self): 110 | this(self.account.get_exchange_rate('BTC', 'USD')) \ 111 | .should.equal(Decimal('500')) 112 | this(self.account.get_exchange_rate('USD', 'BTC')) \ 113 | .should.equal(Decimal('.002')) 114 | 115 | def test_transactions(self): 116 | self.account.send(amount=CoinbaseAmount('.5', 'BTC'), 117 | to_address='alice@example.com') 118 | self.account.send(amount=CoinbaseAmount('.8', 'BTC'), 119 | to_address='bob@example.com') 120 | this([tx.recipient_address for tx in self.account.transactions()]) \ 121 | .should.equal(['bob@example.com', 'alice@example.com']) 122 | 123 | 124 | class CoinbaseAccountMockSpecTests(unittest.TestCase): 125 | 126 | def test_account_spec(self): 127 | a = public_argspecs(CoinbaseAccount) 128 | b = public_argspecs(CoinbaseAccountMock) 129 | this(a).should.equal(b) 130 | 131 | 132 | def public_argspecs(x): 133 | return dict([(key, inspect.getargspec(value)) 134 | for key, value in x.__dict__.items() 135 | if key[0] != '_' and inspect.isfunction(value)]) 136 | -------------------------------------------------------------------------------- /coinbase/tests/test_order.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseOrder 8 | from .http_mocking import * 9 | from . import account_setup 10 | 11 | 12 | @with_http_mocking 13 | class OrderTest(TestCase): 14 | """ 15 | The example from the API doc 16 | https://coinbase.com/api/doc/1.0/orders/show.html 17 | """ 18 | 19 | def setUp(self): 20 | mock_http('GET https://coinbase.com/api/v1/orders/A7C52JQT', 21 | response_body) 22 | mock_http('GET https://coinbase.com/api/v1/orders/custom123', 23 | response_body) 24 | 25 | def test_get_order_by_id_with_key(self): 26 | account = account_setup.with_key() 27 | this(account.get_order('A7C52JQT')).should.equal(expected_order) 28 | this(last_request_params()).should.equal({ 29 | 'api_key': [account_setup.api_key], 30 | }) 31 | 32 | def test_get_order_by_id_with_oauth(self): 33 | account = account_setup.with_oauth() 34 | this(account.get_order('A7C52JQT')).should.equal(expected_order) 35 | this(last_request_params()).should.equal({}) 36 | 37 | def test_get_order_by_custom_with_oauth(self): 38 | account = account_setup.with_oauth() 39 | this(account.get_order('custom123')).should.equal(expected_order) 40 | this(last_request_params()).should.equal({}) 41 | 42 | 43 | response_body = """ 44 | { 45 | "order": { 46 | "id": "A7C52JQT", 47 | "created_at": "2013-03-11T22:04:37-07:00", 48 | "status": "completed", 49 | "total_btc": { 50 | "cents": 10000000, 51 | "currency_iso": "BTC" 52 | }, 53 | "total_native": { 54 | "cents": 10000000, 55 | "currency_iso": "BTC" 56 | }, 57 | "custom": "custom123", 58 | "receive_address": "mgrmKftH5CeuFBU3THLWuTNKaZoCGJU5jQ", 59 | "button": { 60 | "type": "buy_now", 61 | "name": "test", 62 | "description": "", 63 | "id": "eec6d08e9e215195a471eae432a49fc7" 64 | }, 65 | "transaction": { 66 | "id": "513eb768f12a9cf27400000b", 67 | "hash": 68 | "4cc5eec20cd692f3cdb7fc264a0e1d78b9a7e3d7b862dec1e39cf7e37ababc14", 69 | "confirmations": 0 70 | } 71 | } 72 | } 73 | """ 74 | 75 | 76 | expected_order = CoinbaseOrder( 77 | id='A7C52JQT', 78 | created_at=datetime(2013, 3, 11, 22, 4, 37, 79 | tzinfo=tzoffset(None, -25200)), 80 | status=CoinbaseOrder.Status.complete, 81 | total=CoinbaseAmount.BtcAndNative( 82 | btc=CoinbaseAmount('.1', 'BTC'), 83 | native=CoinbaseAmount('.1', 'BTC'), 84 | ), 85 | custom='custom123', 86 | receive_address='mgrmKftH5CeuFBU3THLWuTNKaZoCGJU5jQ', 87 | button=CoinbaseOrder.Button( 88 | type='buy_now', 89 | name='test', 90 | description='', 91 | id='eec6d08e9e215195a471eae432a49fc7', 92 | ), 93 | transaction=CoinbaseOrder.Transaction( 94 | id='513eb768f12a9cf27400000b', 95 | hash='4cc5eec20cd692f3cdb7fc264a0e1d78' 96 | 'b9a7e3d7b862dec1e39cf7e37ababc14', 97 | confirmations=0, 98 | ), 99 | ) 100 | -------------------------------------------------------------------------------- /coinbase/tests/test_order_callback.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | 3 | from datetime import datetime 4 | from dateutil.tz import tzoffset 5 | 6 | from coinbase import CoinbaseAmount, CoinbaseOrder 7 | 8 | 9 | def test_order_callback(): 10 | """ 11 | The example from the callbacks doc 12 | https://coinbase.com/docs/merchant_tools/callbacks 13 | """ 14 | this(CoinbaseOrder.parse_callback(callback_body)) \ 15 | .should.be.equal(expected_order) 16 | 17 | 18 | callback_body = """ 19 | { 20 | "customer": { 21 | "email": "coinbase@example.com", 22 | "shipping_address": [ 23 | "John Smith", 24 | "123 Main St.", 25 | "Springfield, OR 97477", 26 | "United States" 27 | ] 28 | }, 29 | "order": { 30 | "id": "5RTQNACF", 31 | "created_at": "2012-12-09T21:23:41-08:00", 32 | "status": "completed", 33 | "total_btc": { 34 | "cents": 100000000, 35 | "currency_iso": "BTC" 36 | }, 37 | "total_native": { 38 | "cents": 1253, 39 | "currency_iso": "USD" 40 | }, 41 | "custom": "order1234", 42 | "receive_address": "1NhwPYPgoPwr5hynRAsto5ZgEcw1LzM3My", 43 | "button": { 44 | "type": "buy_now", 45 | "name": "Alpaca Socks", 46 | "description": "The ultimate in lightweight footwear", 47 | "id": "5d37a3b61914d6d0ad15b5135d80c19f" 48 | }, 49 | "transaction": { 50 | "id": "514f18b7a5ea3d630a00000f", 51 | "hash": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 52 | "confirmations": 0 53 | }, 54 | "refund_address": "1HcmQZarSgNuGYz4r7ZkjYumiU4PujrNYk" 55 | } 56 | } 57 | """ 58 | 59 | 60 | expected_order = CoinbaseOrder( 61 | id='5RTQNACF', 62 | created_at=datetime(2012, 12, 9, 21, 23, 41, 63 | tzinfo=tzoffset(None, -28800)), 64 | status=CoinbaseOrder.Status.complete, 65 | total=CoinbaseAmount.BtcAndNative( 66 | btc=CoinbaseAmount('1', 'BTC'), 67 | native=CoinbaseAmount('12.53', 'USD'), 68 | ), 69 | custom='order1234', 70 | receive_address='1NhwPYPgoPwr5hynRAsto5ZgEcw1LzM3My', 71 | button=CoinbaseOrder.Button( 72 | type='buy_now', 73 | name='Alpaca Socks', 74 | description='The ultimate in lightweight footwear', 75 | id='5d37a3b61914d6d0ad15b5135d80c19f', 76 | ), 77 | transaction=CoinbaseOrder.Transaction( 78 | id='514f18b7a5ea3d630a00000f', 79 | hash='4a5e1e4baab89f3a32518a88c31bc87f' 80 | '618f76673e2cc77ab2127b7afdeda33b', 81 | confirmations=0, 82 | ), 83 | customer=CoinbaseOrder.Customer( 84 | email='coinbase@example.com', 85 | shipping_address=[ 86 | 'John Smith', 87 | '123 Main St.', 88 | 'Springfield, OR 97477', 89 | 'United States', 90 | ] 91 | ), 92 | refund_address='1HcmQZarSgNuGYz4r7ZkjYumiU4PujrNYk', 93 | ) 94 | -------------------------------------------------------------------------------- /coinbase/tests/test_order_creation.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseOrder, CoinbasePaymentButton 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class CreateButtonAndOrderTest(TestCase): 14 | """ 15 | The example from https://coinbase.com/api/doc/1.0/orders/create.html 16 | """ 17 | 18 | def setUp(self): 19 | mock_http('POST https://coinbase.com/api/v1/orders', 20 | response_body) 21 | 22 | def test_create_button_and_order_with_key(self): 23 | account = account_setup.with_key() 24 | order = account.create_button_and_order(button_spec) 25 | this(last_request_json()).should.equal(expected_button_json) 26 | this(last_request_params()).should.equal({ 27 | 'api_key': [account_setup.api_key], 28 | }) 29 | this(order).should.equal(expected_order) 30 | 31 | def test_create_button_and_order_with_oauth(self): 32 | account = account_setup.with_oauth() 33 | order = account.create_button_and_order(button_spec) 34 | this(last_request_json()).should.equal(expected_button_json) 35 | this(last_request_params()).should.equal({}) 36 | this(order).should.equal(expected_order) 37 | 38 | 39 | @with_http_mocking 40 | class CreateOrderFromButtonTest(TestCase): 41 | """ 42 | The example from 43 | https://coinbase.com/api/doc/1.0/buttons/create_order.html 44 | """ 45 | 46 | def setUp(self): 47 | mock_http('POST https://coinbase.com/api/v1/buttons/' 48 | '93865b9cae83706ae59220c013bc0afd/create_order', 49 | response_body) 50 | 51 | def test_create_order_from_button_with_key(self): 52 | account = account_setup.with_key() 53 | order = account.create_order_from_button( 54 | button_id='93865b9cae83706ae59220c013bc0afd') 55 | this(last_request_body()).should.equal(b'') 56 | this(last_request_params()).should.equal({ 57 | 'api_key': [account_setup.api_key], 58 | }) 59 | this(order).should.equal(expected_order) 60 | 61 | def test_create_order_from_button_with_oauth(self): 62 | account = account_setup.with_oauth() 63 | order = account.create_order_from_button( 64 | button_id='93865b9cae83706ae59220c013bc0afd') 65 | this(last_request_body()).should.equal(b'') 66 | this(last_request_params()).should.equal({}) 67 | this(order).should.equal(expected_order) 68 | 69 | 70 | button_spec = CoinbasePaymentButton( 71 | name='test', 72 | type='buy_now', 73 | price=CoinbaseAmount('1.23', 'USD'), 74 | ) 75 | 76 | 77 | expected_button_json = { 78 | 'button': { 79 | 'name': 'test', 80 | 'type': 'buy_now', 81 | 'price_string': '1.23', 82 | 'price_currency_iso': 'USD', 83 | } 84 | } 85 | 86 | 87 | response_body = """ 88 | { 89 | "success": true, 90 | "order": { 91 | "id": "8QNULQFE", 92 | "created_at": "2014-02-04T23:36:30-08:00", 93 | "status": "new", 94 | "total_btc": { 95 | "cents": 12300000, 96 | "currency_iso": "BTC" 97 | }, 98 | "total_native": { 99 | "cents": 123, 100 | "currency_iso": "USD" 101 | }, 102 | "custom": null, 103 | "receive_address": "mnskjZs57dBAmeU2n4csiRKoQcGRF4tpxH", 104 | "button": { 105 | "type": "buy_now", 106 | "name": "test", 107 | "description": null, 108 | "id": "1741b3be1eb5dc50625c48851a94ae13" 109 | }, 110 | "transaction": null 111 | } 112 | } 113 | """ 114 | 115 | 116 | expected_order = CoinbaseOrder( 117 | id='8QNULQFE', 118 | created_at=datetime(2014, 2, 4, 23, 36, 30, 119 | tzinfo=tzoffset(None, -28800)), 120 | status=CoinbaseOrder.Status.pending, 121 | total=CoinbaseAmount.BtcAndNative( 122 | btc=CoinbaseAmount('.12300000', 'BTC'), 123 | native=CoinbaseAmount('1.23', 'USD'), 124 | ), 125 | receive_address='mnskjZs57dBAmeU2n4csiRKoQcGRF4tpxH', 126 | button=CoinbaseOrder.Button( 127 | type='buy_now', 128 | name='test', 129 | id='1741b3be1eb5dc50625c48851a94ae13', 130 | ), 131 | ) 132 | -------------------------------------------------------------------------------- /coinbase/tests/test_order_list.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseOrder 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class OrdersTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('GET https://coinbase.com/api/v1/orders', 17 | response_body) 18 | 19 | def test_orders_with_key(self): 20 | account = account_setup.with_key() 21 | this(account.orders()).should.equal(expected_orders) 22 | this(last_request_params()).should.equal({ 23 | 'api_key': [account_setup.api_key], 24 | }) 25 | 26 | def test_orders_with_oauth(self): 27 | account = account_setup.with_oauth() 28 | this(account.orders()).should.equal(expected_orders) 29 | this(last_request_params()).should.equal({}) 30 | 31 | 32 | response_body = """ 33 | { 34 | "current_page": 1, 35 | "num_pages": 131, 36 | "orders": [ 37 | { 38 | "customer": { 39 | "email": null 40 | }, 41 | "order": { 42 | "button": { 43 | "description": "warm and fuzzy", 44 | "id": "0fde6d456181be1279fef6879d6897a3", 45 | "name": "Alpaca socks", 46 | "type": "buy_now" 47 | }, 48 | "created_at": "2014-04-21T10:25:50-07:00", 49 | "custom": "abcdef", 50 | "id": "8DJ2Z9AQ", 51 | "receive_address": "8uREGg34ji4gn43M93cuibhbkfi6FbyF1g", 52 | "status": "expired", 53 | "total_btc": { 54 | "cents": 1818000, 55 | "currency_iso": "BTC" 56 | }, 57 | "total_native": { 58 | "cents": 900, 59 | "currency_iso": "USD" 60 | }, 61 | "transaction": null 62 | } 63 | }, 64 | { 65 | "customer": { 66 | "email": "alice@example.com" 67 | }, 68 | "order": { 69 | "button": { 70 | "description": "20% off", 71 | "id": "69adb65c95af59ed5b9ab5de55a579db", 72 | "name": "Pineapple", 73 | "type": "buy_now" 74 | }, 75 | "created_at": "2014-04-21T09:56:57-07:00", 76 | "custom": "ghijkl", 77 | "id": "J3KAD35D", 78 | "receive_address": "b87nihewshngyuFUbu6fy5vbtdtryfhhj1", 79 | "status": "completed", 80 | "total_btc": { 81 | "cents": 799600, 82 | "currency_iso": "BTC" 83 | }, 84 | "total_native": { 85 | "cents": 400, 86 | "currency_iso": "USD" 87 | }, 88 | "transaction": { 89 | "confirmations": 11, 90 | "hash": 91 | "67b6a75d56cd5675868d5695c695865ab9568ef5895653a2f23454d45e4a357a", 92 | "id": "658bc586df6ef56740ac6de5" 93 | } 94 | } 95 | }, 96 | { 97 | "customer": { 98 | "email": "bob@example.com" 99 | }, 100 | "order": { 101 | "button": { 102 | "description": null, 103 | "id": "586df68e5a665c6975d569e569a768c5", 104 | "name": "Things", 105 | "type": "buy_now" 106 | }, 107 | "created_at": "2014-04-19T17:07:37-07:00", 108 | "custom": "xyzzy", 109 | "id": "7DAF5310", 110 | "mispaid_btc": { 111 | "cents": 2034753, 112 | "currency_iso": "BTC" 113 | }, 114 | "mispaid_native": { 115 | "cents": 1007, 116 | "currency_iso": "USD" 117 | }, 118 | "receive_address": "8Wmgg87fgu6777ihgbFTYugyjfFT686fFf", 119 | "status": "mispaid", 120 | "total_btc": { 121 | "cents": 1980000, 122 | "currency_iso": "BTC" 123 | }, 124 | "total_native": { 125 | "cents": 1000, 126 | "currency_iso": "USD" 127 | }, 128 | "transaction": { 129 | "confirmations": 314, 130 | "hash": 131 | "56949ae6498b66f9865e67a6c4d759578ad5986e65965f5965a695696ec59c5d", 132 | "id": "16a64b43fe6c435a45c07a0d" 133 | } 134 | } 135 | } 136 | ], 137 | "total_count": 3262 138 | } 139 | """ 140 | 141 | 142 | expected_orders = [ 143 | CoinbaseOrder( 144 | id='8DJ2Z9AQ', 145 | created_at=datetime(2014, 4, 21, 10, 25, 50, 146 | tzinfo=tzoffset(None, -25200)), 147 | status=CoinbaseOrder.Status.expired, 148 | receive_address='8uREGg34ji4gn43M93cuibhbkfi6FbyF1g', 149 | button=CoinbaseOrder.Button( 150 | id='0fde6d456181be1279fef6879d6897a3', 151 | description='warm and fuzzy', 152 | name='Alpaca socks', 153 | type='buy_now', 154 | ), 155 | custom='abcdef', 156 | total=CoinbaseAmount.BtcAndNative( 157 | btc=CoinbaseAmount('.01818000', 'BTC'), 158 | native=CoinbaseAmount('9', 'USD'), 159 | ), 160 | customer=CoinbaseOrder.Customer(), 161 | ), 162 | CoinbaseOrder( 163 | id='J3KAD35D', 164 | created_at=datetime(2014, 4, 21, 9, 56, 57, 165 | tzinfo=tzoffset(None, -25200)), 166 | status=CoinbaseOrder.Status.complete, 167 | receive_address='b87nihewshngyuFUbu6fy5vbtdtryfhhj1', 168 | button=CoinbaseOrder.Button( 169 | id='69adb65c95af59ed5b9ab5de55a579db', 170 | description='20% off', 171 | name='Pineapple', 172 | type='buy_now', 173 | ), 174 | custom='ghijkl', 175 | total=CoinbaseAmount.BtcAndNative( 176 | btc=CoinbaseAmount('.00799600', 'BTC'), 177 | native=CoinbaseAmount('4', 'USD'), 178 | ), 179 | transaction=CoinbaseOrder.Transaction( 180 | id='658bc586df6ef56740ac6de5', 181 | hash='67b6a75d56cd5675868d5695c695865a' 182 | 'b9568ef5895653a2f23454d45e4a357a', 183 | confirmations=11 184 | ), 185 | customer=CoinbaseOrder.Customer( 186 | email='alice@example.com', 187 | ), 188 | ), 189 | CoinbaseOrder( 190 | id='7DAF5310', 191 | created_at=datetime(2014, 4, 19, 17, 7, 37, 192 | tzinfo=tzoffset(None, -25200)), 193 | status=CoinbaseOrder.Status.mispaid, 194 | receive_address='8Wmgg87fgu6777ihgbFTYugyjfFT686fFf', 195 | button=CoinbaseOrder.Button( 196 | id='586df68e5a665c6975d569e569a768c5', 197 | name='Things', 198 | type='buy_now', 199 | ), 200 | custom='xyzzy', 201 | mispaid=CoinbaseAmount.BtcAndNative( 202 | btc=CoinbaseAmount('.02034753', 'BTC'), 203 | native=CoinbaseAmount('10.07', 'USD'), 204 | ), 205 | total=CoinbaseAmount.BtcAndNative( 206 | btc=CoinbaseAmount('.0198', 'BTC'), 207 | native=CoinbaseAmount('10', 'USD'), 208 | ), 209 | customer=CoinbaseOrder.Customer( 210 | email='bob@example.com', 211 | ), 212 | transaction=CoinbaseOrder.Transaction( 213 | id='16a64b43fe6c435a45c07a0d', 214 | hash='56949ae6498b66f9865e67a6c4d75957' 215 | '8ad5986e65965f5965a695696ec59c5d', 216 | confirmations=314, 217 | ), 218 | ), 219 | ] 220 | -------------------------------------------------------------------------------- /coinbase/tests/test_receive_address.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from . import account_setup 5 | from .http_mocking import * 6 | 7 | 8 | @with_http_mocking 9 | class ReceiveAddressTest(TestCase): 10 | 11 | def setUp(self): 12 | mock_http('GET https://coinbase.com/api/v1/account/receive_address', 13 | response_body) 14 | 15 | def test_receive_addresses_with_key(self): 16 | account = account_setup.with_key() 17 | this(account.receive_address).should.equal(expected_receive_address) 18 | this(last_request_params()).should.equal({ 19 | 'api_key': [account_setup.api_key], 20 | }) 21 | 22 | def test_receive_addresses_with_oauth(self): 23 | account = account_setup.with_oauth() 24 | this(account.receive_address).should.equal(expected_receive_address) 25 | this(last_request_params()).should.equal({}) 26 | 27 | 28 | response_body = """ 29 | { 30 | "address":"1DX9ECEF3FbGUtzzoQhDT8CG3nLUEA2FJt" 31 | } 32 | """ 33 | 34 | 35 | expected_receive_address = '1DX9ECEF3FbGUtzzoQhDT8CG3nLUEA2FJt' 36 | -------------------------------------------------------------------------------- /coinbase/tests/test_request.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseTransaction, CoinbaseContact 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class RequestTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('POST https://coinbase.com/api/v1/transactions/request_money', 17 | response_body) 18 | 19 | def test_request_bitcoin_with_key(self): 20 | account = account_setup.with_key() 21 | transaction = account.request(**request_args) 22 | this(last_request_json()).should.equal(expected_request_json) 23 | this(last_request_params()).should.equal({ 24 | 'api_key': [account_setup.api_key], 25 | }) 26 | this(transaction).should.equal(expected_transaction) 27 | 28 | def test_request_bitcoin_with_oauth(self): 29 | account = account_setup.with_oauth() 30 | transaction = account.request(**request_args) 31 | this(last_request_json()).should.equal(expected_request_json) 32 | this(last_request_params()).should.equal({}) 33 | this(transaction).should.equal(expected_transaction) 34 | 35 | 36 | request_args = { 37 | 'from_email': 'alice@example.com', 38 | 'amount': CoinbaseAmount('1', 'BTC'), 39 | 'notes': 'Testing', 40 | } 41 | 42 | 43 | expected_request_json = { 44 | 'transaction': { 45 | 'from': 'alice@example.com', 46 | 'amount': '1', 47 | 'notes': 'Testing', 48 | } 49 | } 50 | 51 | 52 | response_body = """ 53 | { 54 | "success": true, 55 | "transaction": { 56 | "amount": { 57 | "amount": "1.00000000", 58 | "currency": "BTC" 59 | }, 60 | "created_at": "2013-03-23T17:43:35-07:00", 61 | "hsh": null, 62 | "id": "96ab6e96f69a69c6d6960173", 63 | "notes": "Testing", 64 | "recipient": { 65 | "email": "bob@example.com", 66 | "id": "65ab697e5d67a58675675d31", 67 | "name": "bob@example.com" 68 | }, 69 | "request": true, 70 | "sender": { 71 | "email": "alice@example.com", 72 | "id": "956df569c9ae67598a6c56e9", 73 | "name": "alice@example.com" 74 | }, 75 | "status": "pending" 76 | } 77 | } 78 | """ 79 | 80 | 81 | expected_transaction = CoinbaseTransaction( 82 | amount=CoinbaseAmount('1', 'BTC'), 83 | created_at=datetime(2013, 3, 23, 17, 43, 35, 84 | tzinfo=tzoffset(None, -25200)), 85 | id='96ab6e96f69a69c6d6960173', 86 | notes='Testing', 87 | recipient=CoinbaseContact( 88 | id='65ab697e5d67a58675675d31', 89 | name='bob@example.com', 90 | email='bob@example.com', 91 | ), 92 | request=True, 93 | sender=CoinbaseContact( 94 | id='956df569c9ae67598a6c56e9', 95 | name='alice@example.com', 96 | email='alice@example.com', 97 | ), 98 | status=CoinbaseTransaction.Status.pending, 99 | recipient_type='coinbase', 100 | ) 101 | -------------------------------------------------------------------------------- /coinbase/tests/test_sell_price.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from coinbase import CoinbaseAmount 5 | from . import account_setup 6 | from .http_mocking import * 7 | 8 | 9 | @with_http_mocking 10 | class SellPriceTest1(TestCase): 11 | 12 | def setUp(self): 13 | mock_http('GET https://coinbase.com/api/v1/prices/sell', 14 | self.response_body) 15 | 16 | def test_sell_price_without_auth(self): 17 | self.go(account_setup.without_auth()) 18 | 19 | def test_sell_price_with_key(self): 20 | self.go(account_setup.with_key()) 21 | 22 | def test_sell_price_with_oauth(self): 23 | self.go(account_setup.with_oauth()) 24 | 25 | def go(self, account): 26 | this(account.sell_price()).should.equal(self.expected_price) 27 | params = last_request_params() 28 | params.pop('api_key', None) 29 | this(params).should.equal({'qty': ['1']}) 30 | 31 | response_body = """ 32 | { 33 | "amount": "63.31", 34 | "currency": "USD" 35 | } 36 | """ 37 | 38 | expected_price = CoinbaseAmount('63.31', 'USD') 39 | 40 | 41 | @with_http_mocking 42 | class SellPriceTest2(TestCase): 43 | 44 | def setUp(self): 45 | mock_http('GET https://coinbase.com/api/v1/prices/sell', 46 | self.response_body) 47 | 48 | def test_sell_price_without_auth(self): 49 | self.go(account_setup.with_key()) 50 | 51 | def test_sell_price_with_key(self): 52 | self.go(account_setup.with_key()) 53 | 54 | def test_sell_price_with_oauth(self): 55 | self.go(account_setup.with_oauth()) 56 | 57 | def go(self, account): 58 | this(account.sell_price(10)).should.equal(self.expected_price) 59 | params = last_request_params() 60 | params.pop('api_key', None) 61 | this(params).should.equal({'qty': ['10']}) 62 | 63 | response_body = """ 64 | { 65 | "amount": "630.31", 66 | "currency": "USD" 67 | } 68 | """ 69 | 70 | expected_price = CoinbaseAmount('630.31', 'USD') 71 | -------------------------------------------------------------------------------- /coinbase/tests/test_send_btc_to_bitcoin.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseContact, CoinbaseTransaction 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class SendBtcToBitcoinTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('POST https://coinbase.com/api/v1/transactions/send_money', 17 | response_body) 18 | 19 | def test_send_btc_to_bitcoinaddress_with_key(self): 20 | account = account_setup.with_key() 21 | tx = account.send( 22 | to_address='7nregFERfhn8f34FERf8yn8fEGgfe274nv', 23 | amount=CoinbaseAmount('0.1', 'BTC'), 24 | idem='abc', 25 | ) 26 | this(last_request_json()).should.equal(expected_request_json) 27 | this(last_request_params()).should.equal({ 28 | 'api_key': [account_setup.api_key], 29 | }) 30 | this(tx).should.equal(expected_transaction) 31 | 32 | def test_send_btc_to_bitcoin_address_with_oauth(self): 33 | account = account_setup.with_oauth() 34 | tx = account.send( 35 | to_address='7nregFERfhn8f34FERf8yn8fEGgfe274nv', 36 | amount=CoinbaseAmount('0.1', 'BTC'), 37 | idem='abc', 38 | ) 39 | this(last_request_json()).should.equal(expected_request_json) 40 | this(last_request_params()).should.equal({}) 41 | this(tx).should.equal(expected_transaction) 42 | 43 | 44 | expected_request_json = { 45 | 'transaction': { 46 | 'to': '7nregFERfhn8f34FERf8yn8fEGgfe274nv', 47 | 'amount': '0.1', 48 | 'notes': '', 49 | 'idem': 'abc', 50 | } 51 | } 52 | 53 | 54 | response_body = """ 55 | { 56 | "success": true, 57 | "transaction": { 58 | "amount": { 59 | "amount": "-0.10000000", 60 | "currency": "BTC" 61 | }, 62 | "created_at": "2013-03-31T15:01:11-07:00", 63 | "hsh": null, 64 | "id": "760n6abc6790e6bd67e6ba50", 65 | "notes": "", 66 | "idem": "abc", 67 | "recipient_address": "7nregFERfhn8f34FERf8yn8fEGgfe274nv", 68 | "request": false, 69 | "sender": { 70 | "email": "alice@example.com", 71 | "id": "701bdfea6f6e1062b6823532", 72 | "name": "alice@example.com" 73 | }, 74 | "status": "pending" 75 | } 76 | } 77 | """ 78 | 79 | 80 | expected_transaction = CoinbaseTransaction( 81 | id='760n6abc6790e6bd67e6ba50', 82 | created_at=datetime(2013, 3, 31, 15, 1, 11, 83 | tzinfo=tzoffset(None, -25200)), 84 | notes='', 85 | idem='abc', 86 | amount=CoinbaseAmount('-0.1', 'BTC'), 87 | status=CoinbaseTransaction.Status.pending, 88 | request=False, 89 | sender=CoinbaseContact( 90 | id='701bdfea6f6e1062b6823532', 91 | email='alice@example.com', 92 | name='alice@example.com', 93 | ), 94 | recipient_address='7nregFERfhn8f34FERf8yn8fEGgfe274nv', 95 | recipient_type='bitcoin', 96 | ) 97 | -------------------------------------------------------------------------------- /coinbase/tests/test_send_btc_to_email.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseContact, CoinbaseTransaction 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class SendBtcToEmailTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('POST https://coinbase.com/api/v1/transactions/send_money', 17 | response_body) 18 | 19 | def test_send_btc_to_email_address_with_key(self): 20 | account = account_setup.with_key() 21 | tx = account.send( 22 | to_address='bob@example.com', 23 | amount=CoinbaseAmount('0.1', 'BTC'), 24 | ) 25 | this(last_request_json()).should.equal(expected_request_json) 26 | this(last_request_params()).should.equal({ 27 | 'api_key': [account_setup.api_key], 28 | }) 29 | this(tx).should.equal(expected_transaction) 30 | 31 | def test_send_btc_to_email_address_with_oauth(self): 32 | account = account_setup.with_oauth() 33 | tx = account.send( 34 | to_address='bob@example.com', 35 | amount=CoinbaseAmount('0.1', 'BTC'), 36 | ) 37 | this(last_request_json()).should.equal(expected_request_json) 38 | this(last_request_params()).should.equal({}) 39 | this(tx).should.equal(expected_transaction) 40 | 41 | 42 | expected_request_json = { 43 | 'transaction': { 44 | 'to': 'bob@example.com', 45 | 'amount': '0.1', 46 | 'notes': '', 47 | } 48 | } 49 | 50 | 51 | response_body = """ 52 | { 53 | "success": true, 54 | "transaction": { 55 | "amount": { 56 | "amount": "-0.10000000", 57 | "currency": "BTC" 58 | }, 59 | "created_at": "2013-03-31T15:02:58-07:00", 60 | "hsh": null, 61 | "id": "69ab532bde59cfba595c5738", 62 | "notes": "", 63 | "idem": "", 64 | "recipient": { 65 | "email": "bob@example.com", 66 | "id": "72370bd60efa506c6596d56e", 67 | "name": "Bob" 68 | }, 69 | "recipient_address": "bob@example.com", 70 | "request": false, 71 | "sender": { 72 | "email": "alice@example.com", 73 | "id": "016bde60ac5603bde5300011", 74 | "name": "alice@example.com" 75 | }, 76 | "status": "pending" 77 | } 78 | } 79 | """ 80 | 81 | 82 | expected_transaction = CoinbaseTransaction( 83 | id='69ab532bde59cfba595c5738', 84 | created_at=datetime(2013, 3, 31, 15, 2, 58, 85 | tzinfo=tzoffset(None, -25200)), 86 | notes='', 87 | amount=CoinbaseAmount('-0.1', 'BTC'), 88 | status=CoinbaseTransaction.Status.pending, 89 | request=False, 90 | sender=CoinbaseContact( 91 | id='016bde60ac5603bde5300011', 92 | email='alice@example.com', 93 | name='alice@example.com', 94 | ), 95 | recipient=CoinbaseContact( 96 | id='72370bd60efa506c6596d56e', 97 | email='bob@example.com', 98 | name='Bob', 99 | ), 100 | recipient_address='bob@example.com', 101 | recipient_type='coinbase', 102 | ) 103 | -------------------------------------------------------------------------------- /coinbase/tests/test_send_usd_to_bitcoin.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseContact, CoinbaseTransaction 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class SendUsdToBitcoinTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('POST https://coinbase.com/api/v1/transactions/send_money', 17 | response_body) 18 | 19 | def test_send_usd_to_bitcoin_address_with_key(self): 20 | account = account_setup.with_key() 21 | tx = account.send( 22 | to_address='7nregFERfhn8f34FERf8yn8fEGgfe274nv', 23 | amount=CoinbaseAmount('12', 'USD'), 24 | ) 25 | this(last_request_json()).should.equal(expected_request_json) 26 | this(last_request_params()).should.equal({ 27 | 'api_key': [account_setup.api_key], 28 | }) 29 | this(tx).should.equal(expected_transaction) 30 | 31 | def test_send_usd_to_bitcoin_address_with_oauth(self): 32 | account = account_setup.with_oauth() 33 | tx = account.send( 34 | to_address='7nregFERfhn8f34FERf8yn8fEGgfe274nv', 35 | amount=CoinbaseAmount('12', 'USD'), 36 | ) 37 | this(last_request_json()).should.equal(expected_request_json) 38 | this(last_request_params()).should.equal({}) 39 | this(tx).should.equal(expected_transaction) 40 | 41 | 42 | expected_request_json = { 43 | 'transaction': { 44 | 'to': '7nregFERfhn8f34FERf8yn8fEGgfe274nv', 45 | 'amount_string': '12', 46 | 'amount_currency_iso': 'USD', 47 | 'notes': '', 48 | } 49 | } 50 | 51 | 52 | response_body = """ 53 | { 54 | "success": true, 55 | "transaction": { 56 | "amount": { 57 | "amount": "-12.00000000", 58 | "currency": "USD" 59 | }, 60 | "created_at": "2013-03-31T15:01:11-07:00", 61 | "hsh": null, 62 | "id": "760n6abc6790e6bd67e6ba50", 63 | "notes": "", 64 | "idem": "", 65 | "recipient_address": "7nregFERfhn8f34FERf8yn8fEGgfe274nv", 66 | "request": false, 67 | "sender": { 68 | "email": "alice@example.com", 69 | "id": "701bdfea6f6e1062b6823532", 70 | "name": "alice@example.com" 71 | }, 72 | "status": "pending" 73 | } 74 | } 75 | """ 76 | 77 | 78 | expected_transaction = CoinbaseTransaction( 79 | id='760n6abc6790e6bd67e6ba50', 80 | created_at=datetime(2013, 3, 31, 15, 1, 11, 81 | tzinfo=tzoffset(None, -25200)), 82 | notes='', 83 | amount=CoinbaseAmount('-12', 'USD'), 84 | status=CoinbaseTransaction.Status.pending, 85 | request=False, 86 | sender=CoinbaseContact( 87 | id='701bdfea6f6e1062b6823532', 88 | email='alice@example.com', 89 | name='alice@example.com', 90 | ), 91 | recipient_address='7nregFERfhn8f34FERf8yn8fEGgfe274nv', 92 | recipient_type='bitcoin', 93 | ) 94 | -------------------------------------------------------------------------------- /coinbase/tests/test_send_usd_to_email.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseContact, CoinbaseTransaction 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class SendUsdToEmailTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('POST https://coinbase.com/api/v1/transactions/send_money', 17 | response_body) 18 | 19 | def test_send_btc_to_email_address_with_key(self): 20 | account = account_setup.with_key() 21 | tx = account.send( 22 | to_address='bob@example.com', 23 | amount=CoinbaseAmount('12', 'USD'), 24 | ) 25 | this(last_request_json()).should.equal(expected_request_json) 26 | this(last_request_params()).should.equal({ 27 | 'api_key': [account_setup.api_key], 28 | }) 29 | this(tx).should.equal(expected_transaction) 30 | 31 | def test_send_btc_to_email_address_with_oauth(self): 32 | account = account_setup.with_oauth() 33 | tx = account.send( 34 | to_address='bob@example.com', 35 | amount=CoinbaseAmount('12', 'USD'), 36 | ) 37 | this(last_request_json()).should.equal(expected_request_json) 38 | this(last_request_params()).should.equal({}) 39 | this(tx).should.equal(expected_transaction) 40 | 41 | 42 | expected_request_json = { 43 | 'transaction': { 44 | 'to': 'bob@example.com', 45 | 'amount_string': '12', 46 | 'amount_currency_iso': 'USD', 47 | 'notes': '', 48 | } 49 | } 50 | 51 | 52 | response_body = """ 53 | { 54 | "success": true, 55 | "transaction": { 56 | "amount": { 57 | "amount": "-12.00000000", 58 | "currency": "USD" 59 | }, 60 | "created_at": "2013-03-31T15:02:58-07:00", 61 | "hsh": null, 62 | "id": "69ab532bde59cfba595c5738", 63 | "notes": "", 64 | "idem": "", 65 | "recipient": { 66 | "email": "bob@example.com", 67 | "id": "72370bd60efa506c6596d56e", 68 | "name": "Bob" 69 | }, 70 | "recipient_address": "bob@example.com", 71 | "request": false, 72 | "sender": { 73 | "email": "alice@example.com", 74 | "id": "016bde60ac5603bde5300011", 75 | "name": "alice@example.com" 76 | }, 77 | "status": "pending" 78 | } 79 | } 80 | """ 81 | 82 | 83 | expected_transaction = CoinbaseTransaction( 84 | id='69ab532bde59cfba595c5738', 85 | created_at=datetime(2013, 3, 31, 15, 2, 58, 86 | tzinfo=tzoffset(None, -25200)), 87 | notes='', 88 | amount=CoinbaseAmount('-12', 'USD'), 89 | status=CoinbaseTransaction.Status.pending, 90 | request=False, 91 | sender=CoinbaseContact( 92 | id='016bde60ac5603bde5300011', 93 | email='alice@example.com', 94 | name='alice@example.com', 95 | ), 96 | recipient=CoinbaseContact( 97 | id='72370bd60efa506c6596d56e', 98 | email='bob@example.com', 99 | name='Bob', 100 | ), 101 | recipient_address='bob@example.com', 102 | recipient_type='coinbase', 103 | ) 104 | -------------------------------------------------------------------------------- /coinbase/tests/test_transaction.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseContact, CoinbaseTransaction 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class TransactionTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('GET https://coinbase.com/api/v1/transactions/' 17 | '5158b227802669269c000009', response_body) 18 | 19 | def test_getting_transaction_with_oauth(self): 20 | account = account_setup.with_oauth() 21 | transaction = account.get_transaction('5158b227802669269c000009') 22 | this(transaction).should.equal(expected_transaction) 23 | 24 | 25 | response_body = """ 26 | { 27 | "transaction": { 28 | "amount": { 29 | "amount": "-0.10000000", 30 | "currency": "BTC" 31 | }, 32 | "created_at": "2013-03-31T15:01:11-07:00", 33 | "hsh": 34 | "223a404485c39173ab41f343439e59b53a5d6cba94a02501fc6c67eeca0d9d9e", 35 | "id": "5158b227802669269c000009", 36 | "notes": "", 37 | "idem": "abcdef", 38 | "recipient_address": "15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP", 39 | "request": false, 40 | "sender": { 41 | "email": "gsibble@gmail.com", 42 | "id": "509e01ca12838e0200000212", 43 | "name": "gsibble@gmail.com" 44 | }, 45 | "status": "pending" 46 | } 47 | } 48 | """ 49 | 50 | 51 | expected_transaction = CoinbaseTransaction( 52 | id='5158b227802669269c000009', 53 | status=CoinbaseTransaction.Status.pending, 54 | amount=CoinbaseAmount('-0.1', 'BTC'), 55 | hash='223a404485c39173ab41f343439e59b53a5d6cba94a02501fc6c67eeca0d9d9e', 56 | idem='abcdef', 57 | created_at=datetime(2013, 3, 31, 15, 1, 11, 58 | tzinfo=tzoffset(None, -25200)), 59 | notes='', 60 | recipient_address='15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP', 61 | recipient_type='bitcoin', 62 | request=False, 63 | sender=CoinbaseContact( 64 | id='509e01ca12838e0200000212', 65 | email='gsibble@gmail.com', 66 | name='gsibble@gmail.com', 67 | ), 68 | ) 69 | -------------------------------------------------------------------------------- /coinbase/tests/test_transaction_list.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseContact, CoinbaseTransaction 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class TransactionListTest(TestCase): 14 | 15 | def setUp(self): 16 | mock_http('GET https://coinbase.com/api/v1/transactions', 17 | response_body) 18 | 19 | def test_transaction_list_with_oauth(self): 20 | account = account_setup.with_oauth() 21 | this(account.transactions()).should.equal(expected_transactions) 22 | 23 | 24 | response_body = """ 25 | { 26 | "balance": { 27 | "amount": "0.00000000", 28 | "currency": "BTC" 29 | }, 30 | "current_page": 1, 31 | "current_user": { 32 | "email": "gsibble@gmail.com", 33 | "id": "509e01ca12838e0200000212", 34 | "name": "gsibble@gmail.com" 35 | }, 36 | "num_pages": 1, 37 | "total_count": 4, 38 | "transactions": [ 39 | { 40 | "transaction": { 41 | "amount": { 42 | "amount": "1.00000000", 43 | "currency": "BTC" 44 | }, 45 | "created_at": "2013-03-23T17:43:35-07:00", 46 | "hsh": null, 47 | "id": "514e4c37802e1bf69100000e", 48 | "notes": "Testing", 49 | "idem": "", 50 | "recipient": { 51 | "email": "gsibble@gmail.com", 52 | "id": "509e01ca12838e0200000212", 53 | "name": "gsibble@gmail.com" 54 | }, 55 | "request": true, 56 | "sender": { 57 | "email": "george@atlasr.com", 58 | "id": "514e4c1c802e1bef9800001e", 59 | "name": "george@atlasr.com" 60 | }, 61 | "status": "pending" 62 | } 63 | }, 64 | { 65 | "transaction": { 66 | "amount": { 67 | "amount": "1.00000000", 68 | "currency": "BTC" 69 | }, 70 | "created_at": "2013-03-23T17:43:08-07:00", 71 | "hsh": null, 72 | "id": "514e4c1c802e1bef98000020", 73 | "notes": "Testing", 74 | "idem": "", 75 | "recipient": { 76 | "email": "gsibble@gmail.com", 77 | "id": "509e01ca12838e0200000212", 78 | "name": "gsibble@gmail.com" 79 | }, 80 | "request": true, 81 | "sender": { 82 | "email": "george@atlasr.com", 83 | "id": "514e4c1c802e1bef9800001e", 84 | "name": "george@atlasr.com" 85 | }, 86 | "status": "pending" 87 | } 88 | }, 89 | { 90 | "transaction": { 91 | "amount": { 92 | "amount": "-1.00000000", 93 | "currency": "BTC" 94 | }, 95 | "created_at": "2013-03-21T17:02:57-07:00", 96 | "hsh": 97 | "42dd65a18dbea0779f32021663e60b1fab8ee0f859db7172a078d4528e01c6c8", 98 | "id": "514b9fb1b8377ee36500000d", 99 | "notes": "You gave me this a while ago.", 100 | "idem": "jkl", 101 | "recipient": { 102 | "email": "brian@coinbase.com", 103 | "id": "4efec8d7bedd320001000003", 104 | "name": "Brian Armstrong" 105 | }, 106 | "recipient_address": "brian@coinbase.com", 107 | "request": false, 108 | "sender": { 109 | "email": "gsibble@gmail.com", 110 | "id": "509e01ca12838e0200000212", 111 | "name": "gsibble@gmail.com" 112 | }, 113 | "status": "complete" 114 | } 115 | }, 116 | { 117 | "transaction": { 118 | "amount": { 119 | "amount": "1.00000000", 120 | "currency": "BTC" 121 | }, 122 | "created_at": "2012-11-09T23:27:07-08:00", 123 | "hsh": 124 | "ac9b0ffbe36dbe12c5ca047a5bdf9cadca3c9b89b74751dff83b3ac863ccc0b3", 125 | "id": "509e01cb12838e0200000224", 126 | "notes": "", 127 | "idem": "xyz", 128 | "recipient": { 129 | "email": "gsibble@gmail.com", 130 | "id": "509e01ca12838e0200000212", 131 | "name": "gsibble@gmail.com" 132 | }, 133 | "recipient_address": "gsibble@gmail.com", 134 | "request": false, 135 | "sender": { 136 | "email": "brian@coinbase.com", 137 | "id": "4efec8d7bedd320001000003", 138 | "name": "Brian Armstrong" 139 | }, 140 | "status": "complete" 141 | } 142 | } 143 | ] 144 | } 145 | """ 146 | 147 | 148 | expected_transactions = [ 149 | CoinbaseTransaction( 150 | amount=CoinbaseAmount('1', 'BTC'), 151 | created_at=datetime(2013, 3, 23, 17, 43, 35, 152 | tzinfo=tzoffset(None, -25200)), 153 | id='514e4c37802e1bf69100000e', 154 | notes='Testing', 155 | recipient=CoinbaseContact( 156 | id='509e01ca12838e0200000212', 157 | email='gsibble@gmail.com', 158 | name='gsibble@gmail.com', 159 | ), 160 | recipient_type='coinbase', 161 | request=True, 162 | sender=CoinbaseContact( 163 | id='514e4c1c802e1bef9800001e', 164 | email='george@atlasr.com', 165 | name='george@atlasr.com', 166 | ), 167 | status=CoinbaseTransaction.Status.pending, 168 | ), 169 | CoinbaseTransaction( 170 | amount=CoinbaseAmount('1', 'BTC'), 171 | created_at=datetime(2013, 3, 23, 17, 43, 8, 172 | tzinfo=tzoffset(None, -25200)), 173 | id='514e4c1c802e1bef98000020', 174 | notes='Testing', 175 | recipient=CoinbaseContact( 176 | id='509e01ca12838e0200000212', 177 | email='gsibble@gmail.com', 178 | name='gsibble@gmail.com', 179 | ), 180 | recipient_type='coinbase', 181 | request=True, 182 | sender=CoinbaseContact( 183 | id='514e4c1c802e1bef9800001e', 184 | email='george@atlasr.com', 185 | name='george@atlasr.com', 186 | ), 187 | status=CoinbaseTransaction.Status.pending, 188 | ), 189 | CoinbaseTransaction( 190 | amount=CoinbaseAmount('-1', 'BTC'), 191 | created_at=datetime(2013, 3, 21, 17, 2, 57, 192 | tzinfo=tzoffset(None, -25200)), 193 | hash='42dd65a18dbea0779f32021663e60b1fab8ee0f859db7172a078d4528e01c6c8', 194 | id='514b9fb1b8377ee36500000d', 195 | notes='You gave me this a while ago.', 196 | idem='jkl', 197 | recipient=CoinbaseContact( 198 | id='4efec8d7bedd320001000003', 199 | email='brian@coinbase.com', 200 | name='Brian Armstrong', 201 | ), 202 | recipient_type='coinbase', 203 | recipient_address='brian@coinbase.com', 204 | request=False, 205 | sender=CoinbaseContact( 206 | id='509e01ca12838e0200000212', 207 | email='gsibble@gmail.com', 208 | name='gsibble@gmail.com', 209 | ), 210 | status=CoinbaseTransaction.Status.complete, 211 | ), 212 | CoinbaseTransaction( 213 | amount=CoinbaseAmount('1', 'BTC'), 214 | created_at=datetime(2012, 11, 9, 23, 27, 7, 215 | tzinfo=tzoffset(None, -28800)), 216 | hash='ac9b0ffbe36dbe12c5ca047a5bdf9cadca3c9b89b74751dff83b3ac863ccc0b3', 217 | id='509e01cb12838e0200000224', 218 | notes='', 219 | idem='xyz', 220 | recipient=CoinbaseContact( 221 | id='509e01ca12838e0200000212', 222 | email='gsibble@gmail.com', 223 | name='gsibble@gmail.com', 224 | ), 225 | recipient_type='coinbase', 226 | recipient_address='gsibble@gmail.com', 227 | request=False, 228 | sender=CoinbaseContact( 229 | id='4efec8d7bedd320001000003', 230 | email='brian@coinbase.com', 231 | name='Brian Armstrong', 232 | ), 233 | status=CoinbaseTransaction.Status.complete, 234 | ), 235 | ] 236 | -------------------------------------------------------------------------------- /coinbase/tests/test_transfer.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from datetime import datetime 5 | from dateutil.tz import tzoffset 6 | 7 | from coinbase import CoinbaseAmount, CoinbaseTransfer 8 | from . import account_setup 9 | from .http_mocking import * 10 | 11 | 12 | @with_http_mocking 13 | class TransferTest(TestCase): 14 | """ 15 | The example from the API doc 16 | https://coinbase.com/api/doc/1.0/transfers/index.html 17 | """ 18 | 19 | def setUp(self): 20 | mock_http('GET https://coinbase.com/api/v1/transfers', 21 | response_body) 22 | 23 | def test_transfers_with_oauth(self): 24 | account = account_setup.with_oauth() 25 | this(account.transfers()).should.equal([expected_transfer]) 26 | 27 | 28 | response_body = """ 29 | { 30 | "transfers": [ 31 | { 32 | "transfer": { 33 | "type": "Buy", 34 | "code": "QPCUCZHR", 35 | "created_at": "2013-02-27T23:28:18-08:00", 36 | "fees": { 37 | "coinbase": { 38 | "cents": 14, 39 | "currency_iso": "USD" 40 | }, 41 | "bank": { 42 | "cents": 15, 43 | "currency_iso": "USD" 44 | } 45 | }, 46 | "payout_date": "2013-03-05T18:00:00-08:00", 47 | "transaction_id": "5011f33df8182b142400000e", 48 | "status": "Pending", 49 | "btc": { 50 | "amount": "1.00000000", 51 | "currency": "BTC" 52 | }, 53 | "subtotal": { 54 | "amount": "13.55", 55 | "currency": "USD" 56 | }, 57 | "total": { 58 | "amount": "13.84", 59 | "currency": "USD" 60 | }, 61 | "description": "Paid for with $13.84 from Test xxxxx3111." 62 | } 63 | } 64 | ], 65 | "total_count": 1, 66 | "num_pages": 1, 67 | "current_page": 1 68 | } 69 | """ 70 | 71 | 72 | expected_transfer = CoinbaseTransfer( 73 | type='Buy', 74 | code='QPCUCZHR', 75 | created_at=datetime(2013, 2, 27, 23, 28, 18, 76 | tzinfo=tzoffset(None, -28800)), 77 | fees_coinbase=CoinbaseAmount('.14', 'USD'), 78 | fees_bank=CoinbaseAmount('.15', 'USD'), 79 | payout_date=datetime(2013, 3, 5, 18, 0, 0, 80 | tzinfo=tzoffset(None, -28800)), 81 | transaction_id='5011f33df8182b142400000e', 82 | status=CoinbaseTransfer.Status.pending, 83 | btc_amount=CoinbaseAmount('1', 'BTC'), 84 | subtotal_amount=CoinbaseAmount('13.55', 'USD'), 85 | total_amount=CoinbaseAmount('13.84', 'USD'), 86 | description='Paid for with $13.84 from Test xxxxx3111.', 87 | ) 88 | -------------------------------------------------------------------------------- /coinbase/tests/test_user.py: -------------------------------------------------------------------------------- 1 | from sure import this 2 | from unittest import TestCase 3 | 4 | from coinbase import CoinbaseAmount 5 | from . import account_setup 6 | from .http_mocking import * 7 | 8 | 9 | @with_http_mocking 10 | class UserTest(TestCase): 11 | 12 | def setUp(self): 13 | mock_http('GET https://coinbase.com/api/v1/users', response_body) 14 | 15 | def test_getting_user_details_with_oauth(self): 16 | account = account_setup.with_oauth() 17 | user = account.get_user_details() 18 | this(user.id).should.equal("509f01da12837e0201100212") 19 | this(user.balance).should.equal(CoinbaseAmount('1225.86084181', 'BTC')) 20 | 21 | 22 | response_body = """ 23 | { 24 | "users": [ 25 | { 26 | "user": { 27 | "balance": { 28 | "amount": "1225.86084181", 29 | "currency": "BTC" 30 | }, 31 | "buy_level": 1, 32 | "buy_limit": { 33 | "amount": "10.00000000", 34 | "currency": "BTC" 35 | }, 36 | "email": "gsibble@gmail.com", 37 | "id": "509f01da12837e0201100212", 38 | "name": "New User", 39 | "native_currency": "USD", 40 | "sell_level": 1, 41 | "sell_limit": { 42 | "amount": "50.00000000", 43 | "currency": "BTC" 44 | }, 45 | "time_zone": "Pacific Time (US & Canada)" 46 | } 47 | } 48 | ] 49 | } 50 | """ 51 | -------------------------------------------------------------------------------- /coinbase_oauth2/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'gsibble' 2 | 3 | from flask import Flask, render_template, request, make_response 4 | 5 | from oauth2client.client import OAuth2WebServerFlow 6 | import httplib2 7 | 8 | APP = Flask(__name__) 9 | APP.debug = True 10 | 11 | import logging 12 | logging.basicConfig() 13 | 14 | from secrets import CALLBACK_URL, CLIENT_ID, CLIENT_SECRET 15 | 16 | coinbase_client = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, 'all', redirect_uri='http://www.paywithair.com/consumer_auth', auth_uri='https://www.coinbase.com/oauth/authorize', token_uri='https://www.coinbase.com/oauth/token') 17 | 18 | @APP.route('/') 19 | def register_me(): 20 | 21 | auth_url = coinbase_client.step1_get_authorize_url() 22 | 23 | return render_template('register.jinja2', auth_url=auth_url) 24 | 25 | @APP.route('/consumer_auth') 26 | def receive_token(): 27 | 28 | oauth_code = request.args['code'] 29 | 30 | print(oauth_code) 31 | 32 | http = httplib2.Http(ca_certs='/etc/ssl/certs/ca-certificates.crt') 33 | 34 | token = coinbase_client.step2_exchange(oauth_code, http=http) 35 | 36 | return make_response(token.to_json()) 37 | 38 | if __name__ == '__main__': 39 | APP.run(host='0.0.0.0', port=80) 40 | -------------------------------------------------------------------------------- /coinbase_oauth2/ca_certs.txt: -------------------------------------------------------------------------------- 1 | '''-----BEGIN CERTIFICATE----- 2 | MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 5 | ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL 6 | MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 7 | LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug 8 | RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm 9 | +9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW 10 | PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM 11 | xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB 12 | Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 13 | hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg 14 | EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF 15 | MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA 16 | FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec 17 | nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z 18 | eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF 19 | hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 20 | Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe 21 | vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep 22 | +OkuE6N36B9K 23 | -----END CERTIFICATE----- 24 | -----BEGIN CERTIFICATE----- 25 | MIIGsDCCBZigAwIBAgIQDalUvE9VoP3NjE77+zlkbTANBgkqhkiG9w0BAQUFADBm 26 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 27 | d3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 28 | ZSBDQS0zMB4XDTEyMTIxMDAwMDAwMFoXDTE0MDExNDEyMDAwMFowbDELMAkGA1UE 29 | BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lz 30 | Y28xFzAVBgNVBAoTDkNvaW5iYXNlLCBJbmMuMRcwFQYDVQQDDA4qLmNvaW5iYXNl 31 | LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALH3Y1drFDDilWgH 32 | eh07CxU2Jzg1cBAiDrlYBYf/c8ZedDvcFmPgdj3kGNTh4tSK1+YI+4iqOwre9r1r 33 | uxHu6IfQjM2POoRSFBEdzWCRayPiQ6M1MPIFi4RL26VM74kryXUbp0YB0Xb0DOac 34 | BGfgX11bRhxqZOdqIM+KajdAUP7FZ3RpDlJ1ALUiv8dHxtXweQz229qpp7iiBVnv 35 | sk/o9KOCBhKeHIkGXOoiDsEdfSeF0+j6Q2AO2fwqLQdkyJ1rPckT2bDUsLuJ6dJa 36 | woxg0yNF5tAb+Rwm4eMmBZ8PBRQrtF2w0SsADViMITa5WChK0F46D+YEh7WpRy11 37 | As4l39sCAwEAAaOCA1IwggNOMB8GA1UdIwQYMBaAFFDqc4nbKfsQj57lASDU3nmZ 38 | SIP3MB0GA1UdDgQWBBTM2gzeBxs4iIF7zi7DTLI6PZT09jAnBgNVHREEIDAegg4q 39 | LmNvaW5iYXNlLmNvbYIMY29pbmJhc2UuY29tMA4GA1UdDwEB/wQEAwIFoDAdBgNV 40 | HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwYQYDVR0fBFowWDAqoCigJoYkaHR0 41 | cDovL2NybDMuZGlnaWNlcnQuY29tL2NhMy1nMTcuY3JsMCqgKKAmhiRodHRwOi8v 42 | Y3JsNC5kaWdpY2VydC5jb20vY2EzLWcxNy5jcmwwggHEBgNVHSAEggG7MIIBtzCC 43 | AbMGCWCGSAGG/WwBATCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNl 44 | cnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCCAVYe 45 | ggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYA 46 | aQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQA 47 | YQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8A 48 | QwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQA 49 | eQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAA 50 | bABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAA 51 | bwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4A 52 | YwBlAC4wewYIKwYBBQUHAQEEbzBtMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k 53 | aWdpY2VydC5jb20wRQYIKwYBBQUHMAKGOWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0 54 | LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VDQS0zLmNydDAMBgNVHRMBAf8EAjAA 55 | MA0GCSqGSIb3DQEBBQUAA4IBAQAKAhkujOlGlF3A4QbuH1NlJzblgThWPttm1/0M 56 | 29E5ea4IYkV/dvAqH8GDm0EnVI06PC5aGTMw7T1n059x8wsNAAIgOkvw4Vt4bN/s 57 | v4znVYeuJKqAGrZ9gTeJklXEKhdC7oPR3cIAVaHy2pX6j9cErX7A3/hWRurWyM6P 58 | jwi/qmEhy/mkp5A4Y7+7e1/AKv4RV54FjLP4TEC3pyhHt/d/1VrwBCc9RbUZhooX 59 | sbMozCd39CrrS68BuQb5TIUuRG9Mfay1b0ppZlsDMnB8sW+scAra64uRtAcv8PCn 60 | A4kb6CJ2tDYw2p82iV1Uoxb8nHHrFlyFv7RoAQKLgkhbMZH2 61 | -----END CERTIFICATE-----''' -------------------------------------------------------------------------------- /coinbase_oauth2/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.9 2 | Jinja2==2.6 3 | Pygments==1.6 4 | Werkzeug==0.8.3 5 | httplib2==0.8 6 | oauth2client==1.1 7 | requests==2.20.0 8 | wsgiref==0.1.2 9 | -------------------------------------------------------------------------------- /coinbase_oauth2/templates/register.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |