├── .gitignore ├── Bitcoin_Wallet_Mixin.py ├── Bitcoin_Wallet_Mixin_consoleGUI.py ├── LICENSE ├── README-cn.md ├── README.md ├── exincore_api.py ├── mixin_api.py ├── mixin_asset_id_collection.py ├── mixin_config.py ├── requirements.txt ├── screen_shot_wallet.png ├── screen_trade_exin.png ├── screen_wallet_open.png └── wallet_api.py /.gitignore: -------------------------------------------------------------------------------- 1 | # vim 2 | *.swp 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | #virtualenv 108 | bin 109 | -------------------------------------------------------------------------------- /Bitcoin_Wallet_Mixin.py: -------------------------------------------------------------------------------- 1 | from mixin_api import MIXIN_API 2 | import uuid 3 | import mixin_config 4 | import json 5 | import csv 6 | import time 7 | import umsgpack 8 | import base64 9 | import getpass 10 | import requests 11 | import os 12 | import wallet_api 13 | import exincore_api 14 | from datetime import datetime 15 | import pytz 16 | import time 17 | import mixin_asset_id_collection 18 | 19 | PIN = "945689"; 20 | PIN2 = "845689"; 21 | MASTER_ID = "37222956"; 22 | MASTER_UUID = "28ee416a-0eaa-4133-bc79-9676909b7b4e"; 23 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 24 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 25 | USDT_ASSET_ID = "815b0b1a-2764-3736-8faa-42d694fa620a" 26 | ETC_ASSET_ID = "2204c1ee-0ea2-4add-bb9a-b3719cfff93a"; 27 | XRP_ASSET_ID = "23dfb5a5-5d7b-48b6-905f-3970e3176e27"; 28 | XEM_ASSET_ID = "27921032-f73e-434e-955f-43d55672ee31" 29 | ETH_ASSET_ID = "43d61dcd-e413-450d-80b8-101d5e903357"; 30 | DASH_ASSET_ID = "6472e7e3-75fd-48b6-b1dc-28d294ee1476"; 31 | DOGE_ASSET_ID = "6770a1e5-6086-44d5-b60f-545f9d9e8ffd" 32 | LTC_ASSET_ID = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8"; 33 | SIA_ASSET_ID = "990c4c29-57e9-48f6-9819-7d986ea44985"; 34 | ZEN_ASSET_ID = "a2c5d22b-62a2-4c13-b3f0-013290dbac60" 35 | ZEC_ASSET_ID = "c996abc9-d94e-4494-b1cf-2a3fd3ac5714" 36 | BCH_ASSET_ID = "fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0" 37 | 38 | MIXIN_DEFAULT_CHAIN_GROUP = [BTC_ASSET_ID, EOS_ASSET_ID, USDT_ASSET_ID, ETC_ASSET_ID, XRP_ASSET_ID, XEM_ASSET_ID, ETH_ASSET_ID, DASH_ASSET_ID, DOGE_ASSET_ID, LTC_ASSET_ID, SIA_ASSET_ID, ZEN_ASSET_ID, ZEC_ASSET_ID, BCH_ASSET_ID] 39 | 40 | 41 | 42 | BTC_WALLET_ADDR = "14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C"; 43 | AMOUNT = "0.001"; 44 | 45 | # // Mixin Network support cryptocurrencies (2019-02-19) 46 | # // |EOS|6cfe566e-4aad-470b-8c9a-2fd35b49c68d 47 | # // |CNB|965e5c6e-434c-3fa9-b780-c50f43cd955c 48 | # // |BTC|c6d0c728-2624-429b-8e0d-d9d19b6592fa 49 | # // |ETC|2204c1ee-0ea2-4add-bb9a-b3719cfff93a 50 | # // |XRP|23dfb5a5-5d7b-48b6-905f-3970e3176e27 51 | # // |XEM|27921032-f73e-434e-955f-43d55672ee31 52 | # // |ETH|43d61dcd-e413-450d-80b8-101d5e903357 53 | # // |DASH|6472e7e3-75fd-48b6-b1dc-28d294ee1476 54 | # // |DOGE|6770a1e5-6086-44d5-b60f-545f9d9e8ffd 55 | # // |LTC|76c802a2-7c88-447f-a93e-c29c9e5dd9c8 56 | # // |SC|990c4c29-57e9-48f6-9819-7d986ea44985 57 | # // |ZEN|a2c5d22b-62a2-4c13-b3f0-013290dbac60 58 | # // |ZEC|c996abc9-d94e-4494-b1cf-2a3fd3ac5714 59 | # // |BCH|fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0 60 | 61 | def generateMixinAPI(private_key,pin_token,session_id,user_id,pin,client_secret): 62 | mixin_config.private_key = private_key 63 | mixin_config.pin_token = pin_token 64 | mixin_config.pay_session_id = session_id 65 | mixin_config.client_id = user_id 66 | mixin_config.client_secret = client_secret 67 | mixin_config.pay_pin = pin 68 | return MIXIN_API(mixin_config) 69 | 70 | def strPresent_of_depositAddress_from(AssetData): 71 | 72 | result_string = "" 73 | for eachSeg in AssetData.deposit_address(): 74 | result_string += (" %s: %s | "%(eachSeg["title"], eachSeg["value"])) 75 | return result_string 76 | def strPresent_of_asset_withdrawaddress(thisAddress, asset_id, prefix = " "* 4): 77 | if(type(thisAddress) is wallet_api.Address): 78 | address_id = thisAddress.address_id 79 | address_pubkey = thisAddress.public_key 80 | address_label = thisAddress.label 81 | address_accountname = thisAddress.account_name 82 | address_accounttag = thisAddress.account_tag 83 | address_fee = thisAddress.fee 84 | address_dust = thisAddress.dust 85 | 86 | else: 87 | address_id = thisAddress.get("address_id") 88 | address_pubkey = thisAddress.get("public_key") 89 | address_label = thisAddress.get("label") 90 | address_accountname = thisAddress.get("account_name") 91 | address_accounttag = thisAddress.get("account_tag") 92 | address_fee = thisAddress.get("fee") 93 | address_dust = thisAddress.get("dust") 94 | Address = "" 95 | if address_label != "": 96 | Address += prefix + "tag : %s\n"%address_label 97 | Address += prefix + "id : %s\n"%address_id 98 | 99 | if address_pubkey!= "": 100 | Address += prefix + "Address : %s\n"%address_pubkey 101 | 102 | if address_accountname!= "": 103 | Address += prefix + "Account name : %s\n"%address_accountname 104 | 105 | if address_accounttag!= "": 106 | Address += prefix + "Account memo : %s\n"%address_accounttag 107 | Address += prefix + "fee : %s\n"%address_fee 108 | Address += prefix + "dust : %s\n"%address_dust 109 | return Address 110 | 111 | def strPresent_of_btc_withdrawaddress(thisAddress, prefix= " " * 8): 112 | return strPresent_of_asset_withdrawaddress(thisAddress, BTC_ASSET_ID, prefix) 113 | 114 | 115 | def loadSnapshots(UserInstance, timestamp, asset_id = "", limit = 500): 116 | USDT_Snapshots_result_of_account = UserInstance.my_snapshots_after(timestamp, asset_id , limit) 117 | for singleSnapShot in USDT_Snapshots_result_of_account: 118 | is_exin = exincore_api.about_me(singleSnapShot) 119 | if(is_exin): 120 | print(is_exin) 121 | 122 | mixinApiBotInstance = MIXIN_API(mixin_config) 123 | 124 | padding = 70 125 | PromptMsg = "Read first user from local file new_users.csv : loaduser\n" 126 | PromptMsg += "Create account and append to new_users.csv : create\n" 127 | PromptMsg += "Exit : q\n" 128 | 129 | PromptMsg_nolocalfile = "Create account and append to new_users.csv : create\n" 130 | PromptMsg_nolocalfile += "Exit : q\n" 131 | loadedPromptMsg = "Read account asset non-zero balance".ljust(padding) + ": balance\n" 132 | loadedPromptMsg += "deposit asset ".ljust(padding) + ": deposit\n" 133 | loadedPromptMsg += "send asset ".ljust(padding) + ": send\n" 134 | loadedPromptMsg += "Read transaction of my account".ljust(padding) + ": searchsnapshots\n" 135 | loadedPromptMsg += "Read transaction of my account".ljust(padding) + ": searchsnapshot\n" 136 | loadedPromptMsg += "Instant exchange BTC, USDT ... : ExinCore ".ljust(padding) + ": instanttrade\n" 137 | loadedPromptMsg += "Ocean.one protocol exchange : ocean.one".ljust(padding) + ": ocean\n" 138 | 139 | loadedPromptMsg += "List account withdraw address".ljust(padding) + ": manageassets\n" 140 | loadedPromptMsg += "verify pin".ljust(padding) + ": verifypin\n" 141 | loadedPromptMsg += "updatepin".ljust(padding) + ": updatepin\n" 142 | loadedPromptMsg += "switch account".ljust(padding) + ": switch\n" 143 | 144 | global mixinWalletInstance 145 | global mixin_account_name 146 | mixinWalletInstance = None 147 | mixin_account_name = None 148 | while ( 1 > 0 ): 149 | if (mixinWalletInstance!= None): 150 | cmd = input(loadedPromptMsg) 151 | else: 152 | if os.path.isfile('new_users.csv'): 153 | cmd = input(PromptMsg) 154 | else: 155 | cmd = input(PromptMsg_nolocalfile) 156 | if (cmd == 'q' ): 157 | exit() 158 | if (cmd == 'switch'): 159 | mixinApiNewUserInstance = None 160 | 161 | if ( cmd == 'loaduser'): 162 | wallet_records = wallet_api.load_wallet_csv_file('new_users.csv') 163 | i = 0 164 | for each_wallet in wallet_records: 165 | print("%d: user_id-> %s"%(i, each_wallet.userid)) 166 | i = i + 1 167 | user_index = input(("%d account in your file, load which account: "%len(wallet_records))) 168 | 169 | selected_wallet = wallet_records[int(user_index)] 170 | mixinWalletInstance = selected_wallet 171 | if ( cmd == 'balance' ): 172 | balance_result = mixinWalletInstance.get_balance() 173 | if balance_result.is_success: 174 | all_assets = balance_result.data 175 | asset_id_groups_in_myassets = [] 176 | for eachAsset in all_assets: 177 | asset_id_groups_in_myassets.append(eachAsset.asset_id) 178 | 179 | print("Your asset balance is\n===========") 180 | 181 | for eachAsset in all_assets: 182 | print("%s: %s" %(eachAsset.name.ljust(15), eachAsset.balance)) 183 | print(time.time()) 184 | 185 | for eachAssetID in MIXIN_DEFAULT_CHAIN_GROUP: 186 | if ( not (eachAssetID in asset_id_groups_in_myassets)): 187 | eachAsset = mixinWalletInstance.get_singleasset_balance(eachAssetID) 188 | if eachAsset.is_success: 189 | print("%s: %s" %(eachAsset.data.name.ljust(15), eachAsset.data.balance)) 190 | print("===========") 191 | if (cmd == "deposit"): 192 | 193 | balance_result = mixinWalletInstance.get_balance() 194 | if (balance_result.is_success): 195 | all_assets = balance_result.data 196 | asset_id_groups_in_myassets = [] 197 | print("Your asset deposit address \n===========") 198 | 199 | for eachAsset in all_assets: 200 | print("%s: %s" %(eachAsset.name.ljust(15), strPresent_of_depositAddress_from(eachAsset))) 201 | asset_id_groups_in_myassets.append(eachAsset.asset_id) 202 | for eachAssetID in MIXIN_DEFAULT_CHAIN_GROUP: 203 | if ( not (eachAssetID in asset_id_groups_in_myassets)): 204 | this_asset = mixinWalletInstance.get_singleasset_balance(eachAssetID) 205 | if this_asset.is_success: 206 | print("%s: %s" %(this_asset.data.name.ljust(15), strPresent_of_depositAddress_from(this_asset.data))) 207 | print("===========") 208 | 209 | if (cmd == "send"): 210 | 211 | balance_result = mixinWalletInstance.get_balance() 212 | print("select an asset to send" + "===========") 213 | 214 | if(balance_result.is_success): 215 | all_asset = balance_result.data 216 | none_zero_asset = [] 217 | for eachAsset in all_asset: 218 | if (float(eachAsset.balance)) > 0: 219 | none_zero_asset.append(eachAsset) 220 | i = 0 221 | for eachNoneZero in none_zero_asset: 222 | print("Send %s: %d" %(eachNoneZero.name.ljust(15), i)) 223 | i = i + 1 224 | if (i > 0 and i <= len(none_zero_asset)): 225 | selected_index = int(input("index number:")) 226 | if selected_index < len(none_zero_asset): 227 | selected_asset = none_zero_asset[selected_index] 228 | print("send to mixin network uuid: 0") 229 | print("send to asset address : 1") 230 | address_type = input("your selection:") 231 | if (address_type == "0"): 232 | destination_uuid = input("destination uuid:") 233 | amount_tosend = input("quantity(%s remain):"%selected_asset.balance) 234 | memo_input = input("memo:") 235 | asset_pin_input = getpass.getpass("pin code:") 236 | this_uuid = str(uuid.uuid1()) 237 | user_confirm = input("Type YES and press enter key to confirm: send %s %s to %s , memo:%s, trace id: %s:"%(amount_tosend, selected_asset.name, destination_uuid, memo_input, this_uuid)) 238 | if (user_confirm == "YES"): 239 | transfer_result = mixinWalletInstance.transfer_to(destination_uuid, selected_asset.asset_id, amount_tosend, memo_input, this_uuid, asset_pin_input) 240 | if(transfer_result.is_success): 241 | print(str(transfer_result.data)) 242 | if (address_type == "1"): 243 | withdraw_addresses_result = mixinWalletInstance.get_asset_withdrawl_addresses(selected_asset.asset_id) 244 | if withdraw_addresses_result.is_success: 245 | withdraw_addresses = withdraw_addresses_result.data 246 | else: 247 | withdraw_addresses = [] 248 | i = 0 249 | for eachAddress in withdraw_addresses: 250 | btcAddress = strPresent_of_btc_withdrawaddress(eachAddress) 251 | print("%s:\n%s"%(str(i).ljust(10, '-'), btcAddress)) 252 | i = i + 1 253 | user_choice = int(input("your choice:")) 254 | 255 | if (user_choice < len(withdraw_addresses)): 256 | selected_withdraw_address = withdraw_addresses[user_choice] 257 | withdraw_amount = input("amount to withdraw:") 258 | address_id = selected_withdraw_address.address_id 259 | address_pubkey = selected_withdraw_address.public_key 260 | withdraw_asset_id = selected_withdraw_address.asset_id 261 | 262 | address_selected = "%s"%(strPresent_of_asset_withdrawaddress(selected_withdraw_address, withdraw_asset_id)) 263 | confirm = input("Type YES and press enter key to withdraw " + withdraw_amount + " " + selected_asset.name + " to \n" + address_selected + ":") 264 | if (confirm == "YES"): 265 | this_uuid = str(uuid.uuid1()) 266 | asset_pin = getpass.getpass("pin:") 267 | asset_withdraw_result = mixinWalletInstance.withdraw_asset_to(address_id, withdraw_amount, "withdraw2"+address_pubkey, this_uuid, asset_pin) 268 | if(asset_withdraw_result.is_success): 269 | print("Your withdraw is successful , snapshot id: %s"%asset_withdraw_result.data.snapshot_id) 270 | else: 271 | print("Your withdraw is failed due to %s"%asset_withdraw_result) 272 | 273 | else: 274 | print("no available asset to send") 275 | 276 | if ( cmd == 'searchsnapshots'): 277 | timestamp = input("input timestamp, history after the time will be searched:") 278 | USDT_Snapshots_result_of_account = mixinWalletInstance.my_snapshots_after(timestamp, asset_id = "", limit = 500, retry = 100) 279 | for singleSnapShot in USDT_Snapshots_result_of_account: 280 | print(singleSnapShot) 281 | print(exincore_api.about_me(singleSnapShot)) 282 | 283 | if ( cmd == 'searchsnapshot'): 284 | timestamp = datetime.fromtimestamp(time.time() - 60*60*3, pytz.utc).isoformat() 285 | USDT_Snapshots_result_of_account = mixinWalletInstance.my_snapshots_after(timestamp, asset_id = mixin_asset_id_collection.USDT_ASSET_ID, limit = 500, retry = 100) 286 | for singleSnapShot in USDT_Snapshots_result_of_account: 287 | print(singleSnapShot) 288 | print(exincore_api.about_me(singleSnapShot)) 289 | 290 | if ( cmd == 'instanttrade'): 291 | # Pack memo 292 | 293 | exin_assets_price = exincore_api.fetchExinPrice(USDT_ASSET_ID) 294 | i = 0 295 | for each_asset_price in exin_assets_price: 296 | print(str(i).ljust(2) + ":" + str(each_asset_price)) 297 | i = i + 1 298 | 299 | if (len(exin_assets_price) > 0): 300 | 301 | user_select_coin = int(input("which index:")) 302 | if (user_select_coin < len(exin_assets_price)): 303 | selected_coin = exin_assets_price[user_select_coin] 304 | buy_or_sell = input("buy or sell %s:"%selected_coin.exchange_asset_symbol) 305 | 306 | if buy_or_sell == "sell": 307 | target_asset_id = USDT_ASSET_ID 308 | source_asset_id = selected_coin.echange_asset 309 | if buy_or_sell == "buy": 310 | target_asset_id = selected_coin.echange_asset 311 | source_asset_id = USDT_ASSET_ID 312 | 313 | if buy_or_sell == "sell" or buy_or_sell == "buy": 314 | 315 | print(selected_coin.debug_str()) 316 | print("fetching latest price" + selected_coin.echange_asset) 317 | asset_price_result = exincore_api.fetchExinPrice(source_asset_id, target_asset_id) 318 | asset_price = asset_price_result[0] 319 | minimum_pay_base_asset = asset_price.minimum_amount 320 | maximum_pay_base_asset = asset_price.maximum_amount 321 | price_base_asset = asset_price.price 322 | base_sym = asset_price.base_asset_symbol 323 | target_sym = asset_price.exchange_asset_symbol 324 | 325 | memo_for_exin = exincore_api.gen_memo_ExinBuy(target_asset_id) 326 | 327 | single_balance = mixinWalletInstance.get_singleasset_balance(source_asset_id) 328 | if single_balance.is_success: 329 | balance_base_asset = single_balance.data.balance 330 | amount_to_pay = input("how much you want to pay, %s %s in your balance:"%(balance_base_asset, base_sym)) 331 | this_uuid = str(uuid.uuid1()) 332 | estimated_target_amount = str(float(amount_to_pay)/float(price_base_asset)) 333 | confirm_pay = input("Pay " + amount_to_pay + " " + base_sym + " to buy " + estimated_target_amount + " " + target_sym + " on ExinCore" + ", Type YES and press enter key to confirm") 334 | if ( confirm_pay == "YES" ): 335 | input_pin = getpass.getpass("pin code:") 336 | 337 | transfer_result = mixinWalletInstance.transfer_to(exincore_api.EXINCORE_UUID, source_asset_id, amount_to_pay, memo_for_exin, this_uuid, input_pin) 338 | if(transfer_result.is_success): 339 | print(transfer_result.data) 340 | snapShotID = transfer_result.data.snapshot_id 341 | print("Pay " + amount_to_pay + " " + base_sym + " to ExinCore to buy " + estimated_target_amount + target_sym + " by uuid:" + this_uuid + ", you can verify the result on https://mixin.one/snapshots/" + snapShotID) 342 | checkResult = input("Type YES and press enter key to check latest snapshot:") 343 | if (checkResult == "YES"): 344 | loadSnapshots(mixinWalletInstance, transfer_result.data.created_at, target_asset_id, 3) 345 | else: 346 | print(transfer_result) 347 | 348 | if ( cmd == 'create' ): 349 | thisAccountRSAKeyPair = wallet_api.RSAKey4Mixin() 350 | account_name = input("wallet name") 351 | print(thisAccountRSAKeyPair.session_key) 352 | body = { 353 | "session_secret": thisAccountRSAKeyPair.session_key, 354 | "full_name": account_name 355 | } 356 | token_from_freeweb = wallet_api.fetchTokenForCreateUser(body, "http://freemixinapptoken.myrual.me/token") 357 | temp_wallet_instance = wallet_api.WalletRecord("","","", "","") 358 | create_wallet_result = temp_wallet_instance.create_wallet(thisAccountRSAKeyPair.session_key, account_name, token_from_freeweb) 359 | if(create_wallet_result.is_success): 360 | print(str(create_wallet_result.data.user_id) + "is created") 361 | create_wallet_result.data.private_key = thisAccountRSAKeyPair.private_key 362 | wallet_api.append_wallet_into_csv_file(create_wallet_result.data, "new_users.csv") 363 | 364 | new_wallet = wallet_api.WalletRecord("",create_wallet_result.data.user_id, create_wallet_result.data.session_id, create_wallet_result.data.pin_token, create_wallet_result.data.private_key) 365 | defauled_pin = getpass.getpass("input pin:") 366 | create_pin_result = new_wallet.update_pin("", defauled_pin) 367 | if(create_pin_result.is_success): 368 | print("pin is created") 369 | 370 | 371 | # c6d0c728-2624-429b-8e0d-d9d19b6592fa 372 | if ( cmd == 'allmoney' ): 373 | balance_result = mixinWalletInstance.get_balance() 374 | if(balance_result.is_success): 375 | AssetsInfo = balance_result.data 376 | availableAssset = [] 377 | my_pin = getpass.getpass("pin:") 378 | for eachAssetInfo in AssetsInfo: 379 | if (eachAssetInfo.balance == "0"): 380 | continue 381 | if (float(eachAssetInfo.balance) > 0): 382 | availableAssset.append(eachAssetInfo) 383 | print("You have : " + eachAssetInfo.balance + eachAssetInfo.name) 384 | this_uuid = str(uuid.uuid1()) 385 | print("uuid is: " + this_uuid) 386 | confirm_pay= input("type YES to pay " + eachAssetInfo.balance+ " to MASTER:") 387 | if ( confirm_pay== "YES" ): 388 | transfer_result = mixinWalletInstance.transfer_to(MASTER_UUID, eachAssetInfo.asset_id, eachAssetInfo.balance, "", this_uuid, my_pin) 389 | if(transfer_result.is_success): 390 | print(transfer_result.data) 391 | 392 | if ( cmd == 'manageassets' ): 393 | balance_result = mixinWalletInstance.get_balance() 394 | if balance_result.is_success: 395 | all_asset = balance_result.data 396 | asset_id_groups_in_myassets = [] 397 | for eachAsset in all_asset: 398 | asset_id_groups_in_myassets.append(eachAsset.asset_id) 399 | print("Your asset is\n===========") 400 | 401 | i = 0 402 | for eachAsset in all_asset: 403 | print("%s: %d" %(eachAsset.name.ljust(15), i)) 404 | i += 1 405 | 406 | 407 | user_choice = int(input("which asset:")) 408 | if (user_choice < len(all_asset)): 409 | selected_asset = all_asset[user_choice] 410 | withdraw_addresses_result = mixinWalletInstance.get_asset_withdrawl_addresses(selected_asset.asset_id) 411 | if withdraw_addresses_result.is_success: 412 | withdraw_addresses = withdraw_addresses_result.data 413 | print("%s: Total %d withdraw address "%(selected_asset.name, len(withdraw_addresses))) 414 | i = 0 415 | for eachAddress in withdraw_addresses: 416 | btcAddress = strPresent_of_btc_withdrawaddress(eachAddress, " " * 8).ljust(100) 417 | print("%s: %d\n%s"%("Remove".ljust(40, '-') ,i, btcAddress)) 418 | i = i + 1 419 | print("%s: %d"%("Add new address".ljust(40, '-'), i)) 420 | user_choice = int(input("your choice:")) 421 | if (user_choice == (len(withdraw_addresses))): 422 | print("add new address") 423 | if (selected_asset.chain_id != EOS_ASSET_ID): 424 | deposit_address = input("address:") 425 | tag_content = input("write a tag:") 426 | Confirm = input("address %s with tag %s, Type YES and press enter key to confirm:"%(deposit_address, tag_content)) 427 | if (Confirm == "YES"): 428 | input_pin = getpass.getpass("pin:") 429 | add_withdraw_addresses_result = mixinWalletInstance.create_address(selected_asset.asset_id, deposit_address, tag_content, asset_pin = input_pin) 430 | if add_withdraw_addresses_result.is_success: 431 | address_id = add_withdraw_addresses_result.data.address_id 432 | print("Added :" + str(add_withdraw_addresses_result.data)) 433 | else: 434 | print("Failed to add deposit_address %s tag_content %s %s"%(deposit_address, tag_content, add_withdraw_addresses_result)) 435 | else: 436 | deposit_account = input("account_name:") 437 | deposit_memo = input("account_tag(Very important if you withdraw to exchange):") 438 | tag_content = input("Tag for the address:") 439 | Confirm = input("EOS account: %s, memo: %s, summary: %s. Type YES and press enter key to confirm:"%(deposit_account, deposit_memo, tag_content)) 440 | if (Confirm == "YES"): 441 | input_pin = getpass.getpass("pin:") 442 | add_withdraw_addresses_result = mixinWalletInstance.create_address(selected_asset.asset_id, "", tag_content, input_pin, deposit_account, deposit_memo) 443 | if add_withdraw_addresses_result.is_success: 444 | address_id = add_withdraw_addresses_result.data.address_id 445 | print("Added :" + str(add_withdraw_addresses_result.data)) 446 | else: 447 | print("Failed to add deposit_address %s tag_content %s %s"%(deposit_address, tag_content, add_withdraw_addresses_result)) 448 | 449 | 450 | elif (user_choice < len(withdraw_addresses)): 451 | tobe_delete_address = withdraw_addresses[user_choice] 452 | print("Following address will be removed\n%s"%strPresent_of_btc_withdrawaddress(tobe_delete_address, " " * 8)) 453 | remove_address_pin = getpass.getpass("asset pin code:") 454 | remove_address_confirm = input("Type YES and press enter key to confirm:") 455 | if (remove_address_confirm == "YES"): 456 | remove_address_result = mixinWalletInstance.remove_address(tobe_delete_address.address_id, remove_address_pin) 457 | print(remove_address_result) 458 | 459 | if ( cmd == 'verifypin' ): 460 | input_pin = getpass.getpass("input your account pin:") 461 | userInfo = mixinWalletInstance.verify_pin(input_pin) 462 | if(userInfo.is_success): 463 | print(userInfo.data) 464 | else: 465 | print(userInfo) 466 | 467 | if ( cmd == 'updatepin' ): 468 | 469 | oldPin = getpass.getpass("input old pin:") 470 | newPin = getpass.getpass("input new pin:") 471 | print(mixinWalletInstance.update_pin(oldPin, newPin)) 472 | -------------------------------------------------------------------------------- /Bitcoin_Wallet_Mixin_consoleGUI.py: -------------------------------------------------------------------------------- 1 | import urwid 2 | import wallet_api 3 | import pyperclip 4 | import os 5 | import exincore_api 6 | import mixin_asset_id_collection 7 | from datetime import datetime 8 | import pytz 9 | import time 10 | import mixin_asset_id_collection 11 | 12 | def menu_button(caption, callback): 13 | button = urwid.Button(caption) 14 | urwid.connect_signal(button, 'click', callback) 15 | return urwid.AttrMap(button, None, focus_map='reversed') 16 | def menu_button_withobj(caption, callback, ooo): 17 | button = urwid.Button(caption) 18 | urwid.connect_signal(button, 'click', callback, ooo) 19 | return urwid.AttrMap(button, None, focus_map='reversed') 20 | 21 | 22 | def sub_menu(caption, choices): 23 | contents = menu(caption, choices) 24 | def open_menu(button): 25 | return top.open_box(contents) 26 | return menu_button([caption, u'...'], open_menu) 27 | 28 | def menu(title, choices): 29 | body = [urwid.Text(title), urwid.Divider()] 30 | body.extend(choices) 31 | return urwid.ListBox(urwid.SimpleFocusListWalker(body)) 32 | def create_wallet_chosen(button): 33 | menu_buttons = [] 34 | 35 | wallet_name_field = urwid.Edit(u'wallet name(less than 512 characters):\n') 36 | menu_buttons.append(wallet_name_field) 37 | 38 | pin_code_field = urwid.Edit(u'input 6 digits pin:\n', mask=u"*") 39 | menu_buttons.append(pin_code_field) 40 | 41 | url_token_field = urwid.Edit(u'free token url:\n') 42 | url_token_field.set_edit_text("http://freemixinapptoken.myrual.me/token") 43 | menu_buttons.append(url_token_field) 44 | 45 | done = menu_button_withobj(u'Create wallet', create_wallet_confirm_chosen, (wallet_api.WalletRecord("","","", "",""), wallet_name_field, pin_code_field, url_token_field)) 46 | 47 | back = menu_button(u'Back', pop_current_menu) 48 | menu_buttons.append(done) 49 | menu_buttons.append(back) 50 | 51 | top.open_box(menu(u'Create a wallet', menu_buttons)) 52 | 53 | 54 | def verify_pin_chosen(button, wallet_obj): 55 | menu_buttons = [] 56 | 57 | exe_pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 58 | 59 | menu_buttons.append(exe_pin_code_field) 60 | done = menu_button_withobj(u'Verify', verify_pin_confirm_chosen, (wallet_obj, exe_pin_code_field)) 61 | 62 | back = menu_button(u'Back', pop_current_menu) 63 | menu_buttons.append(done) 64 | menu_buttons.append(back) 65 | 66 | top.open_box(menu(u'Verify pin', menu_buttons)) 67 | 68 | def update_pin_chosen(button, wallet_obj): 69 | menu_buttons = [] 70 | 71 | old_pin_code_field = urwid.Edit(u'old pin:\n', mask=u"*") 72 | new_pin_code_field = urwid.Edit(u'new pin:\n', mask=u"*") 73 | 74 | menu_buttons.append(old_pin_code_field) 75 | menu_buttons.append(new_pin_code_field) 76 | 77 | done = menu_button_withobj(u'Update', update_pin_confirm_chosen, (wallet_obj, old_pin_code_field, new_pin_code_field)) 78 | 79 | back = menu_button(u'Back', pop_current_menu) 80 | menu_buttons.append(done) 81 | menu_buttons.append(back) 82 | 83 | top.open_box(menu(u'Update pin', menu_buttons)) 84 | 85 | 86 | def withdraw_asset_chosen(button, wallet_asset_obj): 87 | wallet_obj = wallet_asset_obj[0] 88 | asset_obj = wallet_asset_obj[1] 89 | 90 | 91 | menu_buttons = [] 92 | 93 | withdraw_addresses_result = wallet_obj.get_asset_withdrawl_addresses(asset_obj.asset_id) 94 | if(withdraw_addresses_result.is_success): 95 | withdraw_addresses = withdraw_addresses_result.data 96 | else: 97 | withdraw_addresses = [] 98 | for each_withdraw_address in withdraw_addresses: 99 | select_to_detail = menu_button_withobj(each_withdraw_address.label, withdraw_asset_to_address_chosen, (wallet_obj, asset_obj, each_withdraw_address)) 100 | menu_buttons.append(select_to_detail) 101 | if (asset_obj.chain_id != mixin_asset_id_collection.EOS_ASSET_ID): 102 | add_new_address = menu_button_withobj(u'Add new', add_withdraw_address_bitcoin_style, wallet_asset_obj) 103 | else: 104 | add_new_address = menu_button_withobj(u'Add new', add_withdraw_address_eos_style, wallet_asset_obj) 105 | 106 | menu_buttons.append(add_new_address) 107 | back = menu_button(u'Back', pop_current_menu) 108 | menu_buttons.append(back) 109 | 110 | top.open_box(menu(u'Manage withdraw addresses for ' + asset_obj.name, menu_buttons)) 111 | 112 | 113 | def manageasset_chosen(button, wallet_asset_obj): 114 | wallet_obj = wallet_asset_obj[0] 115 | asset_obj = wallet_asset_obj[1] 116 | 117 | 118 | menu_buttons = [] 119 | 120 | withdraw_addresses_result = wallet_obj.get_asset_withdrawl_addresses(asset_obj.asset_id) 121 | if withdraw_addresses_result.is_success: 122 | withdraw_addresses = withdraw_addresses_result.data 123 | else: 124 | withdraw_addresses = [] 125 | for each_withdraw_address in withdraw_addresses: 126 | select_to_detail = menu_button_withobj(each_withdraw_address.label, show_withdraw_address_remove, (wallet_obj, each_withdraw_address)) 127 | menu_buttons.append(select_to_detail) 128 | 129 | 130 | #done = menu_button_withobj(u'Send', show_content, (wallet_obj, asset_obj, "12", "23", "memo", "pin")) 131 | 132 | if (asset_obj.chain_id != mixin_asset_id_collection.EOS_ASSET_ID): 133 | add_new_address = menu_button_withobj(u'Add new', add_withdraw_address_bitcoin_style, wallet_asset_obj) 134 | else: 135 | add_new_address = menu_button_withobj(u'Add new', add_withdraw_address_eos_style, wallet_asset_obj) 136 | back = menu_button(u'Back', pop_current_menu) 137 | 138 | menu_buttons.append(add_new_address) 139 | menu_buttons.append(back) 140 | 141 | top.open_box(menu(u'Manage withdraw addresses for ' + asset_obj.name, menu_buttons)) 142 | 143 | def show_content(button, wallet_asset_uuid_amount_pin_obj): 144 | wallet_obj = wallet_asset_uuid_amount_pin_obj[0] 145 | asset_obj = wallet_asset_uuid_amount_pin_obj[1] 146 | uuid_obj = wallet_asset_uuid_amount_pin_obj[2] 147 | amount_obj = wallet_asset_uuid_amount_pin_obj[3] 148 | memo_obj = wallet_asset_uuid_amount_pin_obj[4] 149 | pin_obj = wallet_asset_uuid_amount_pin_obj[5] 150 | this_uuid = "" 151 | 152 | response = urwid.Text([asset_obj.asset_id , uuid_obj.get_edit_text(), amount_obj.get_edit_text(), memo_obj.get_edit_text(), pin_obj.get_edit_text(), this_uuid]) 153 | 154 | done = menu_button(u'Ok', pop_current_menu) 155 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 156 | 157 | def add_withdraw_address_confirm_chosen(button, wallet_asset_uuid_amount_pin_obj): 158 | wallet_obj = wallet_asset_uuid_amount_pin_obj[0] 159 | asset_obj = wallet_asset_uuid_amount_pin_obj[1] 160 | deposit_address = wallet_asset_uuid_amount_pin_obj[2] 161 | tag_content = wallet_asset_uuid_amount_pin_obj[3] 162 | account_name_obj = wallet_asset_uuid_amount_pin_obj[4] 163 | account_tag_obj = wallet_asset_uuid_amount_pin_obj[5] 164 | pin_obj = wallet_asset_uuid_amount_pin_obj[6] 165 | #let wallet create uuid for us 166 | 167 | create_address_result = wallet_obj.create_address(asset_obj.asset_id, deposit_address.get_edit_text(), tag_content.get_edit_text(), asset_pin = pin_obj.get_edit_text()) 168 | 169 | if(create_address_result.is_success): 170 | response = urwid.Text(["the address :", deposit_address.get_edit_text(), " is added to your account with id:", create_address_result.data.address_id]) 171 | done = menu_button(u'Ok', pop_to_account_menu) 172 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 173 | else: 174 | response = urwid.Text(["Failed deposit_address %s tag_content %s %s"%(deposit_address.get_edit_text(), tag_content.get_edit_text(), create_address_result)]) 175 | done = menu_button(u'Ok', pop_to_account_menu) 176 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 177 | 178 | 179 | def withdraw_asset_to_address_confirm_chosen(button, wallet_address_amount_pin_obj): 180 | wallet_obj = wallet_address_amount_pin_obj[0] 181 | address_obj = wallet_address_amount_pin_obj[1] 182 | 183 | amount_obj = wallet_address_amount_pin_obj[2] 184 | memo_obj = wallet_address_amount_pin_obj[3] 185 | pin_obj = wallet_address_amount_pin_obj[4] 186 | this_uuid = "" 187 | 188 | withdraw_asset_to_address_result = wallet_obj.withdraw_asset_to(address_obj.address_id, amount_obj.get_edit_text(), memo_obj.get_edit_text(), this_uuid, pin_obj.get_edit_text()) 189 | if(withdraw_asset_to_address_result.is_success): 190 | #pop_to_account_menu(button) 191 | response = urwid.Text(["Your withdraw operation is successful, snapshot id is:", withdraw_asset_to_address_result.data.snapshot_id]) 192 | done = menu_button(u'Ok', pop_to_account_menu) 193 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 194 | 195 | else: 196 | response = urwid.Text(["Your withdraw asset operation is failed due to ", str(withdraw_asset_to_address_result)]) 197 | done = menu_button(u'Ok', pop_to_account_menu) 198 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 199 | 200 | 201 | 202 | def remove_withdraw_address_confirm_chosen(button, wallet_asset_uuid_amount_pin_obj): 203 | wallet_obj = wallet_asset_uuid_amount_pin_obj[0] 204 | address_obj = wallet_asset_uuid_amount_pin_obj[1] 205 | pin_obj = wallet_asset_uuid_amount_pin_obj[2] 206 | #let wallet create uuid for us 207 | this_uuid = "" 208 | 209 | remove_address_result = wallet_obj.remove_address(address_obj.address_id, pin_obj.get_edit_text()) 210 | response = urwid.Text([str(remove_address_result)]) 211 | done = menu_button(u'Ok', pop_to_account_menu) 212 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 213 | 214 | def create_wallet_confirm_chosen(button, wallet_name_pin_obj): 215 | wallet_obj = wallet_name_pin_obj[0] 216 | name_obj = wallet_name_pin_obj[1] 217 | pin_obj = wallet_name_pin_obj[2] 218 | url_obj = wallet_name_pin_obj[3] 219 | 220 | thisAccountRSAKeyPair = wallet_api.RSAKey4Mixin() 221 | body = { 222 | "session_secret": thisAccountRSAKeyPair.session_key, 223 | "full_name": name_obj.get_edit_text() 224 | } 225 | 226 | token2create = wallet_api.fetchTokenForCreateUser(body, url_obj.get_edit_text()) 227 | 228 | create_wallet_result = wallet_obj.create_wallet(thisAccountRSAKeyPair.session_key, name_obj.get_edit_text(), token2create) 229 | if(create_wallet_result.is_success): 230 | create_wallet_result.data.private_key = thisAccountRSAKeyPair.private_key 231 | wallet_api.append_wallet_into_csv_file(create_wallet_result.data, "new_users.csv") 232 | new_wallet = wallet_api.WalletRecord("",create_wallet_result.data.user_id, create_wallet_result.data.session_id, create_wallet_result.data.pin_token, create_wallet_result.data.private_key) 233 | 234 | create_pin_result = new_wallet.update_pin("", pin_obj.get_edit_text()) 235 | if(create_pin_result.is_success): 236 | response = urwid.Text(["Successfully created wallet with your pin"]) 237 | else: 238 | response = urwid.Text(["Wallet is created, pin is not created. Update pin please"]) 239 | done_button = menu_button(u'Ok', pop_current_and_more_menu) 240 | top.open_box(urwid.Filler(urwid.Pile([response, done_button]))) 241 | else: 242 | response = urwid.Text(["Failed to create account"]) 243 | done = menu_button(u'Ok', pop_current_and_more_menu) 244 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 245 | 246 | def verify_pin_confirm_chosen(button, wallet_asset_uuid_amount_pin_obj): 247 | wallet_obj = wallet_asset_uuid_amount_pin_obj[0] 248 | pin_obj = wallet_asset_uuid_amount_pin_obj[1] 249 | verify_result = wallet_obj.verify_pin(pin_obj.get_edit_text()) 250 | if(verify_result.is_success): 251 | verify_url = "Successfully verified pin" 252 | response = urwid.Text(["Successfully verified pin"]) 253 | done_button = menu_button(u'Ok', pop_to_account_menu) 254 | top.open_box(urwid.Filler(urwid.Pile([response, done_button]))) 255 | else: 256 | response = urwid.Text(["Failed to verify pin"]) 257 | done = menu_button(u'Ok', pop_current_and_more_menu) 258 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 259 | 260 | def update_pin_confirm_chosen(button, wallet_asset_uuid_amount_pin_obj): 261 | wallet_obj = wallet_asset_uuid_amount_pin_obj[0] 262 | old_pin_obj = wallet_asset_uuid_amount_pin_obj[1] 263 | new_pin_obj = wallet_asset_uuid_amount_pin_obj[2] 264 | update_result = wallet_obj.update_pin(old_pin_obj.get_edit_text(), new_pin_obj.get_edit_text()) 265 | if(update_result.is_success): 266 | response = urwid.Text(["Successfully update pin"]) 267 | done_button = menu_button(u'Ok', pop_to_account_menu) 268 | top.open_box(urwid.Filler(urwid.Pile([response, done_button]))) 269 | else: 270 | response = urwid.Text(["Failed to update pin"]) 271 | done = menu_button(u'Ok', pop_current_and_more_menu) 272 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 273 | 274 | def Pay2Exin_confirm_chosen(button, wallet_asset_uuid_amount_pin_obj): 275 | wallet_obj = wallet_asset_uuid_amount_pin_obj[0] 276 | asset_id_obj = wallet_asset_uuid_amount_pin_obj[1] 277 | uuid_exin_obj = wallet_asset_uuid_amount_pin_obj[2] 278 | amount_obj = wallet_asset_uuid_amount_pin_obj[3] 279 | memo_obj = wallet_asset_uuid_amount_pin_obj[4] 280 | pin_obj = wallet_asset_uuid_amount_pin_obj[5] 281 | this_uuid = "" 282 | 283 | transfer_result = wallet_obj.transfer_to(uuid_exin_obj, asset_id_obj, amount_obj, memo_obj, this_uuid, pin_obj.get_edit_text()) 284 | if(transfer_result.is_success): 285 | verify_url = "https://mixin.one/snapshots/" + transfer_result.data.snapshot_id 286 | response = urwid.Text([str(transfer_result.data), ". You can verify on browser:\n%s"%verify_url]) 287 | done_button = menu_button(u'Ok', pop_to_account_menu) 288 | copy_button = menu_button_withobj(("copy %s to clip board"%(verify_url)), copy_content_to_system_clip, verify_url) 289 | top.open_box(urwid.Filler(urwid.Pile([response, copy_button, done_button]))) 290 | else: 291 | response = urwid.Text([str(transfer_result)]) 292 | done = menu_button(u'Ok', pop_current_and_more_menu) 293 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 294 | 295 | 296 | def PayExin_input_pin_chosen(button, wallet_tradepair_amount): 297 | wallet_obj = wallet_tradepair_amount[0] 298 | tradeprice_obj = wallet_tradepair_amount[1] 299 | amount_obj = wallet_tradepair_amount[2] 300 | amount_to_pay = amount_obj.get_edit_text() 301 | price_base_asset = tradeprice_obj.price 302 | estimated_target_amount = str(float(amount_to_pay)/float(price_base_asset)) 303 | 304 | 305 | menu_buttons = [] 306 | 307 | memo_for_exin = exincore_api.gen_memo_ExinBuy(tradeprice_obj.echange_asset) 308 | 309 | exe_pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 310 | menu_buttons.append(exe_pin_code_field) 311 | done = menu_button_withobj(u'Pay ', Pay2Exin_confirm_chosen, (wallet_obj, tradeprice_obj.base_asset, exincore_api.EXINCORE_UUID, amount_to_pay, memo_for_exin, exe_pin_code_field)) 312 | 313 | 314 | back = menu_button(u'Back', pop_current_menu) 315 | menu_buttons.append(done) 316 | menu_buttons.append(back) 317 | 318 | top.open_box(menu(u'Pay ' + amount_to_pay + " " + tradeprice_obj.base_asset_symbol+ " to buy " + estimated_target_amount + " " + tradeprice_obj.exchange_asset_symbol + " on ExinCore" , menu_buttons)) 319 | 320 | def searchsnapshot_chosen(button, wallet_asset_offset_obj): 321 | 322 | wallet_obj = wallet_asset_offset_obj[0] 323 | asset_obj = wallet_asset_offset_obj[1] 324 | offset_obj = wallet_asset_offset_obj[2] 325 | if hasattr(asset_obj, 'asset_id'): 326 | input_asset_id = asset_obj.asset_id 327 | else: 328 | input_asset_id = "" 329 | USDT_Snapshots_result_of_account = wallet_obj.my_snapshots_after(offset_obj, asset_id = input_asset_id, limit = 500, retry = 100) 330 | balance_chosen_menu_buttons = [] 331 | 332 | for singleSnapShot in USDT_Snapshots_result_of_account: 333 | title = singleSnapShot.created_at.ljust(25) + " " + singleSnapShot.amount.ljust(10)+" "+ singleSnapShot.asset.name.ljust(20) + " opponent: " + singleSnapShot.opponent_id 334 | balance_chosen_menu_buttons.append(menu_button_withobj(title, snapshot_chosen, singleSnapShot)) 335 | 336 | balance_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 337 | top.open_box(menu(u'user id:' + wallet_obj.userid, balance_chosen_menu_buttons)) 338 | 339 | def searchsnapshot_recent_n_day_chosen(button, wallet_asset_offset_obj): 340 | wallet_obj = wallet_asset_offset_obj[0] 341 | asset_obj = wallet_asset_offset_obj[1] 342 | recent_obj = wallet_asset_offset_obj[2].get_edit_text() 343 | recent_n_day = float(recent_obj) 344 | offset_obj = datetime.fromtimestamp(time.time() - recent_n_day * 24 * 60 * 60, pytz.utc).isoformat() 345 | searchsnapshot_chosen(button, (wallet_obj, asset_obj, offset_obj)) 346 | def send_confirm_chosen(button, wallet_asset_uuid_amount_pin_obj): 347 | wallet_obj = wallet_asset_uuid_amount_pin_obj[0] 348 | asset_obj = wallet_asset_uuid_amount_pin_obj[1] 349 | uuid_obj = wallet_asset_uuid_amount_pin_obj[2] 350 | amount_obj = wallet_asset_uuid_amount_pin_obj[3] 351 | memo_obj = wallet_asset_uuid_amount_pin_obj[4] 352 | pin_obj = wallet_asset_uuid_amount_pin_obj[5] 353 | #let wallet create uuid for us 354 | this_uuid = "" 355 | 356 | transfer_result = wallet_obj.transfer_to(uuid_obj.get_edit_text(), asset_obj.asset_id, amount_obj.get_edit_text(), memo_obj.get_edit_text(), this_uuid, pin_obj.get_edit_text()) 357 | if(transfer_result.is_success): 358 | verify_url = "https://mixin.one/snapshots/" + transfer_result.data.snapshot_id 359 | response = urwid.Text([str(transfer_result.data), ". You can verify on browser:\n%s"%verify_url]) 360 | done_button = menu_button(u'Ok', pop_to_account_menu) 361 | copy_button = menu_button_withobj(("copy %s to clip board"%(verify_url)), copy_content_to_system_clip, verify_url) 362 | top.open_box(urwid.Filler(urwid.Pile([response, copy_button, done_button]))) 363 | else: 364 | response = urwid.Text([str(transfer_result)]) 365 | done = menu_button(u'Ok', pop_current_and_more_menu) 366 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 367 | def tradepair_sell_chosen(button, wallet_asset_obj): 368 | 369 | wallet_obj = wallet_asset_obj[0] 370 | asset_obj = wallet_asset_obj[1] 371 | 372 | menu_buttons = [] 373 | 374 | exe_destination_uuid_field = urwid.Edit(u'Destination uuid:\n') 375 | exe_amount_field = urwid.Edit(u'Amount:\n') 376 | exe_memo_field = urwid.Edit(u'Memo:\n') 377 | exe_pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 378 | 379 | 380 | menu_buttons.append(exe_destination_uuid_field) 381 | menu_buttons.append(exe_amount_field) 382 | menu_buttons.append(exe_memo_field) 383 | menu_buttons.append(exe_pin_code_field) 384 | done = menu_button_withobj(u'Send', send_confirm_chosen, (wallet_obj, asset_obj, exe_destination_uuid_field, exe_amount_field, exe_memo_field, exe_pin_code_field)) 385 | #done = menu_button_withobj(u'Send', show_content, (wallet_obj, asset_obj, "12", "23", "memo", "pin")) 386 | 387 | back = menu_button(u'Back', pop_current_menu) 388 | menu_buttons.append(done) 389 | menu_buttons.append(back) 390 | 391 | top.open_box(menu(u'Send ' + asset_obj.name, menu_buttons)) 392 | 393 | 394 | 395 | def tradepair_buy_chosen(button, wallet_asset_obj): 396 | 397 | wallet_obj = wallet_asset_obj[0] 398 | base_asset_id = wallet_asset_obj[1] 399 | target_asset_id = wallet_asset_obj[2] 400 | 401 | menu_buttons = [] 402 | asset_price_result = exincore_api.fetchExinPrice(base_asset_id, target_asset_id) 403 | #confirm price again 404 | this_trade_price = asset_price_result[0] 405 | minimum_pay_base_asset = this_trade_price.minimum_amount 406 | maximum_pay_base_asset = this_trade_price.maximum_amount 407 | price_base_asset = this_trade_price.price 408 | base_sym = this_trade_price.base_asset_symbol 409 | target_sym = this_trade_price.exchange_asset_symbol 410 | 411 | single_balance = wallet_obj.get_singleasset_balance(base_asset_id) 412 | if single_balance.is_success: 413 | pay_amount_field = urwid.Edit(u'Pay amount(%s in your balance):\n'%single_balance.data.balance) 414 | menu_buttons.append(pay_amount_field) 415 | done = menu_button_withobj(u'Input pin to buy '+ this_trade_price.exchange_asset_symbol, PayExin_input_pin_chosen, (wallet_obj, this_trade_price, pay_amount_field)) 416 | 417 | back = menu_button(u'Back', pop_current_menu) 418 | menu_buttons.append(done) 419 | menu_buttons.append(back) 420 | 421 | top.open_box(menu(this_trade_price.price + " " + this_trade_price.base_asset_symbol + " -> " + " 1 " + this_trade_price.exchange_asset_symbol, menu_buttons)) 422 | 423 | 424 | def recent_transfer_chosen(button, wallet_asset_obj): 425 | 426 | wallet_obj = wallet_asset_obj[0] 427 | asset_obj = wallet_asset_obj[1] 428 | 429 | menu_buttons = [] 430 | 431 | recent3hours = menu_button_withobj(u'Recent 3 hours', searchsnapshot_chosen, (wallet_obj, asset_obj, datetime.fromtimestamp(time.time() - 60*60*3, pytz.utc).isoformat())) 432 | recent30m = menu_button_withobj(u'Recent 30 minutes', searchsnapshot_chosen, (wallet_obj, asset_obj, datetime.fromtimestamp(time.time() - 30*60, pytz.utc).isoformat())) 433 | recent1m = menu_button_withobj(u'Recent 5 minutes', searchsnapshot_chosen, (wallet_obj, asset_obj, datetime.fromtimestamp(time.time() - 5*60, pytz.utc).isoformat())) 434 | 435 | recent_n_field = urwid.Edit(u'Recent n day:\n') 436 | 437 | recentnday = menu_button_withobj(u'Search last n day', searchsnapshot_recent_n_day_chosen, (wallet_obj, asset_obj, recent_n_field)) 438 | 439 | back = menu_button(u'Back', pop_current_menu) 440 | 441 | menu_buttons.append(recent1m) 442 | menu_buttons.append(recent30m) 443 | menu_buttons.append(recent3hours) 444 | menu_buttons.append(recent_n_field) 445 | menu_buttons.append(recentnday) 446 | menu_buttons.append(back) 447 | if hasattr(asset_obj, 'name'): 448 | input_asset_name = asset_obj.name 449 | else: 450 | input_asset_name = "all assets" 451 | 452 | top.open_box(menu(u'Search snapshot of:' + input_asset_name, menu_buttons)) 453 | 454 | 455 | 456 | def send_chosen(button, wallet_asset_obj): 457 | 458 | wallet_obj = wallet_asset_obj[0] 459 | asset_obj = wallet_asset_obj[1] 460 | 461 | menu_buttons = [] 462 | 463 | exe_destination_uuid_field = urwid.Edit(u'Destination uuid:\n') 464 | exe_amount_field = urwid.Edit(u'Amount:\n') 465 | exe_memo_field = urwid.Edit(u'Memo:\n') 466 | exe_pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 467 | 468 | 469 | 470 | 471 | menu_buttons.append(exe_destination_uuid_field) 472 | menu_buttons.append(exe_amount_field) 473 | menu_buttons.append(exe_memo_field) 474 | menu_buttons.append(exe_pin_code_field) 475 | done = menu_button_withobj(u'Send', send_confirm_chosen, (wallet_obj, asset_obj, exe_destination_uuid_field, exe_amount_field, exe_memo_field, exe_pin_code_field)) 476 | #done = menu_button_withobj(u'Send', show_content, (wallet_obj, asset_obj, "12", "23", "memo", "pin")) 477 | 478 | back = menu_button(u'Back', pop_current_menu) 479 | menu_buttons.append(done) 480 | menu_buttons.append(back) 481 | 482 | top.open_box(menu(u'Send ' + asset_obj.name, menu_buttons)) 483 | 484 | 485 | 486 | def deposit_chosen(button, wallet_asset_obj): 487 | deposit_chosen_menu_buttons = [] 488 | 489 | wallet_obj = wallet_asset_obj[0] 490 | asset_obj = wallet_asset_obj[1] 491 | 492 | response = urwid.Text([u'Deposit address of ', asset_obj.name]) 493 | deposit_chosen_menu_buttons.append(response) 494 | 495 | deposit_address_title_value_segments = asset_obj.deposit_address() 496 | for each_seg in deposit_address_title_value_segments: 497 | 498 | response = urwid.Text([u'', each_seg["title"] + " : " + each_seg["value"]]) 499 | deposit_chosen_menu_buttons.append(response) 500 | deposit_chosen_menu_buttons.append(urwid.Divider()) 501 | 502 | deposit_chosen_menu_buttons.append(menu_button_withobj(("copy %s"%(each_seg["title"])), copy_content_to_system_clip, each_seg["value"])) 503 | 504 | 505 | deposit_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 506 | top.open_box(urwid.Filler(urwid.Pile(deposit_chosen_menu_buttons))) 507 | 508 | 509 | 510 | def balance_chosen(button, wallet_obj): 511 | balance_chosen_menu_buttons = [] 512 | 513 | balance_result = wallet_obj.get_balance() 514 | if (balance_result.is_success): 515 | all_assets = balance_result.data 516 | if (len(all_assets) == 0): 517 | for eachAssetID in mixin_asset_id_collection.MIXIN_DEFAULT_CHAIN_GROUP: 518 | this_asset = wallet_obj.get_singleasset_balance(eachAssetID) 519 | else: 520 | for eachAsset in all_assets: 521 | balance_chosen_menu_buttons.append(menu_button_withobj(eachAsset.name.ljust(15)+":"+ eachAsset.balance, asset_chosen, (wallet_obj, eachAsset))) 522 | usdt_balance_on_btc = wallet_obj.get_singleasset_balance(mixin_asset_id_collection.USDT_ASSET_ID) 523 | if(usdt_balance_on_btc.data.balance == "0"): 524 | balance_chosen_menu_buttons.append(menu_button_withobj(usdt_balance_on_btc.data.name.ljust(15)+":"+ usdt_balance_on_btc.data.balance, asset_chosen, (wallet_obj, usdt_balance_on_btc.data))) 525 | 526 | 527 | balance_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 528 | top.open_box(menu(u'user id:' + wallet_obj.userid, balance_chosen_menu_buttons)) 529 | 530 | def exin_chosen(button, wallet_obj): 531 | exin_trade_pair_chosen_menu_buttons = [] 532 | 533 | trade_asset_list = exincore_api.fetchExinPrice(mixin_asset_id_collection.USDT_ASSET_ID) 534 | for eachPair in trade_asset_list: 535 | exin_trade_pair_chosen_menu_buttons.append(menu_button_withobj(str(eachPair), exin_tradepair_chosen, (wallet_obj, eachPair))) 536 | 537 | exin_trade_pair_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 538 | top.open_box(menu(u'Exin instant trade:', exin_trade_pair_chosen_menu_buttons)) 539 | 540 | 541 | def balance_send_to_mixin(button, wallet_asset_obj): 542 | wallet_obj = wallet_asset_obj[0] 543 | asset_obj = wallet_asset_obj[1] 544 | 545 | response = urwid.Text([u'Send ', asset_obj.name]) 546 | done = menu_button(u'Ok', exit_program) 547 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 548 | 549 | 550 | 551 | def copy_content_to_system_clip(button, to_copy_content): 552 | pyperclip.copy(to_copy_content) 553 | response = urwid.Text([u'Content has been copied to your clipboard']) 554 | done = menu_button(u'Ok', pop_current_menu) 555 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 556 | 557 | def remove_withdraw_address_chosen(button, wallet_asset_obj): 558 | 559 | wallet_obj = wallet_asset_obj[0] 560 | address_obj = wallet_asset_obj[1] 561 | 562 | menu_buttons = [] 563 | 564 | exe_pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 565 | menu_buttons.append(exe_pin_code_field) 566 | 567 | done = menu_button_withobj(u'Remove', remove_withdraw_address_confirm_chosen, (wallet_obj, address_obj, exe_pin_code_field)) 568 | #done = menu_button_withobj(u'Send', show_content, (wallet_obj, asset_obj, "12", "23", "memo", "pin")) 569 | 570 | back = menu_button(u'Back', pop_current_menu) 571 | menu_buttons.append(done) 572 | menu_buttons.append(back) 573 | 574 | top.open_box(menu(u'Remove address' + address_obj.label, menu_buttons)) 575 | def withdraw_asset_to_address_chosen(button, wallet_asset_address_obj): 576 | 577 | wallet_obj = wallet_asset_address_obj[0] 578 | 579 | asset_obj = wallet_asset_address_obj[1] 580 | address_obj = wallet_asset_address_obj[2] 581 | 582 | menu_buttons = [] 583 | withdraw_amount_field = urwid.Edit(u'Amount:\n') 584 | menu_buttons.append(withdraw_amount_field) 585 | 586 | withdraw_memo_field = urwid.Edit(u'Memo:\n') 587 | menu_buttons.append(withdraw_memo_field) 588 | withdraw_pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 589 | menu_buttons.append(withdraw_pin_code_field) 590 | 591 | done = menu_button_withobj(u'Withdraw', withdraw_asset_to_address_confirm_chosen, (wallet_obj, address_obj, withdraw_amount_field, withdraw_memo_field, withdraw_pin_code_field)) 592 | 593 | back = menu_button(u'Back', pop_current_menu) 594 | menu_buttons.append(done) 595 | menu_buttons.append(back) 596 | 597 | top.open_box(menu(u'Withdraw asset to ' + str(address_obj), menu_buttons)) 598 | 599 | 600 | 601 | def show_withdraw_address_remove(button, wallet_asset_obj): 602 | wallet_obj = wallet_asset_obj[0] 603 | address_obj = wallet_asset_obj[1] 604 | 605 | menu_buttons = [] 606 | 607 | if(address_obj.label != ""): 608 | menu_buttons.append(urwid.Text([u'Label:'.ljust(20), address_obj.label])) 609 | if(address_obj.public_key!= ""): 610 | menu_buttons.append(urwid.Text([u'Deposit address:'.ljust(20), address_obj.public_key])) 611 | if(address_obj.account_name!= ""): 612 | menu_buttons.append(urwid.Text([u'Account name:'.ljust(20), address_obj.account_name])) 613 | if(address_obj.account_tag!= ""): 614 | menu_buttons.append(urwid.Text([u'Account tag:'.ljust(20), address_obj.account_tag])) 615 | if(address_obj.fee!= ""): 616 | menu_buttons.append(urwid.Text([u'fee:'.ljust(20), address_obj.fee])) 617 | if(address_obj.reserve != ""): 618 | menu_buttons.append(urwid.Text([u'reserve:'.ljust(20), address_obj.reserve])) 619 | if(address_obj.dust!= ""): 620 | menu_buttons.append(urwid.Text([u'dust:'.ljust(20), address_obj.dust])) 621 | 622 | 623 | done = menu_button(u'Back', pop_current_menu) 624 | menu_buttons.append(done) 625 | remove = menu_button_withobj(u'Remove', remove_withdraw_address_chosen, (wallet_obj, address_obj)) 626 | menu_buttons.append(remove) 627 | 628 | top.open_box(menu(u'Withdraw address detail', menu_buttons)) 629 | 630 | def add_withdraw_address_eos_style(button, to_copy_content): 631 | wallet_obj = wallet_asset_obj[0] 632 | asset_obj = wallet_asset_obj[1] 633 | 634 | menu_buttons = [] 635 | 636 | deposit_address_field = urwid.Edit(u'Deposit address:\n') 637 | label_field = urwid.Edit(u'Account label:\n') 638 | pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 639 | 640 | 641 | menu_buttons.append(deposit_address_field) 642 | menu_buttons.append(label_field) 643 | menu_buttons.append(pin_code_field) 644 | done = menu_button_withobj(u'Add ', add_withdraw_address_confirm_chosen, (wallet_obj, asset_obj, deposit_address_field, label_field, "", "",pin_code_field)) 645 | #done = menu_button_withobj(u'Send', show_content, (wallet_obj, asset_obj, "12", "23", "memo", "pin")) 646 | 647 | back = menu_button(u'Back', pop_current_menu) 648 | menu_buttons.append(done) 649 | menu_buttons.append(back) 650 | 651 | top.open_box(menu(u'Add withdraw address for ' + asset_obj.name, menu_buttons)) 652 | 653 | 654 | def add_withdraw_address_bitcoin_style(button, wallet_asset_obj): 655 | wallet_obj = wallet_asset_obj[0] 656 | asset_obj = wallet_asset_obj[1] 657 | 658 | menu_buttons = [] 659 | 660 | deposit_address_field = urwid.Edit(u'Deposit address:\n') 661 | label_field = urwid.Edit(u'Account label:\n') 662 | pin_code_field = urwid.Edit(u'pin:\n', mask=u"*") 663 | 664 | 665 | menu_buttons.append(deposit_address_field) 666 | menu_buttons.append(label_field) 667 | menu_buttons.append(pin_code_field) 668 | done = menu_button_withobj(u'Add ', add_withdraw_address_confirm_chosen, (wallet_obj, asset_obj, deposit_address_field, label_field, "", "",pin_code_field)) 669 | #done = menu_button_withobj(u'Send', show_content, (wallet_obj, asset_obj, "12", "23", "memo", "pin")) 670 | 671 | back = menu_button(u'Back', pop_current_menu) 672 | menu_buttons.append(done) 673 | menu_buttons.append(back) 674 | 675 | top.open_box(menu(u'Add withdraw address for ' + asset_obj.name, menu_buttons)) 676 | 677 | def snapshot_chosen(button, snapshot_obj): 678 | snapshot_chosen_menu_buttons = [] 679 | 680 | snapshot_chosen_menu_buttons.append(urwid.Text([u'snapshot id'.ljust(20), u': ',snapshot_obj.snapshot_id, u'\n',u'created at'.ljust(20), u': ', snapshot_obj.created_at, u'\n',u'Asset Name'.ljust(20),': ', snapshot_obj.asset.name.ljust(20), u'\n', u'Amount'.ljust(20) ,': ', snapshot_obj.amount,u'\n',u'Opponent'.ljust(20),u': ',snapshot_obj.opponent_id, u'\n', u'Memo'.ljust(20),': ' ,snapshot_obj.memo])) 681 | 682 | snapshot_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 683 | 684 | top.open_box(menu("Snapshot", snapshot_chosen_menu_buttons)) 685 | 686 | def asset_chosen(button, wallet_asset_obj): 687 | wallet_obj = wallet_asset_obj[0] 688 | asset_obj = wallet_asset_obj[1] 689 | asset_chosen_menu_buttons = [] 690 | asset_chosen_menu_buttons.append(menu_button_withobj("send to mixin account", send_chosen, wallet_asset_obj)) 691 | asset_chosen_menu_buttons.append(menu_button_withobj("withdraw to other address", withdraw_asset_chosen, wallet_asset_obj)) 692 | asset_chosen_menu_buttons.append(menu_button_withobj(asset_obj.name + u" recent transfer", recent_transfer_chosen, wallet_asset_obj)) 693 | asset_chosen_menu_buttons.append(menu_button_withobj("show deposit address", deposit_chosen, wallet_asset_obj)) 694 | asset_chosen_menu_buttons.append(menu_button_withobj("manage withdraw contacts", manageasset_chosen, wallet_asset_obj)) 695 | #asset_chosen_menu_buttons.append(menu_button_withobj("recent transaction", send_chosen, wallet_obj)) 696 | 697 | asset_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 698 | 699 | top.open_box(menu(asset_obj.name.ljust(15)+":"+ asset_obj.balance, asset_chosen_menu_buttons)) 700 | def exin_tradepair_chosen(button, wallet_asset_obj): 701 | wallet_obj = wallet_asset_obj[0] 702 | tradepair_obj = wallet_asset_obj[1] 703 | tradepair_chosen_menu_buttons = [] 704 | tradepair_chosen_menu_buttons.append(menu_button_withobj("buy", tradepair_buy_chosen, (wallet_obj, tradepair_obj.base_asset, tradepair_obj.echange_asset))) 705 | tradepair_chosen_menu_buttons.append(menu_button_withobj("sell", tradepair_buy_chosen, (wallet_obj, tradepair_obj.echange_asset, tradepair_obj.base_asset))) 706 | 707 | tradepair_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 708 | 709 | top.open_box(menu(tradepair_obj.base_asset_symbol + " <-> " + tradepair_obj.exchange_asset_symbol, tradepair_chosen_menu_buttons)) 710 | 711 | 712 | def wallet_chosen(button, wallet_obj): 713 | wallet_chosen_menu_buttons = [] 714 | wallet_chosen_menu_buttons.append(menu_button_withobj("balance", balance_chosen, wallet_obj)) 715 | #wallet_chosen_menu_buttons.append(menu_button_withobj("search snapshots", send_chosen, wallet_obj)) 716 | wallet_chosen_menu_buttons.append(menu_button_withobj(u"recent transfer", recent_transfer_chosen, (wallet_obj, ""))) 717 | wallet_chosen_menu_buttons.append(menu_button_withobj("instant exchange token in exin", exin_chosen, wallet_obj)) 718 | #wallet_chosen_menu_buttons.append(menu_button_withobj("ocean.one exchange", send_chosen, wallet_obj)) 719 | wallet_chosen_menu_buttons.append(menu_button_withobj("verify pin", verify_pin_chosen, wallet_obj)) 720 | wallet_chosen_menu_buttons.append(menu_button_withobj("update pin", update_pin_chosen, wallet_obj)) 721 | wallet_chosen_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 722 | 723 | top.open_box(menu(u'user id:' + wallet_obj.userid, wallet_chosen_menu_buttons)) 724 | 725 | def item_chosen(button): 726 | response = urwid.Text([u'You chose ', button.label, u'\n']) 727 | done = menu_button(u'Ok', exit_program) 728 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 729 | 730 | def exit_program(button): 731 | raise urwid.ExitMainLoop() 732 | def pop_current_menu(button): 733 | top.close_box() 734 | 735 | def pop_current_and_more_menu(button): 736 | top.close_box() 737 | top.close_box() 738 | def pop_current_and_more_more_menu(button): 739 | top.close_box() 740 | top.close_box() 741 | top.close_box() 742 | def pop_to_account_menu(button): 743 | top.back_to_account() 744 | def load_wallet(button): 745 | if(os.path.isfile("new_users.csv")): 746 | wallet_records = wallet_api.load_wallet_csv_file('new_users.csv') 747 | load_wallet_menu_buttons = [] 748 | for each_wallet in wallet_records: 749 | load_wallet_menu_buttons.append(menu_button_withobj(each_wallet.userid, wallet_chosen, each_wallet)) 750 | 751 | load_wallet_menu_buttons.append(menu_button(u'Back', pop_current_menu)) 752 | top.open_box(menu("select wallet", load_wallet_menu_buttons)) 753 | else: 754 | response = urwid.Text([u'No local wallet record, Please create one', u'\n']) 755 | done = menu_button(u'Ok', pop_current_menu) 756 | top.open_box(urwid.Filler(urwid.Pile([response, done]))) 757 | 758 | 759 | 760 | menu_top = menu(u'Mixin pywallet', [ 761 | menu_button(u'load wallet', load_wallet), 762 | menu_button(u'create wallet', create_wallet_chosen), 763 | menu_button('exit', exit_program) 764 | ]) 765 | 766 | 767 | class CascadingBoxes(urwid.WidgetPlaceholder): 768 | max_box_levels = 10 769 | 770 | def __init__(self, box): 771 | super(CascadingBoxes, self).__init__(urwid.SolidFill(u'/')) 772 | self.box_level = 0 773 | self.open_box(box) 774 | 775 | def open_box(self, box): 776 | self.original_widget = urwid.Overlay(urwid.LineBox(box), 777 | self.original_widget, 778 | align='center', width=('relative', 80), 779 | valign='middle', height=('relative', 80), 780 | min_width=24, min_height=8, 781 | left=self.box_level * 3, 782 | right=(self.max_box_levels - self.box_level - 1) * 3, 783 | top=self.box_level * 2, 784 | bottom=(self.max_box_levels - self.box_level - 1) * 2) 785 | self.box_level += 1 786 | def back_to_account(self): 787 | while (self.box_level > 3): 788 | self.original_widget = self.original_widget[0] 789 | self.box_level -= 1 790 | 791 | def close_box(self): 792 | if self.box_level > 1: 793 | self.original_widget = self.original_widget[0] 794 | self.box_level -= 1 795 | 796 | def keypress(self, size, key): 797 | if key == 'esc' and self.box_level > 1: 798 | self.original_widget = self.original_widget[0] 799 | self.box_level -= 1 800 | else: 801 | return super(CascadingBoxes, self).keypress(size, key) 802 | 803 | top = CascadingBoxes(menu_top) 804 | urwid.MainLoop(top, palette=[('reversed', 'standout', '')], handle_mouse=False).run() 805 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-cn.md: -------------------------------------------------------------------------------- 1 |  2 | ### 啥东西 3 | 一个用[python](https://www.python.org)和[urwid](http://urwid.org)写的多功能数字货币钱包 4 | ### 为什么你需要这个东西 5 | 6 | #### 透明 7 | 这是一个开源项目,所有人都可以看代码。 8 | #### 安全 9 | 你持有账户私钥,没人能偷走你的资产。 10 | #### 可靠 11 | 基于一个透明且开源运作的分布式区块链项目 :[Mixin Network](https://github.com/awesome-mixin-network/index_of_Mixin_Network_resource)。主网已经于2019年2月末上线。 12 | #### 有用 13 | 不仅仅能保存比特币,还能保存其他币(Ethereum, EOS, XRP...)。 14 | #### 快 15 | 确认一笔交易只需要1秒钟。 16 | #### 保护你的隐私 17 | 匿名创建账户,匿名交易和付款。 18 | #### 强大 19 | 内置1秒闪兑交易所,可以交易比特币和主流加密资产。交易结束资产回到自己的钱包,不用放在中心化交易所,不担心交易所被攻击。 20 |  21 | #### 这是一个开放世界的入口 22 | 1. 通过开放式交易所可以在买卖任何ERC20 token。你自己创建都可以。 23 | 2. 有真随机数生成器保证的骰子游戏。 24 | 25 | 26 | ## 安装Python 3: 27 | 这个钱包基于Python3。 28 | 29 | macOS 30 | ```bash 31 | brew upgrade 32 | brew install python@3 33 | ``` 34 | 35 | Ubuntu使用第三方源安装python3 36 | ```bash 37 | sudo apt update 38 | sudo apt install software-properties-common 39 | sudo add-apt-repository ppa:deadsnakes/ppa 40 | ``` 41 | 42 | 遇到如下提示,敲击Enter 43 | ```bash 44 | Press [ENTER] to continue or Ctrl-c to cancel adding it. 45 | ``` 46 | 更新apt,安装 python3.7, python3.7-venv 47 | ```bash 48 | sudo apt update 49 | sudo apt install python3.7 python3.7-venv 50 | sudo ln -s /usr/bin/python3.7 /usr/bin/python3 51 | ``` 52 | 53 | 确认python3 和Python3-env 54 | ```bash 55 | $ python3 -V 56 | Python 3.7.2 57 | ``` 58 | 59 | ## 下载代码库并且创建env环境 60 | 61 | ```bash 62 | git clone https://github.com/awesome-mixin-network/bitcoin-cli-wallet-python.git 63 | cd bitcoin-cli-wallet-python 64 | python3 -m venv ./ 65 | ``` 66 | 67 | 激活环境 68 | ```bash 69 | source ./bin/activate 70 | ``` 71 | 72 | ## 安装依赖包 73 | 74 | 先升级pip,然后安装依赖软件库 75 | ```bash 76 | pip install --upgrade pip 77 | pip install -r requirements.txt 78 | ``` 79 | 80 | 运行代码 81 | ```bash 82 | python Bitcoin_Wallet_Mixin_consoleGUI.py 83 | ``` 84 |  85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin and altcoins wallet written in python 2 |  3 |
6 | 7 | 8 | ### What is this? 9 | a cryptocurrency wallet based on Python and urwid. 10 | ### Why you need this? 11 | 12 | #### Transparent 13 | It is a open source project. Every programmer can review the code. 14 | #### Secure 15 | You hold the wallet key. Nobody can steal your asset without the wallet key. 16 | #### Reliable 17 | It is based on a transparent and distributed blockchain : [Mixin Network](https://github.com/awesome-mixin-network/index_of_Mixin_Network_resource). The network launched it's main net on 29,Feb, 2019. 18 | #### Useful 19 | Not just hold Bitcoin, but also many altcoin(Ethereum, EOS, XRP...). 20 | #### Fast 21 | Every payment happen on Mixin network can be confirmed in 1 second. 22 | #### Protect your privacy 23 | Anonymously create account, anonymously pay and transfer cryptocurrency. 24 | #### Still powerful 25 | Instantly trade Bitcoin and altcoins, asset is in your wallet instead of a centralized exchange. 26 |  27 | #### Portal to an open world 28 | 1. You can sell or buy any ERC20 token inside wallet through open exchange protocol Ocean.one 29 | 2. You can play fair dice game 30 | 31 | 32 | ## Python 3 installation: 33 | This tool is written in Python 3.7.2 So you need to install Python 3.7.2 or above. 34 | 35 | macOS 36 | ```bash 37 | brew upgrade 38 | brew install python@3 39 | ``` 40 | 41 | Ubuntu, install python 3.7.2 from the third apt source. 42 | ```bash 43 | sudo apt update 44 | sudo apt install software-properties-common 45 | sudo add-apt-repository ppa:deadsnakes/ppa 46 | ``` 47 | 48 | When prompt like below, press Enter to continue: 49 | ```bash 50 | Press [ENTER] to continue or Ctrl-c to cancel adding it. 51 | ``` 52 | Update the source, then install python3.7, python3.7-venv 53 | ```bash 54 | sudo apt update 55 | sudo apt install python3.7 python3.7-venv 56 | sudo ln -s /usr/bin/python3.7 /usr/bin/python3 57 | ``` 58 | 59 | check both python3 and python3-venv are installed 60 | ```bash 61 | $ python3 -V 62 | Python 3.7.2 63 | ``` 64 | 65 | 66 | ## Clone the repo and create python env 67 | 68 | ```bash 69 | git clone https://github.com/awesome-mixin-network/bitcoin-cli-wallet-python.git 70 | cd bitcoin-cli-wallet-python 71 | python3 -m venv ./ 72 | ``` 73 | 74 | Active the env now 75 | ```bash 76 | source ./bin/activate 77 | ``` 78 | 79 | ## Install required packages by "virtual environment" 80 | 81 | Use pip to upgrade pip itself, and install required packages. 82 | ```bash 83 | pip install --upgrade pip 84 | pip install -r requirements.txt 85 | ``` 86 | 87 | Run the code 88 | ```bash 89 | python Bitcoin_Wallet_Mixin_consoleGUI.py 90 | ``` 91 |  92 | -------------------------------------------------------------------------------- /exincore_api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import uuid 3 | import base64 4 | import umsgpack 5 | import binascii 6 | EXINCORE_UUID = "61103d28-3ac2-44a2-ae34-bd956070dab1" 7 | 8 | 9 | class Asset_pair_price(): 10 | def __init__(self, asset_price_in_exin): 11 | self.minimum_amount = asset_price_in_exin.get("minimum_amount") 12 | self.maximum_amount = asset_price_in_exin.get("maximum_amount") 13 | self.price = asset_price_in_exin.get("price") 14 | self.base_asset_symbol = asset_price_in_exin.get("base_asset_symbol") 15 | self.exchange_asset_symbol = asset_price_in_exin.get("exchange_asset_symbol") 16 | self.base_asset = asset_price_in_exin.get("base_asset") 17 | self.echange_asset = asset_price_in_exin.get("exchange_asset") 18 | self.supported_by_exchanges = "" 19 | 20 | for eachExchange in asset_price_in_exin.get("exchanges"): 21 | self.supported_by_exchanges += eachExchange 22 | self.supported_by_exchanges += " " 23 | def __str__(self): 24 | result = "%s %s %s, exchange: %s"%(self.price.ljust(8), (self.base_asset_symbol)+"/"+self.exchange_asset_symbol.ljust(15), "min:"+self.minimum_amount.ljust(10)+" max:"+ self.maximum_amount.ljust(10)+ self.base_asset_symbol.ljust(20), self.supported_by_exchanges) 25 | return result 26 | def debug_str(self): 27 | result = "%s %s %s, exchange: %s base:%s target:%s"%(self.price.ljust(8), (self.base_asset_symbol)+"/"+self.exchange_asset_symbol.ljust(15), "min:"+self.minimum_amount+" max:"+ self.maximum_amount+ self.base_asset_symbol.ljust(20), self.supported_by_exchanges, self.base_asset, self.echange_asset) 28 | return result 29 | 30 | def fetchExinPrice(source_asset_id , target_asset_id = ""): 31 | result_fetchPrice = requests.get('https://exinone.com/exincore/markets', params={'base_asset':source_asset_id, "exchange_asset":target_asset_id}) 32 | exin_response = result_fetchPrice.json() 33 | 34 | datalist_in_response = [] 35 | if (exin_response.get("code") == 0): 36 | for eachData in exin_response.get("data"): 37 | datalist_in_response.append(Asset_pair_price(eachData)) 38 | return datalist_in_response 39 | 40 | def gen_memo_ExinBuy(asset_id_string): 41 | return base64.b64encode(umsgpack.packb({"A": uuid.UUID("{" + asset_id_string + "}").bytes})).decode("utf-8") 42 | 43 | def memo_is_pay_to_exin(input_snapshot): 44 | memo_at_snap = input_snapshot.memo 45 | try: 46 | exin_order = umsgpack.unpackb(base64.b64decode(memo_at_snap)) 47 | 48 | if "A"in exin_order: 49 | target_asset_uuid_in_myorder = str(uuid.UUID(bytes = exin_order["A"])) 50 | 51 | my_request_to_exin = Exin_execute_request(input_snapshot, target_asset_uuid_in_myorder) 52 | return my_request_to_exin 53 | else: 54 | return False 55 | except umsgpack.InsufficientDataException: 56 | return False 57 | except binascii.Error: 58 | return False 59 | 60 | EXIN_EXEC_TYPE_REQUEST = 0 61 | EXIN_EXEC_TYPE_RESULT = 1 62 | 63 | class Exin_execute(): 64 | def __init__(self, execute_type): 65 | self.execute_type = execute_type 66 | def is_request(self): 67 | return self.execute_type == EXIN_EXEC_TYPE_REQUEST 68 | def is_result(self): 69 | return self.execute_type == EXIN_EXEC_TYPE_RESULT 70 | 71 | 72 | class Exin_execute_request(Exin_execute): 73 | def __init__(self, input_snapshot, target_asset_id): 74 | self.pay_amount = abs(float(input_snapshot.amount)) 75 | self.request_asset = target_asset_id 76 | self.pay_asset = input_snapshot.asset 77 | self.order = input_snapshot.trace_id 78 | super().__init__(EXIN_EXEC_TYPE_REQUEST) 79 | def __str__(self): 80 | headString = "order: %s, pay %s %s to exin to buy %s "%(self.order, self.pay_amount, self.pay_asset.symbol, self.request_asset) 81 | return headString 82 | 83 | class Exin_execute_result(Exin_execute): 84 | def __init__(self, exin_order): 85 | self.order_result = exin_order["C"] 86 | self.price = exin_order["P"] 87 | self.fee = exin_order["F"] 88 | self.fee_asset_type = exin_order["FA"] 89 | self.type = exin_order["T"] 90 | self.order = exin_order["O"] 91 | super().__init__(EXIN_EXEC_TYPE_RESULT) 92 | def __str__(self): 93 | headString = "Status of your payment to exin is : " 94 | if(self.order_result == 1000): 95 | headString = headString + "Successful Exchange" 96 | headString = headString + ", your order is executed at price:" + self.price 97 | headString = headString + ", Exin core fee is " + self.fee + " with fee asset" + str(uuid.UUID(bytes = self.fee_asset_type)) 98 | 99 | if(self.order_result == 1001): 100 | headString = headString + "The order not found or invalid" 101 | if(self.order_result == 1002): 102 | headString = headString + "The request data is invalid" 103 | if(self.order_result == 1003): 104 | headString = headString + "The market not supported" 105 | if(self.order_result == 1004): 106 | headString = headString + "Failed exchange" 107 | if(self.order_result == 1005): 108 | headString = headString + "Partial exchange" 109 | if(self.order_result == 1006): 110 | headString = headString + "Insufficient pool" 111 | if(self.order_result == 1007): 112 | headString = headString + "Below the minimum exchange amount" 113 | if(self.order_result == 1008): 114 | headString = headString + "Exceeding the maximum exchange amount" 115 | if (self.type == "F"): 116 | headString = headString +", your order is refund to you because your memo is not correct" 117 | if (self.type == "R"): 118 | headString = headString +", your order is executed successfully" 119 | if (self.type == "E"): 120 | headString = headString +", exin failed to execute your order" 121 | headString = headString +", trace id of your payment to exincore is " + str(uuid.UUID(bytes = self.order)) 122 | return headString 123 | 124 | 125 | 126 | def memo_is_pay_from_exin(input_snapshot): 127 | memo_at_snap = input_snapshot.memo 128 | try: 129 | exin_order = Exin_execute_result(umsgpack.unpackb(base64.b64decode(memo_at_snap))) 130 | return exin_order 131 | except umsgpack.InsufficientDataException: 132 | return False 133 | except binascii.Error: 134 | return False 135 | 136 | 137 | 138 | 139 | def about_me(input_snapshot): 140 | exin_request = memo_is_pay_to_exin(input_snapshot) 141 | if exin_request != False: 142 | return exin_request 143 | exin_result = memo_is_pay_from_exin(input_snapshot) 144 | if exin_result != False: 145 | return exin_result 146 | return False 147 | -------------------------------------------------------------------------------- /mixin_api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mixin API for Python 3.x 4 | This SDK base on 'https://github.com/myrual/mixin_client_demo/blob/master/mixin_api.py' 5 | some method note '?', because can't run right result, may be it will be resolved later. 6 | 7 | env: python 3.x 8 | code by lee.c 9 | update at 2018.12.2 10 | """ 11 | 12 | from Crypto.PublicKey import RSA 13 | import base64 14 | from Crypto.Cipher import PKCS1_OAEP 15 | from Crypto.Signature import PKCS1_v1_5 16 | import Crypto 17 | import time 18 | from Crypto import Random 19 | from Crypto.Cipher import AES 20 | import hashlib 21 | import datetime 22 | import jwt 23 | import uuid 24 | import json 25 | import requests 26 | from urllib.parse import urlencode 27 | 28 | 29 | class MIXIN_API: 30 | def __init__(self, mixin_config): 31 | 32 | # robot's config 33 | self.client_id = mixin_config.client_id 34 | self.client_secret = mixin_config.client_secret 35 | self.pay_session_id = mixin_config.pay_session_id 36 | self.pay_pin = mixin_config.pay_pin 37 | self.pin_token = mixin_config.pin_token 38 | self.private_key = mixin_config.private_key 39 | 40 | 41 | self.keyForAES = "" 42 | # mixin api base url 43 | self.api_base_url = 'https://api.mixin.one' 44 | 45 | """ 46 | BASE METHON 47 | """ 48 | 49 | def generateSig(self, method, uri, body): 50 | hashresult = hashlib.sha256((method + uri+body).encode('utf-8')).hexdigest() 51 | return hashresult 52 | 53 | def genGETPOSTSig(self, methodstring, uristring, bodystring): 54 | jwtSig = self.generateSig(methodstring, uristring, bodystring) 55 | 56 | return jwtSig 57 | 58 | 59 | def genGETSig(self, uristring, bodystring): 60 | return self.genGETPOSTSig("GET", uristring, bodystring) 61 | 62 | def genPOSTSig(self, uristring, bodystring): 63 | return self.genGETPOSTSig("POST", uristring, bodystring) 64 | 65 | def genGETJwtToken(self, uristring, bodystring, jti): 66 | jwtSig = self.genGETSig(uristring, bodystring) 67 | iat = datetime.datetime.utcnow() 68 | exp = datetime.datetime.utcnow() + datetime.timedelta(seconds=200) 69 | encoded = jwt.encode({'uid':self.client_id, 'sid':self.pay_session_id,'iat':iat,'exp': exp, 'jti':jti,'sig':jwtSig}, self.private_key, algorithm='RS512') 70 | 71 | return encoded 72 | 73 | def genGETListenSignedToken(self, uristring, bodystring, jti): 74 | jwtSig = self.genGETSig(uristring, bodystring) 75 | iat = datetime.datetime.utcnow() 76 | exp = datetime.datetime.utcnow() + datetime.timedelta(seconds=200) 77 | encoded = jwt.encode({'uid':self.client_id, 'sid':self.pay_session_id,'iat':iat,'exp': exp, 'jti':jti,'sig':jwtSig}, self.private_key, algorithm='RS512') 78 | privKeyObj = RSA.importKey(self.private_key) 79 | signer = PKCS1_v1_5.new(privKeyObj) 80 | signature = signer.sign(encoded) 81 | return signature 82 | 83 | 84 | def genPOSTJwtToken(self, uristring, bodystring, jti, expseconds = 200): 85 | jwtSig = self.genPOSTSig(uristring, bodystring) 86 | iat = datetime.datetime.utcnow() 87 | exp = datetime.datetime.utcnow() + datetime.timedelta(seconds=expseconds) 88 | encoded = jwt.encode({'uid':self.client_id, 'sid':self.pay_session_id,'iat':iat,'exp': exp, 'jti':jti,'sig':jwtSig}, self.private_key, algorithm='RS512') 89 | return encoded 90 | def genEncrypedPin_withPin(self, self_pay_pin, iterString = None): 91 | if self.keyForAES == "": 92 | privKeyObj = RSA.importKey(self.private_key) 93 | 94 | decoded_result = base64.b64decode(self.pin_token) 95 | 96 | cipher = PKCS1_OAEP.new(key=privKeyObj, hashAlgo=Crypto.Hash.SHA256, label=self.pay_session_id.encode("utf-8")) 97 | 98 | decrypted_msg = cipher.decrypt(decoded_result) 99 | 100 | self.keyForAES = decrypted_msg 101 | 102 | ts = int(time.time()) 103 | tszero = ts % 0x100 104 | tsone = (ts % 0x10000) >> 8 105 | tstwo = (ts % 0x1000000) >> 16 106 | tsthree = (ts % 0x100000000) >> 24 107 | 108 | 109 | tszero = chr(tszero).encode('latin1').decode('latin1') 110 | tsone = chr(tsone) 111 | tstwo = chr(tstwo) 112 | tsthree = chr(tsthree) 113 | 114 | tsstring = tszero + tsone + tstwo + tsthree + '\0\0\0\0' 115 | if iterString is None: 116 | ts = int(time.time() * 100000) 117 | tszero = ts % 0x100 118 | tsone = (ts % 0x10000) >> 8 119 | tstwo = (ts % 0x1000000) >> 16 120 | tsthree = (ts % 0x100000000) >> 24 121 | tsfour= (ts % 0x10000000000) >> 32 122 | tsfive= (ts % 0x10000000000) >> 40 123 | tssix = (ts % 0x1000000000000) >> 48 124 | tsseven= (ts % 0x1000000000000) >> 56 125 | 126 | 127 | tszero = chr(tszero).encode('latin1').decode('latin1') 128 | tsone = chr(tsone) 129 | tstwo = chr(tstwo) 130 | tsthree = chr(tsthree) 131 | tsfour = chr(tsfour) 132 | tsfive= chr(tsfive) 133 | tssix = chr(tssix) 134 | tsseven = chr(tsseven) 135 | iterStringByTS = tszero + tsone + tstwo + tsthree + tsfour + tsfive + tssix + tsseven 136 | toEncryptContent = self_pay_pin + tsstring + iterStringByTS 137 | else: 138 | toEncryptContent = self_pay_pin + tsstring + iterString 139 | 140 | lenOfToEncryptContent = len(toEncryptContent) 141 | toPadCount = 16 - lenOfToEncryptContent % 16 142 | if toPadCount > 0: 143 | paddedContent = toEncryptContent + chr(toPadCount) * toPadCount 144 | else: 145 | paddedContent = toEncryptContent 146 | 147 | iv = Random.new().read(AES.block_size) 148 | 149 | 150 | cipher = AES.new(self.keyForAES, AES.MODE_CBC,iv) 151 | encrypted_result = cipher.encrypt(paddedContent.encode('latin1')) 152 | 153 | msg = iv + encrypted_result 154 | encrypted_pin = base64.b64encode(msg) 155 | 156 | return encrypted_pin 157 | 158 | 159 | def genEncrypedPin(self, iterString = None): 160 | return self.genEncrypedPin_withPin(self.pay_pin) 161 | """ 162 | COMMON METHON 163 | """ 164 | 165 | """ 166 | generate API url 167 | """ 168 | def __genUrl(self, path): 169 | return self.api_base_url + path 170 | 171 | """ 172 | generate GET http request 173 | """ 174 | def __genGetRequest(self, path, auth_token=""): 175 | 176 | url = self.__genUrl(path) 177 | 178 | if auth_token == "": 179 | r = requests.get(url) 180 | else: 181 | r = requests.get(url, headers={"Authorization": "Bearer " + auth_token}) 182 | 183 | result_obj = r.json() 184 | return result_obj['data'] 185 | 186 | """ 187 | generate POST http request 188 | """ 189 | def __genPostRequest(self, path, body, auth_token=""): 190 | 191 | # generate url 192 | url = self.__genUrl(path) 193 | 194 | # transfer obj => json string 195 | body_in_json = json.dumps(body) 196 | 197 | if auth_token == "": 198 | r = requests.post(url, json=body_in_json) 199 | else: 200 | r = requests.post(url, json=body_in_json, headers={"Authorization": "Bearer " + auth_token}) 201 | 202 | result_obj = r.json() 203 | print(result_obj) 204 | return result_obj 205 | 206 | """ 207 | generate Mixin Network GET http request 208 | """ 209 | def __genNetworkGetRequest(self, path, body=None, auth_token=""): 210 | 211 | url = self.__genUrl(path) 212 | 213 | if body is not None: 214 | body = urlencode(body) 215 | else: 216 | body = "" 217 | 218 | if auth_token == "": 219 | token = self.genGETJwtToken(path, body, str(uuid.uuid4())) 220 | auth_token = token.decode('utf8') 221 | 222 | r = requests.get(url, headers={"Authorization": "Bearer " + auth_token}) 223 | result_obj = r.json() 224 | return result_obj 225 | 226 | """ 227 | generate Mixin Network GET http request for snapshot 228 | """ 229 | def __genNetworkGetRequest_snapshots(self, path, body=None, auth_token=""): 230 | if body is not None: 231 | body = urlencode(body) 232 | url = self.__genUrl(path+"?" + body) 233 | if auth_token == "": 234 | token = self.genGETJwtToken(path+"?" + body, "", str(uuid.uuid4())) 235 | auth_token = token.decode('utf8') 236 | 237 | 238 | else: 239 | body = "" 240 | url = self.__genUrl(path) 241 | if auth_token == "": 242 | token = self.genGETJwtToken(path, "", str(uuid.uuid4())) 243 | auth_token = token.decode('utf8') 244 | r = requests.get(url, headers={"Authorization": "Bearer " + auth_token, 'Content-Type': 'application/json', 'Content-length': '0'}) 245 | result_obj = r.json() 246 | return result_obj 247 | 248 | 249 | 250 | """ 251 | generate Mixin Network POST http request 252 | """ 253 | # TODO: request 254 | def __genNetworkPostRequest(self, path, body, auth_token=""): 255 | 256 | body_in_json = json.dumps(body) 257 | 258 | if auth_token == "": 259 | token = self.genPOSTJwtToken(path, body_in_json, str(uuid.uuid4())) 260 | auth_token = token.decode('utf8') 261 | headers = { 262 | 'Content-Type' : 'application/json', 263 | 'Authorization' : 'Bearer ' + auth_token, 264 | } 265 | url = self.__genUrl(path) 266 | 267 | r = requests.post(url, json=body, headers=headers) 268 | if (r.status_code == 200): 269 | result_obj = r.json() 270 | return result_obj 271 | if (r.status_code == 500): 272 | print("path: %s, body:%s"%(path, body_in_json)) 273 | return {"httpfailed":r.status_code} 274 | return (r.json()) 275 | """ 276 | ============ 277 | MESSENGER PRIVATE APIs 278 | ============ 279 | auth token need request 'https://api.mixin.one/me' to get. 280 | """ 281 | 282 | 283 | """ 284 | Read user's all assets. 285 | """ 286 | def getMyAssets(self, auth_token=""): 287 | 288 | assets_result = self.__genNetworkGetRequest('/assets', auth_token) 289 | return assets_result 290 | 291 | """ 292 | Read self profile. 293 | """ 294 | def getMyProfile(self, auth_token): 295 | return self.__genNetworkGetRequest('/me', auth_token) 296 | 297 | """ 298 | ? 299 | Update my preferences. 300 | """ 301 | def updateMyPerference(self,receive_message_source="EVERYBODY",accept_conversation_source="EVERYBODY"): 302 | 303 | body = { 304 | "receive_message_source": receive_message_source, 305 | "accept_conversation_source": accept_conversation_source 306 | } 307 | 308 | return self.__genPostRequest('/me/preferences', body) 309 | 310 | 311 | """ 312 | ? 313 | Update my profile. 314 | """ 315 | def updateMyProfile(self, full_name, auth_token, avatar_base64=""): 316 | 317 | body = { 318 | "full_name": full_name, 319 | "avatar_base64": avatar_base64 320 | } 321 | 322 | return self.__genPostRequest('/me', body, auth_token) 323 | 324 | """ 325 | Get users information by IDs. 326 | """ 327 | def getUsersInfo(self, user_ids, auth_token): 328 | return self.__genPostRequest('/users/fetch', user_ids, auth_token) 329 | 330 | """ 331 | Get user's information by ID. 332 | """ 333 | def getUserInfo(self, user_id, auth_token): 334 | return self.__genGetRequest('/users/' + user_id, auth_token) 335 | 336 | """ 337 | Search user by Mixin ID or Phone Number. 338 | """ 339 | def SearchUser(self, q, auth_token=""): 340 | return self.__genGetRequest('/search/' + q, auth_token) 341 | 342 | """ 343 | Rotate user’s code_id. 344 | """ 345 | def rotateUserQR(self, auth_token): 346 | return self.__genGetRequest('/me/code', auth_token) 347 | 348 | """ 349 | Get my friends. 350 | """ 351 | def getMyFriends(self, auth_token): 352 | return self.__genGetRequest('/friends', auth_token) 353 | 354 | """ 355 | Create a GROUP or CONTACT conversation. 356 | """ 357 | def createConv(self, category, conversation_id, participants, action, role, user_id, auth_token): 358 | 359 | body = { 360 | "category": category, 361 | "conversation_id": conversation_id, 362 | "participants": participants, 363 | "action": action, 364 | "role": role, 365 | "user_id": user_id 366 | } 367 | 368 | return self.__genPostRequest('/conversations', body, auth_token) 369 | 370 | """ 371 | Read conversation by conversation_id. 372 | """ 373 | def getConv(self, conversation_id, auth_token): 374 | return self.__genGetRequest('/conversations/' + conversation_id, auth_token) 375 | 376 | 377 | """ 378 | ============ 379 | NETWORK PRIVATE APIs 380 | ============ 381 | auth token need robot related param to generate. 382 | """ 383 | 384 | """ 385 | PIN is used to manage user’s addresses, assets and etc. There’s no default PIN for a Mixin Network user (except APP). 386 | if auth_token is empty, it create robot' pin. 387 | if auth_token is set, it create messenger user pin. 388 | """ 389 | def updatePin(self, new_pin, old_pin, auth_token=""): 390 | if old_pin == "": 391 | newEncrypedPin = self.genEncrypedPin_withPin(new_pin) 392 | body = { 393 | "old_pin": "", 394 | "pin": newEncrypedPin.decode() 395 | } 396 | else: 397 | oldEncryptedPin = self.genEncrypedPin_withPin(old_pin) 398 | newEncrypedPin = self.genEncrypedPin_withPin(new_pin) 399 | 400 | body = { 401 | "old_pin": oldEncryptedPin.decode(), 402 | "pin": newEncrypedPin.decode() 403 | } 404 | return self.__genNetworkPostRequest('/pin/update', body, auth_token) 405 | 406 | """ 407 | Verify PIN if is valid or not. For example, you can verify PIN before updating it. 408 | if auth_token is empty, it verify robot' pin. 409 | if auth_token is set, it verify messenger user pin. 410 | """ 411 | def verifyPin(self, input_pin, auth_token=""): 412 | enPin = self.genEncrypedPin_withPin(input_pin) 413 | body = { 414 | "pin": enPin.decode() 415 | } 416 | 417 | return self.__genNetworkPostRequest('/pin/verify', body, auth_token) 418 | 419 | """ 420 | Grant an asset's deposit address, usually it is public_key, but account_name and account_tag is used for EOS. 421 | """ 422 | def deposit(self, asset_id): 423 | return self.__genNetworkGetRequest(' /assets/' + asset_id) 424 | """ 425 | Read an asset's withdraw address, usually it is public_key, but account_name and account_tag is used for EOS. 426 | """ 427 | def withdrawals_address(self, asset_id): 428 | return self.__genNetworkGetRequest('/assets/' + asset_id + '/addresses') 429 | 430 | 431 | 432 | """ 433 | withdrawals robot asset to address_id 434 | Tips:Get assets out of Mixin Network, neet to create an address for withdrawal. 435 | """ 436 | def withdrawals(self, address_id, amount, memo, trace_id, asset_pin): 437 | encrypted_pin = self.genEncrypedPin_withPin(asset_pin).decode() 438 | 439 | body = { 440 | "address_id": address_id, 441 | "pin": encrypted_pin, 442 | "amount": amount, 443 | "trace_id": trace_id, 444 | "memo": memo 445 | } 446 | if trace_id == "": 447 | body['trace_id'] = str(uuid.uuid1()) 448 | 449 | 450 | return self.__genNetworkPostRequest('/withdrawals', body) 451 | 452 | 453 | """ 454 | Create an address for withdrawal, you can only withdraw through an existent address. 455 | """ 456 | def createAddress(self, asset_id, public_key = "", label = "", asset_pin = "", account_name = "", account_tag = ""): 457 | 458 | if (asset_pin == ""): 459 | encrypted_pin = self.genEncrypedPin().decode() 460 | else: 461 | encrypted_pin = self.genEncrypedPin_withPin(asset_pin).decode() 462 | body = { 463 | "asset_id": asset_id, 464 | "pin": encrypted_pin, 465 | "public_key": public_key, 466 | "label": label, 467 | "account_name": account_name, 468 | "account_tag": account_tag, 469 | } 470 | return self.__genNetworkPostRequest('/addresses', body) 471 | 472 | def createAddressEOS(self, asset_id, account_name, account_tag, label = ""): 473 | 474 | body = { 475 | "asset_id": asset_id, 476 | "pin": self.genEncrypedPin().decode(), 477 | "account_name": account_name, 478 | "account_tag": account_tag, 479 | "label": label, 480 | } 481 | return self.__genNetworkPostRequest('/addresses', body) 482 | """ 483 | Delete an address by ID. 484 | """ 485 | def delAddress(self, address_id, asset_pin = ""): 486 | 487 | if(asset_pin == ""): 488 | encrypted_pin = self.genEncrypedPin().decode() 489 | else: 490 | encrypted_pin = self.genEncrypedPin_withPin(asset_pin).decode() 491 | 492 | body = {"pin": encrypted_pin} 493 | 494 | return self.__genNetworkPostRequest('/addresses/' + address_id + '/delete', body) 495 | 496 | 497 | """ 498 | Read an address by ID. 499 | """ 500 | def getAddress(self, address_id): 501 | return self.__genNetworkGetRequest('/addresses/' + address_id) 502 | 503 | """ 504 | Transfer of assets between Mixin Network users. 505 | """ 506 | def transferTo(self, to_user_id, to_asset_id, to_asset_amount, memo, trace_uuid="", input_pin = "", input_encrypted_pin = ""): 507 | 508 | if input_encrypted_pin == "": 509 | # generate encrypted pin 510 | if (input_pin == ""): 511 | encrypted_pin = self.genEncrypedPin() 512 | else: 513 | encrypted_pin = self.genEncrypedPin_withPin(input_pin) 514 | 515 | else: 516 | encrypted_pin = input_encrypted_pin 517 | body = {'asset_id': to_asset_id, 'counter_user_id': to_user_id, 'amount': str(to_asset_amount), 518 | 'pin': encrypted_pin.decode('utf8'), 'trace_id': trace_uuid, 'memo': memo} 519 | if trace_uuid == "": 520 | body['trace_id'] = str(uuid.uuid1()) 521 | 522 | return self.__genNetworkPostRequest('/transfers', body) 523 | 524 | """ 525 | Read transfer by trace ID. 526 | """ 527 | def getTransfer(self, trace_id): 528 | return self.__genNetworkGetRequest('/transfers/trace/' + trace_id) 529 | 530 | """ 531 | Verify a transfer, payment status if it is 'paid' or 'pending'. 532 | """ 533 | def verifyPayment(self, asset_id, opponent_id, amount, trace_id): 534 | 535 | body = { 536 | "asset_id": asset_id, 537 | "opponent_id": opponent_id, 538 | "amount": amount, 539 | "trace_id": trace_id 540 | } 541 | 542 | return self.__genNetworkPostRequest('/payments', body) 543 | 544 | """ 545 | Read asset by asset ID. 546 | """ 547 | def getAsset(self, asset_id): 548 | return self.__genNetworkGetRequest('/assets/' + asset_id) 549 | 550 | """ 551 | Read external transactions (pending deposits) by public_key and asset_id, use account_tag for EOS. 552 | """ 553 | def extTrans(self, asset_id, public_key, account_tag, account_name, limit, offset): 554 | 555 | body = { 556 | "asset": asset_id, 557 | "public_key": public_key, 558 | "account_tag": account_tag, 559 | "account_name": account_name, 560 | "limit": limit, 561 | "offset": offset 562 | } 563 | 564 | return self.__genNetworkGetRequest('/external/transactions', body) 565 | def fetchTokenForCreateUser(self, body, url): 566 | body_in_json = json.dumps(body) 567 | headers = { 568 | 'Content-Type' : 'application/json', 569 | } 570 | r = requests.post(url, json=body, headers=headers) 571 | result_obj = r.json() 572 | print(result_obj) 573 | return result_obj.get("token") 574 | 575 | 576 | 577 | """ 578 | Create a new Mixin Network user (like a normal Mixin Messenger user). You should keep PrivateKey which is used to sign an AuthenticationToken and encrypted PIN for the user. 579 | """ 580 | def createUser(self, session_secret, full_name, auth_token = ""): 581 | 582 | body = { 583 | "session_secret": session_secret, 584 | "full_name": full_name 585 | } 586 | 587 | return self.__genNetworkPostRequest('/users', body, auth_token) 588 | 589 | 590 | """ 591 | =========== 592 | NETWORK PUBLIC APIs 593 | =========== 594 | """ 595 | 596 | """ 597 | Read top valuable assets of Mixin Network. 598 | """ 599 | def topAssets(self): 600 | return self.__genGetRequest('/network') 601 | 602 | """ 603 | Read public snapshots of Mixin Network. 604 | """ 605 | def snapshots(self, offset, asset_id, order='DESC',limit=100): 606 | # TODO: SET offset default(UTC TIME) 607 | body = { 608 | "limit":limit, 609 | "offset":offset, 610 | "asset":asset_id, 611 | "order":order 612 | } 613 | finalURL = "/network/snapshots?offset=%s&asset=%s&order=%s&limit=%d" % (offset, asset_id, order, limit) 614 | 615 | 616 | return self.__genNetworkGetRequest_snapshots(finalURL) 617 | def snapshots_after(self, offset, asset_id, limit=100): 618 | return self.snapshots(offset, asset_id, "ASC", limit) 619 | def snapshots_before(self, offset, asset_id, limit=100): 620 | return self.snapshots(offset, asset_id, "DESC", limit) 621 | 622 | 623 | """ 624 | Read public snapshots of Mixin Network by ID. 625 | """ 626 | def snapshot(self, snapshot_id): 627 | return self.__genGetRequest('/network/snapshots/' + snapshot_id) 628 | """ 629 | """ 630 | 631 | def account_snapshot(self, snapshot_id): 632 | return self.__genNetworkGetRequest_snapshots('/network/snapshots/' + snapshot_id) 633 | """ 634 | Read this account snapshots of Mixin Network. Beaer token is required 635 | """ 636 | def account_snapshots(self, offset, asset_id, order='DESC',limit=100): 637 | # TODO: SET offset default(UTC TIME) 638 | body = { 639 | "limit":limit, 640 | "offset":offset, 641 | "asset":asset_id, 642 | "order":order 643 | } 644 | 645 | 646 | result_json = self.__genNetworkGetRequest_snapshots("/network/snapshots", body) 647 | return result_json 648 | def account_snapshots_before(self, offset, asset_id, limit=100): 649 | return self.account_snapshots(offset, asset_id, order = "DESC", limit = limit) 650 | def account_snapshots_after(self, offset, asset_id, limit=100): 651 | return self.account_snapshots(offset, asset_id, order = "ASC", limit = limit) 652 | 653 | 654 | -------------------------------------------------------------------------------- /mixin_asset_id_collection.py: -------------------------------------------------------------------------------- 1 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 2 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 3 | USDT_ASSET_ID = "815b0b1a-2764-3736-8faa-42d694fa620a" 4 | ETC_ASSET_ID = "2204c1ee-0ea2-4add-bb9a-b3719cfff93a"; 5 | XRP_ASSET_ID = "23dfb5a5-5d7b-48b6-905f-3970e3176e27"; 6 | XEM_ASSET_ID = "27921032-f73e-434e-955f-43d55672ee31" 7 | ETH_ASSET_ID = "43d61dcd-e413-450d-80b8-101d5e903357"; 8 | DASH_ASSET_ID = "6472e7e3-75fd-48b6-b1dc-28d294ee1476"; 9 | DOGE_ASSET_ID = "6770a1e5-6086-44d5-b60f-545f9d9e8ffd" 10 | LTC_ASSET_ID = "76c802a2-7c88-447f-a93e-c29c9e5dd9c8"; 11 | SIA_ASSET_ID = "990c4c29-57e9-48f6-9819-7d986ea44985"; 12 | ZEN_ASSET_ID = "a2c5d22b-62a2-4c13-b3f0-013290dbac60" 13 | ZEC_ASSET_ID = "c996abc9-d94e-4494-b1cf-2a3fd3ac5714" 14 | BCH_ASSET_ID = "fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0" 15 | 16 | MIXIN_DEFAULT_CHAIN_GROUP = [BTC_ASSET_ID, EOS_ASSET_ID, USDT_ASSET_ID, ETC_ASSET_ID, XRP_ASSET_ID, XEM_ASSET_ID, ETH_ASSET_ID, DASH_ASSET_ID, DOGE_ASSET_ID, LTC_ASSET_ID, SIA_ASSET_ID, ZEN_ASSET_ID, ZEC_ASSET_ID, BCH_ASSET_ID] 17 | 18 | -------------------------------------------------------------------------------- /mixin_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Mixin Config 4 | get below config from 'https://developers.mixin.one/dashboard' 5 | code by lee.c 6 | update at 2018.12.2 7 | """ 8 | 9 | client_id= '' 10 | client_secret = '' 11 | 12 | 13 | pay_pin = '' 14 | pay_session_id = '' 15 | pin_token = "" 16 | 17 | 18 | private_key = """-----BEGIN RSA PRIVATE KEY----- 19 | -----END RSA PRIVATE KEY-----""" 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography==2.4.2 2 | pycparser==2.19 3 | pycryptodome==3.7.2 4 | PyJWT==1.7.1 5 | python-dateutil==2.7.5 6 | pyyaml>=4.2b1 7 | requests==2.21.0 8 | websocket-client==0.54.0 9 | u-msgpack-python 10 | urwid 11 | pyperclip 12 | iso8601 13 | -------------------------------------------------------------------------------- /screen_shot_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-mixin-network/bitcoin-cli-wallet-python/6eb3df84d45541c8c27d99e770da8928eab3f366/screen_shot_wallet.png -------------------------------------------------------------------------------- /screen_trade_exin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-mixin-network/bitcoin-cli-wallet-python/6eb3df84d45541c8c27d99e770da8928eab3f366/screen_trade_exin.png -------------------------------------------------------------------------------- /screen_wallet_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awesome-mixin-network/bitcoin-cli-wallet-python/6eb3df84d45541c8c27d99e770da8928eab3f366/screen_wallet_open.png -------------------------------------------------------------------------------- /wallet_api.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from mixin_api import MIXIN_API 3 | from Crypto.PublicKey import RSA 4 | import iso8601 5 | import time 6 | import json 7 | import requests 8 | import base64 9 | import random 10 | import string 11 | 12 | def randomString(stringLength=10): 13 | """Generate a random string of fixed length """ 14 | letters = string.ascii_lowercase 15 | return ''.join(random.choice(letters) for i in range(stringLength)) 16 | 17 | 18 | def pubkeyContent(inputContent): 19 | contentWithoutHeader= inputContent[len("-----BEGIN PUBLIC KEY-----") + 1:] 20 | contentWithoutTail = contentWithoutHeader[:-1 * (len("-----END PUBLIC KEY-----") + 1)] 21 | contentWithoutReturn = contentWithoutTail[:64] + contentWithoutTail[65:129] + contentWithoutTail[130:194] + contentWithoutTail[195:] 22 | return contentWithoutReturn 23 | 24 | class MIXIN_config(): 25 | def __init__(self): 26 | self.private_key = "" 27 | self.pin_token = "" 28 | self.pay_session_id = "" 29 | self.client_id = "" 30 | self.client_secret = "" 31 | self.pay_pin = "" 32 | 33 | def generateMixinAPI(private_key,pin_token,session_id,user_id,pin,client_secret): 34 | mixin_config = MIXIN_config() 35 | mixin_config.private_key = private_key 36 | mixin_config.pin_token = pin_token 37 | mixin_config.pay_session_id = session_id 38 | mixin_config.client_id = user_id 39 | mixin_config.client_secret = client_secret 40 | mixin_config.pay_pin = pin 41 | return MIXIN_API(mixin_config) 42 | 43 | 44 | class RSAKey4Mixin(): 45 | def __init__(self): 46 | key = RSA.generate(1024) 47 | pubkey = key.publickey() 48 | private_key = key.exportKey() 49 | session_key = pubkeyContent(pubkey.exportKey()) 50 | self.session_key = session_key.decode() 51 | self.private_key = private_key.decode() 52 | 53 | class Mixin_Wallet_API_Result_Error(): 54 | def __init__(self, dictInput): 55 | self.status = dictInput.get("status") 56 | self.code = dictInput.get("code") 57 | self.description = dictInput.get("description") 58 | def __str__(self): 59 | return "%s with status: %s, code: %s"%(self.description, self.status, self.code) 60 | 61 | class Mixin_Wallet_HTTP_Result_Error(): 62 | def __init__(self, dictInput): 63 | self.http_code = dictInput 64 | def __str__(self): 65 | return "http visit failed with code %d "%(self.http_code) 66 | 67 | 68 | 69 | 70 | 71 | class Mixin_Wallet_API_Result(): 72 | def __init__(self, jsonInput, processFunc = None): 73 | if ("httpfailed" in jsonInput): 74 | self.is_success = False 75 | self.error = Mixin_Wallet_HTTP_Result_Error(jsonInput.get("httpfailed")) 76 | elif ("error" in jsonInput): 77 | self.is_success = False 78 | self.error = Mixin_Wallet_API_Result_Error(jsonInput.get("error")) 79 | else: 80 | self.is_success = True 81 | if processFunc != None: 82 | self.data = processFunc(jsonInput.get("data")) 83 | def __str__(self): 84 | if(self.is_success): 85 | if hasattr(self, 'data'): 86 | return str(self.data) 87 | else: 88 | return "Success" 89 | else: 90 | return str(self.error) 91 | 92 | class userInfo(): 93 | def __init__(self, userInfojson): 94 | self.pin_token = userInfojson.get("pin_token") 95 | self.session_id = userInfojson.get("session_id") 96 | self.user_id = userInfojson.get("user_id") 97 | def fromcreateUserJson(self, userInfojson): 98 | self.pin_token = userInfojson.get("pin_token") 99 | self.session_id = userInfojson.get("session_id") 100 | self.user_id = userInfojson.get("user_id") 101 | 102 | class Static_Asset(): 103 | def __init__(self, jsonInput): 104 | self.type = jsonInput.get("type") 105 | self.name = jsonInput.get("name") 106 | self.asset_id = jsonInput.get("asset_id") 107 | self.chain_id = jsonInput.get("chain_id") 108 | self.symbol = jsonInput.get("symbol") 109 | 110 | class Asset(Static_Asset): 111 | def __init__(self, jsonInput): 112 | self.type = jsonInput.get("type") 113 | self.name = jsonInput.get("name") 114 | self.asset_id = jsonInput.get("asset_id") 115 | self.chain_id = jsonInput.get("chain_id") 116 | self.balance = jsonInput.get("balance") 117 | self.symbol = jsonInput.get("symbol") 118 | self.public_key = jsonInput.get("public_key") 119 | self.account_name = jsonInput.get("account_name") 120 | self.account_tag = jsonInput.get("account_tag") 121 | def deposit_address(self): 122 | result_desposit = [] 123 | if(self.public_key != ""): 124 | result_desposit.append({"title":"Deposit address", "value":self.public_key}) 125 | if(self.account_name!= ""): 126 | result_desposit.append({"title":"Deposit account name", "value":self.account_name}) 127 | if(self.account_tag!= ""): 128 | result_desposit.append({"title":"Deposit account tag", "value":self.account_tag}) 129 | return result_desposit 130 | 131 | class Withdrawal(): 132 | def __init__(self, jsonInput): 133 | self.snapshot_id = jsonInput.get("snapshot_id") 134 | self.transaction_hash = jsonInput.get("transaction_hash") 135 | self.asset_id = jsonInput.get("asset_id") 136 | self.amount = jsonInput.get("amount") 137 | self.trace_id = jsonInput.get("trace_id") 138 | self.memo = jsonInput.get("memo") 139 | self.created_at = jsonInput.get("created_at") 140 | 141 | 142 | class Snapshot(): 143 | def __init__(self, jsonInput): 144 | 145 | self.amount = jsonInput.get("amount") 146 | self.type = jsonInput.get("type") 147 | self.asset = Static_Asset(jsonInput.get("asset")) 148 | self.created_at = jsonInput.get("created_at") 149 | self.memo = jsonInput.get("data") 150 | self.snapshot_id = jsonInput.get("snapshot_id") 151 | self.source = jsonInput.get("source") 152 | self.user_id = jsonInput.get("user_id") 153 | self.trace_id = jsonInput.get("trace_id") 154 | self.opponent_id = jsonInput.get("opponent_id") 155 | def __str__(self): 156 | string_result = "" 157 | string_result += (self.amount.ljust(15)) 158 | string_result += (" " + self.asset.symbol.ljust(10)) 159 | string_result += " created at:" + self.created_at.ljust(30) 160 | if self.user_id != None: 161 | string_result += (" from " + self.user_id) 162 | if self.opponent_id != None: 163 | string_result += (" to " + self.opponent_id) 164 | if (self.trace_id != None): 165 | string_result += (" trace id:" + self.trace_id) 166 | if (self.memo != None): 167 | string_result += (" memo:" + self.memo) 168 | return string_result 169 | 170 | def is_sent(self): 171 | return float(self.amount) < 0 172 | def is_received(self): 173 | return float(self.amount) > 0 174 | def is_my_snap(self): 175 | return self.user_id != None 176 | 177 | class Address(): 178 | def __init__(self, jsonInput): 179 | self.address_id = jsonInput.get("address_id") 180 | self.public_key = jsonInput.get("public_key") 181 | self.asset_id = jsonInput.get("asset_id") 182 | self.label = jsonInput.get("label") 183 | self.account_name = jsonInput.get("account_name") 184 | self.account_tag = jsonInput.get("account_tag") 185 | self.fee = jsonInput.get("fee") 186 | self.reserve = jsonInput.get("reserve") 187 | self.dust = jsonInput.get("dust") 188 | self.updated_at = jsonInput.get("updated_at") 189 | def __str__(self): 190 | result = "\n" 191 | prefix = " " 192 | if self.label != "": 193 | result += prefix + "tag : %s\n"%self.label 194 | 195 | if self.public_key != "": 196 | result += prefix + "Address : %s\n"%self.public_key 197 | 198 | if self.account_name!= "": 199 | result += prefix + "Account name : %s\n"%self.account_name 200 | 201 | if self.account_tag!= "": 202 | result += prefix + "Account memo : %s\n"%self.account_tag 203 | result += prefix + "fee : %s\n"%self.fee 204 | result += prefix + "dust : %s\n"%self.dust 205 | return result 206 | def Address_list(jsonInputList): 207 | result = [] 208 | for i in jsonInputList: 209 | result.append(Address(i)) 210 | return result 211 | 212 | def Asset_list(jsonInputList): 213 | result = [] 214 | for i in jsonInputList: 215 | result.append(Asset(i)) 216 | return result 217 | 218 | def Snapshot_list(jsonInputList): 219 | result = [] 220 | for i in jsonInputList: 221 | result.append(Snapshot(i)) 222 | return result 223 | 224 | 225 | 226 | 227 | 228 | class User_result(): 229 | def __init__(self, data_dict): 230 | self.user_id = data_dict.get("user_id") 231 | self.full_name = data_dict.get("full_name") 232 | self.has_pin = data_dict.get("has_pin") 233 | self.type = data_dict.get("type") 234 | self.created_at = data_dict.get("created_at") 235 | self.session_id = data_dict.get("session_id") 236 | 237 | def __str__(self): 238 | """Format: Name on the first line 239 | and all grades on the second line, 240 | separated by spaces. 241 | """ 242 | result = "" 243 | result += self.full_name + " is created at " + self.created_at + " with user id:" + self.user_id 244 | if self.has_pin: 245 | result += ". Pin is created" 246 | else: 247 | result += ". wallet need to create pin" 248 | return result 249 | 250 | class Transfer_result(): 251 | def __init__(self, data_dict): 252 | 253 | self.amount = data_dict.get("amount") 254 | self.memo = data_dict.get("memo") 255 | self.snapshot_id = data_dict.get("snapshot_id") 256 | self.asset_id = data_dict.get("asset_id") 257 | self.type = data_dict.get("type") 258 | self.trace_id = data_dict.get("trace_id") 259 | self.opponent_id = data_dict.get("opponent_id") 260 | self.created_at = data_dict.get("created_at") 261 | 262 | def __str__(self): 263 | """Format: Name on the first line 264 | and all grades on the second line, 265 | separated by spaces. 266 | """ 267 | result = "Successfully transfer %s %s to %s at %s with trace id:%s, snapshot id:%s"%(self.amount, self.asset_id, self.opponent_id, self.created_at, self.trace_id, self.snapshot_id) 268 | return result 269 | def fetchTokenForCreateUser(body, url): 270 | body_in_json = json.dumps(body) 271 | headers = { 272 | 'Content-Type' : 'application/json', 273 | } 274 | r = requests.post(url, json=body, headers=headers) 275 | result_obj = r.json() 276 | return result_obj.get("token") 277 | 278 | 279 | class WalletRecord(): 280 | def __init__(self, pin, userid, session_id, pin_token, private_key): 281 | self.pin = pin 282 | self.userid = userid 283 | self.session_id = session_id 284 | self.pin_token = pin_token 285 | self.private_key = private_key 286 | self.mixinAPIInstance = generateMixinAPI(self.private_key, 287 | self.pin_token, 288 | self.session_id, 289 | self.userid, 290 | self.pin,"") 291 | 292 | def create_wallet(self, session_key, account_name, token_for_create_wallet): 293 | userInfoJson = self.mixinAPIInstance.createUser(session_key, account_name, token_for_create_wallet) 294 | created_user_result = Mixin_Wallet_API_Result(userInfoJson, userInfo) 295 | return created_user_result 296 | def get_balance(self): 297 | all_assets_json = self.mixinAPIInstance.getMyAssets() 298 | all_balance = Mixin_Wallet_API_Result(all_assets_json, Asset_list) 299 | return all_balance 300 | 301 | def get_singleasset_balance(self, input_asset_id): 302 | single_asset_json = self.mixinAPIInstance.getAsset(input_asset_id) 303 | return Mixin_Wallet_API_Result(single_asset_json, Asset) 304 | def get_asset_withdrawl_addresses(self, input_asset_id): 305 | asset_addresses_json = self.mixinAPIInstance.withdrawals_address(input_asset_id) 306 | asset_withdraw_addresses = Mixin_Wallet_API_Result(asset_addresses_json, Address_list) 307 | return asset_withdraw_addresses 308 | def create_address(self, asset_id, public_key = "", label = "", asset_pin = "", account_name = "", account_tag = ""): 309 | create_result_json = self.mixinAPIInstance.createAddress(asset_id, public_key , label , asset_pin , account_name , account_tag ) 310 | createAddress_result = Mixin_Wallet_API_Result(create_result_json ,Address) 311 | return createAddress_result 312 | def remove_address(self, to_be_deleted_address_id, input_pin): 313 | remove_result_json = self.mixinAPIInstance.delAddress(to_be_deleted_address_id, input_pin) 314 | removeAddress_result = Mixin_Wallet_API_Result(remove_result_json) 315 | return removeAddress_result 316 | 317 | def transfer_to(self, destination_uuid, asset_id, amount_tosend, memo_input, this_uuid, asset_pin_input): 318 | transfer_result_json = self.mixinAPIInstance.transferTo(destination_uuid, asset_id, amount_tosend, memo_input, this_uuid, asset_pin_input) 319 | transfer_result = Mixin_Wallet_API_Result(transfer_result_json, Transfer_result) 320 | return transfer_result 321 | 322 | print(transfer_result_json) 323 | def withdraw_asset_to(self, address_id, withdraw_amount, withdraw_memo, withdraw_this_uuid, withdraw_asset_pin): 324 | asset_withdraw_result_json = self.mixinAPIInstance.withdrawals(address_id, withdraw_amount, withdraw_memo, withdraw_this_uuid, withdraw_asset_pin) 325 | withdraw_result = Mixin_Wallet_API_Result(asset_withdraw_result_json, Withdrawal) 326 | return withdraw_result 327 | def fetch_my_profile(self): 328 | my_profile_json = self.mixinAPIInstance.getMyProfile("") 329 | user_result = Mixin_Wallet_API_Result(my_profile_json, User_result) 330 | return user_result 331 | 332 | def verify_pin(self, input_pin): 333 | verify_pin_result_json = self.mixinAPIInstance.verifyPin(input_pin) 334 | user_result = Mixin_Wallet_API_Result(verify_pin_result_json, User_result) 335 | return user_result 336 | 337 | def update_pin(self, input_old_pin, input_new_pin): 338 | update_pin_result_json = self.mixinAPIInstance.updatePin(input_new_pin, input_old_pin) 339 | user_result = Mixin_Wallet_API_Result(update_pin_result_json, User_result) 340 | return user_result 341 | 342 | def my_snapshots_after(self, timestamp, asset_id = "", limit = 500, retry = 10): 343 | counter = 0 344 | mysnapshots_result = [] 345 | last_time = timestamp 346 | while((len(mysnapshots_result) < limit ) and ((time.time() - iso8601.parse_date(last_time).timestamp()) > 2)): 347 | counter += 1 348 | snapshots_json = self.mixinAPIInstance.account_snapshots_after(last_time, asset_id, 500) 349 | snapshots_list_result = Mixin_Wallet_API_Result(snapshots_json, Snapshot_list) 350 | if(snapshots_list_result.is_success): 351 | snapshots_result = snapshots_list_result.data 352 | last_time = snapshots_result[-1].created_at 353 | for singleSnapShot in snapshots_result: 354 | if (singleSnapShot.is_my_snap()): 355 | mysnapshots_result.append(singleSnapShot) 356 | else: 357 | break 358 | return mysnapshots_result 359 | 360 | def find_snapshot_of(client_id, in_snapshots): 361 | mysnapshots_result = [] 362 | for singleSnapShot in in_snapshots: 363 | if (singleSnapShot.user_id == client_id): 364 | mysnapshots_result.append(singleSnapShot) 365 | return mysnapshots_result 366 | 367 | def append_wallet_into_csv_file(this_wallet, file_name): 368 | with open(file_name, 'a', newline='') as csvfile: 369 | csvwriter = csv.writer(csvfile) 370 | csvwriter.writerow([this_wallet.private_key, 371 | this_wallet.pin_token, 372 | this_wallet.session_id, 373 | this_wallet.user_id, 374 | ""]) 375 | 376 | def write_wallet_into_clear_base64_file(this_wallet, file_name): 377 | finalObj = {"uid":this_wallet.user_id, "sid":this_wallet.session_id, "pintoken":this_wallet.pin_token, "key":this_wallet.private_key} 378 | jsonstring_fromobj = json.dumps(finalObj) 379 | base64decoded_json = base64.b64encode(jsonstring_fromobj.encode('utf-8')).decode('utf-8') 380 | print(base64decoded_json) 381 | with open(file_name, 'w') as wallet_file: 382 | wallet_file.write(base64decoded_json) 383 | 384 | def load_wallet_from_clear_base64_file(file_name): 385 | with open(file_name) as wallt_file: 386 | base64decoded_json = wallt_file.read() 387 | jsonstring = base64.b64decode(base64decoded_json) 388 | wallet_dict = json.loads(jsonstring) 389 | this_wallet_inst = WalletRecord("", wallet_dict.get("uid"), wallet_dict.get("sid"), wallet_dict.get("pintoken"), wallet_dict.get("key")) 390 | return this_wallet_inst 391 | 392 | 393 | def load_wallet_csv_file(file_name): 394 | with open(file_name, newline='') as csvfile: 395 | reader = csv.reader(csvfile) 396 | 397 | wallet_records = [] 398 | for row in reader: 399 | 400 | pin = row.pop() 401 | userid = row.pop() 402 | session_id = row.pop() 403 | pin_token = row.pop() 404 | private_key = row.pop() 405 | wallet_records.append(WalletRecord(pin, userid, session_id, pin_token, private_key)) 406 | return wallet_records 407 | def create_wallet_csv_file(file_name): 408 | with open(file_name, newline='') as csvfile: 409 | reader = csv.reader(csvfile) 410 | 411 | wallet_records = [] 412 | for row in reader: 413 | 414 | pin = row.pop() 415 | userid = row.pop() 416 | session_id = row.pop() 417 | pin_token = row.pop() 418 | private_key = row.pop() 419 | wallet_records.append(WalletRecord(pin, userid, session_id, pin_token, private_key)) 420 | return wallet_records 421 | --------------------------------------------------------------------------------