├── .gitignore ├── Bitcoin_Wallet_Mixin.py ├── Bitcoin_python.jpg ├── README-russian.md ├── README-zhchs.md ├── README.md ├── README2-russian.md ├── README2-zhchs.md ├── README2.md ├── README3-russian.md ├── README3-zhchs.md ├── README3.md ├── app-mini.py ├── app.py ├── app_card.jpg ├── button_test.py ├── call_apis.py ├── createUser.py ├── csv-test.py ├── freetoken.py ├── json-test.py ├── mixin_api.py ├── mixin_config-example.py ├── mixin_config.py ├── mixin_ws_api.py ├── objapi.py ├── pin-test.py ├── pyvenv.cfg ├── requirements.txt ├── requirements2.txt ├── ws-test2.py └── ws_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | bin 3 | lib 4 | -------------------------------------------------------------------------------- /Bitcoin_Wallet_Mixin.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | from mixin_api import MIXIN_API 3 | import uuid 4 | import mixin_config 5 | import json 6 | import csv 7 | import time 8 | import uuid 9 | import umsgpack 10 | import base64 11 | 12 | PIN = "945689"; 13 | PIN2 = "845689"; 14 | MASTER_ID = "37222956"; 15 | EXINCORE_UUID = "61103d28-3ac2-44a2-ae34-bd956070dab1" 16 | MASTER_UUID = "28ee416a-0eaa-4133-bc79-9676909b7b4e"; 17 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 18 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 19 | USDT_ASSET_ID = "815b0b1a-2764-3736-8faa-42d694fa620a" 20 | BTC_WALLET_ADDR = "14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C"; 21 | AMOUNT = "0.001"; 22 | 23 | # // Mixin Network support cryptocurrencies (2019-02-19) 24 | # // |EOS|6cfe566e-4aad-470b-8c9a-2fd35b49c68d 25 | # // |CNB|965e5c6e-434c-3fa9-b780-c50f43cd955c 26 | # // |BTC|c6d0c728-2624-429b-8e0d-d9d19b6592fa 27 | # // |ETC|2204c1ee-0ea2-4add-bb9a-b3719cfff93a 28 | # // |XRP|23dfb5a5-5d7b-48b6-905f-3970e3176e27 29 | # // |XEM|27921032-f73e-434e-955f-43d55672ee31 30 | # // |ETH|43d61dcd-e413-450d-80b8-101d5e903357 31 | # // |DASH|6472e7e3-75fd-48b6-b1dc-28d294ee1476 32 | # // |DOGE|6770a1e5-6086-44d5-b60f-545f9d9e8ffd 33 | # // |LTC|76c802a2-7c88-447f-a93e-c29c9e5dd9c8 34 | # // |SC|990c4c29-57e9-48f6-9819-7d986ea44985 35 | # // |ZEN|a2c5d22b-62a2-4c13-b3f0-013290dbac60 36 | # // |ZEC|c996abc9-d94e-4494-b1cf-2a3fd3ac5714 37 | # // |BCH|fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0 38 | 39 | def pubkeyContent(inputContent): 40 | contentWithoutHeader= inputContent[len("-----BEGIN PUBLIC KEY-----") + 1:] 41 | contentWithoutTail = contentWithoutHeader[:-1 * (len("-----END PUBLIC KEY-----") + 1)] 42 | contentWithoutReturn = contentWithoutTail[:64] + contentWithoutTail[65:129] + contentWithoutTail[130:194] + contentWithoutTail[195:] 43 | return contentWithoutReturn 44 | 45 | def generateMixinAPI(private_key,pin_token,session_id,user_id,pin,client_secret): 46 | mixin_config.private_key = private_key 47 | mixin_config.pin_token = pin_token 48 | mixin_config.pay_session_id = session_id 49 | mixin_config.client_id = user_id 50 | mixin_config.client_secret = client_secret 51 | mixin_config.pay_pin = pin 52 | return MIXIN_API(mixin_config) 53 | 54 | def readAssetBalance(asset_id): 55 | with open('new_users.csv', newline='') as csvfile: 56 | reader = csv.reader(csvfile) 57 | for row in reader: 58 | pin = row.pop() 59 | userid = row.pop() 60 | session_id = row.pop() 61 | pin_token = row.pop() 62 | private_key = row.pop() 63 | mixinApiNewUserInstance = generateMixinAPI(private_key, 64 | pin_token, 65 | session_id, 66 | userid, 67 | pin,"") 68 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 69 | print("Account %s \'s balance is %s " %(userid,btcInfo.get("data").get("balance"))) 70 | 71 | def readAssetAddress(asset_id,isBTC = True): 72 | with open('new_users.csv', newline='') as csvfile: 73 | reader = csv.reader(csvfile) 74 | for row in reader: 75 | pin = row.pop() 76 | userid = row.pop() 77 | session_id = row.pop() 78 | pin_token = row.pop() 79 | private_key = row.pop() 80 | mixinApiNewUserInstance = generateMixinAPI(private_key, 81 | pin_token, 82 | session_id, 83 | userid, 84 | pin,"") 85 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 86 | print(btcInfo) 87 | if isBTC: 88 | print("Account %s \'s Bitcoin wallet address is %s " %(userid,btcInfo.get("data").get("public_key"))) 89 | else: 90 | print("Account %s \'s EOS account name is %s, wallet address is %s " %(userid, 91 | btcInfo.get("data").get("account_name"), 92 | btcInfo.get("data").get("account_tag"))) 93 | 94 | # print(btcInfo.get("data").get("public_key")) 95 | def gen_memo_ExinBuy(asset_id_string): 96 | return base64.b64encode(umsgpack.packb({"A": uuid.UUID("{" + asset_id_string + "}").bytes})).decode("utf-8") 97 | 98 | def asset_balance(mixinApiInstance, asset_id): 99 | asset_result = mixinApiInstance.getAsset(asset_id) 100 | assetInfo = asset_result.get("data") 101 | return assetInfo.get("balance") 102 | 103 | 104 | def btc_balance_of(mixinApiInstance): 105 | return asset_balance(BTC_ASSET_ID) 106 | def usdt_balance_of(mixinApiInstance): 107 | return asset_balance(USDT_ASSET_ID) 108 | 109 | def strPresent_of_asset_withdrawaddress(thisAddress, asset_id): 110 | address_id = thisAddress.get("address_id") 111 | address_pubkey = thisAddress.get("public_key") 112 | address_label = thisAddress.get("label") 113 | address_accountname = thisAddress.get("account_name") 114 | address_accounttag = thisAddress.get("account_tag") 115 | address_fee = thisAddress.get("fee") 116 | address_dust = thisAddress.get("dust") 117 | Address = "tag: %s, id: %s, address: %s, fee: %s, dust: %s"%(address_label, address_id, address_pubkey, address_fee, address_dust) 118 | return Address 119 | 120 | def strPresent_of_btc_withdrawaddress(thisAddress): 121 | return strPresent_of_asset_withdrawaddress(thisAddress, BTC_ASSET_ID) 122 | 123 | def strPresent_of_usdt_withdrawaddress(thisAddress): 124 | return strPresent_of_asset_withdrawaddress(thisAddress, USDT_ASSET_ID) 125 | 126 | def remove_withdraw_address_of(mixinApiUserInstance, withdraw_asset_id, withdraw_asset_name): 127 | USDT_withdraw_addresses_result = mixinApiUserInstance.withdrawals_address(withdraw_asset_id) 128 | USDT_withdraw_addresses = USDT_withdraw_addresses_result.get("data") 129 | i = 0 130 | print("%s withdraw address is:======="%withdraw_asset_name) 131 | for eachAddress in USDT_withdraw_addresses: 132 | usdtAddress = strPresent_of_usdt_withdrawaddress(eachAddress) 133 | print("index %d, %s"%(i, usdtAddress)) 134 | i = i + 1 135 | 136 | userselect = input("which address index you want to remove") 137 | if (int(userselect) < i): 138 | eachAddress = USDT_withdraw_addresses[int(userselect)] 139 | address_id = eachAddress.get("address_id") 140 | Address = "index %d: %s"%(int(userselect), strPresent_of_asset_withdrawaddress(eachAddress, withdraw_asset_id)) 141 | confirm = input("Type YES to remove " + Address + "!!:") 142 | if (confirm == "YES"): 143 | input_pin = input("pin:") 144 | mixinApiUserInstance.delAddress(address_id, input_pin) 145 | 146 | def withdraw_asset(withdraw_asset_id, withdraw_asset_name): 147 | this_asset_balance = asset_balance(mixinApiNewUserInstance, withdraw_asset_id) 148 | withdraw_amount = input("%s %s in your account, how many %s you want to withdraw: "%(withdraw_asset_name, this_asset_balance, withdraw_asset_name)) 149 | withdraw_addresses_result = mixinApiNewUserInstance.withdrawals_address(withdraw_asset_id) 150 | withdraw_addresses = withdraw_addresses_result.get("data") 151 | i = 0 152 | print("current " + withdraw_asset_name +" address:=======") 153 | for eachAddress in withdraw_addresses: 154 | Address = "index %d: %s"%(i, strPresent_of_asset_withdrawaddress(eachAddress, withdraw_asset_id)) 155 | print(Address) 156 | i = i + 1 157 | 158 | userselect = input("which address index is your destination") 159 | if (int(userselect) < i): 160 | eachAddress = withdraw_addresses[int(userselect)] 161 | address_id = eachAddress.get("address_id") 162 | address_pubkey = eachAddress.get("public_key") 163 | address_selected = "index %d: %s"%(int(userselect), strPresent_of_asset_withdrawaddress(eachAddress, withdraw_asset_id)) 164 | confirm = input("Type YES to withdraw " + withdraw_amount + withdraw_asset_name + " to " + address_selected + "!!:") 165 | if (confirm == "YES"): 166 | this_uuid = str(uuid.uuid1()) 167 | asset_pin = input("pin:") 168 | asset_withdraw_result = mixinApiNewUserInstance.withdrawals(address_id, withdraw_amount, "withdraw2"+address_pubkey, this_uuid, asset_pin) 169 | return asset_withdraw_result 170 | return None 171 | 172 | 173 | 174 | mixinApiBotInstance = MIXIN_API(mixin_config) 175 | 176 | PromptMsg = "Read first user from local file new_users.csv : loaduser\n" 177 | PromptMsg += "Read account asset balance : balance\n" 178 | PromptMsg += "Read Bitcoin : btcbalance\n" 179 | PromptMsg += "Read USDT : usdtbalance\n" 180 | PromptMsg += "Read transaction of my account : searchsnapshots\n" 181 | PromptMsg += "Read one snapshots info of account : snapshot\n" 182 | PromptMsg += "Pay USDT to ExinCore to buy BTC : buybtc\n" 183 | PromptMsg += "Create wallet and update PIN : create\n" 184 | PromptMsg += "transafer all asset to my account in Mixin Messenger : allmoney\n" 185 | PromptMsg += "List account withdraw address : listaddress\n" 186 | PromptMsg += "Add new withdraw address for Bitcoin : addbitcoinaddress\n" 187 | PromptMsg += "Add new withdraw address for USDT : addusdtaddress\n" 188 | PromptMsg += "Remove withdraw address for Bitcoin : removebtcaddress\n" 189 | PromptMsg += "Remove withdraw address for Bitcoin : removeusdtaddress\n" 190 | PromptMsg += "Withdraw BTC : withdrawbtc\n" 191 | PromptMsg += "Withdraw USDT : withdrawusdt\n" 192 | PromptMsg += "verify pin : verifypin\n" 193 | PromptMsg += "updatepin : updatepin\n" 194 | PromptMsg += "Exit : q\n" 195 | while ( 1 > 0 ): 196 | cmd = input(PromptMsg) 197 | if (cmd == 'q' ): 198 | exit() 199 | print("Run...") 200 | if ( cmd == 'loaduser'): 201 | with open('new_users.csv', newline='') as csvfile: 202 | reader = csv.reader(csvfile) 203 | row = next(reader) 204 | pin = row.pop() 205 | userid = row.pop() 206 | session_id = row.pop() 207 | pin_token = row.pop() 208 | private_key = row.pop() 209 | mixinApiNewUserInstance = generateMixinAPI(private_key, 210 | pin_token, 211 | session_id, 212 | userid, 213 | pin,"") 214 | print("read user id:" + userid) 215 | if ( cmd == 'balance' ): 216 | AssetsInfo = mixinApiNewUserInstance.getMyAssets() 217 | print("Your asset balance is\n===========") 218 | for eachAssest in AssetsInfo: 219 | print("%s: %s" %(eachAssest.get("name"), eachAssest.get("balance"))) 220 | print("===========") 221 | 222 | 223 | availableAssset = [] 224 | for eachAssetInfo in AssetsInfo: 225 | if (eachAssetInfo.get("balance") == "0"): 226 | continue 227 | if (float(eachAssetInfo.get("balance")) > 0): 228 | availableAssset.append(eachAssetInfo) 229 | if ( cmd == 'btcbalance' ): 230 | asset_result = mixinApiNewUserInstance.getAsset(BTC_ASSET_ID) 231 | btcInfo = asset_result.get("data") 232 | print("%s: %s, depositAddress: %s" %(btcInfo.get("name"), btcInfo.get("balance"), btcInfo.get("public_key"))) 233 | 234 | if ( cmd == 'usdtbalance' ): 235 | asset_result = mixinApiNewUserInstance.getAsset(USDT_ASSET_ID) 236 | usdtInfo = asset_result.get("data") 237 | print("%s: %s, depositAddress: %s" %(usdtInfo.get("name"), usdtInfo.get("balance"), usdtInfo.get("public_key"))) 238 | if ( cmd == 'snapshot'): 239 | input_snapshotid = input('input snapshots id') 240 | print(mixinApiNewUserInstance.account_snapshot(input_snapshotid)) 241 | if ( cmd == 'searchsnapshots'): 242 | timestamp = input("input timestamp, history after the time will be searched:") 243 | limit = input("input max record you want to search:") 244 | snapshots_result_of_account = mixinApiNewUserInstance.account_snapshots_after(timestamp, asset_id = "", limit=limit) 245 | USDT_Snapshots_result_of_account = mixinApiNewUserInstance.find_mysnapshot_in(snapshots_result_of_account) 246 | for singleSnapShot in USDT_Snapshots_result_of_account: 247 | print(singleSnapShot) 248 | amount_snap = singleSnapShot.get("amount") 249 | asset_snap = singleSnapShot.get("asset").get("name") 250 | created_at_snap = singleSnapShot.get("created_at") 251 | memo_at_snap = singleSnapShot.get("data") 252 | id_snapshot = singleSnapShot.get("snapshot_id") 253 | opponent_id_snapshot = singleSnapShot.get("opponent_id") 254 | if((float(amount_snap)) < 0): 255 | try: 256 | exin_order = umsgpack.unpackb(base64.b64decode(memo_at_snap)) 257 | asset_uuid_in_myorder = str(uuid.UUID(bytes = exin_order["A"])) 258 | if(asset_uuid_in_myorder == BTC_ASSET_ID): 259 | print(created_at_snap + ": You pay " + amount_snap + " " + asset_snap + " to buy BTC from ExinCore") 260 | except : 261 | print(created_at_snap + ": You pay " + str(amount_snap) + " " + asset_snap + " to " + opponent_id_snapshot + " with memo:" + memo_at_snap) 262 | 263 | if((float(amount_snap)) > 0 and memo_at_snap): 264 | try: 265 | exin_order = umsgpack.unpackb(base64.b64decode(memo_at_snap)) 266 | if ("C" in exin_order): 267 | order_result = exin_order["C"] 268 | headString = created_at_snap +": status of your payment to exin is : " 269 | if(order_result == 1000): 270 | headString = headString + "Successful Exchange" 271 | if(order_result == 1001): 272 | headString = headString + "The order not found or invalid" 273 | if(order_result == 1002): 274 | headString = headString + "The request data is invalid" 275 | if(order_result == 1003): 276 | headString = headString + "The market not supported" 277 | if(order_result == 1004): 278 | headString = headString + "Failed exchange" 279 | if(order_result == 1005): 280 | headString = headString + "Partial exchange" 281 | if(order_result == 1006): 282 | headString = headString + "Insufficient pool" 283 | if(order_result == 1007): 284 | headString = headString + "Below the minimum exchange amount" 285 | if(order_result == 1008): 286 | headString = headString + "Exceeding the maximum exchange amount" 287 | if ("P" in exin_order): 288 | headString = headString + ", your order is executed at price:" + exin_order["P"] + " USDT" + " per " + asset_snap 289 | if ("F" in exin_order): 290 | headString = headString + ", Exin core fee is " + exin_order["F"] + " with fee asset" + str(uuid.UUID(bytes = exin_order["FA"])) 291 | if ("T" in exin_order): 292 | if (exin_order["T"] == "F"): 293 | headString = headString +", your order is refund to you because your memo is not correct" 294 | if (exin_order["T"] == "R"): 295 | headString = headString +", your order is executed successfully" 296 | if (exin_order["T"] == "E"): 297 | headString = headString +", exin failed to execute your order" 298 | if ("O" in exin_order): 299 | headString = headString +", trace id of your payment to exincore is " + str(uuid.UUID(bytes = exin_order["O"])) 300 | print(headString) 301 | except : 302 | print(created_at_snap +": You receive: " + str(amount_snap) + " " + asset_snap + " from " + opponent_id_snapshot + " with memo:" + memo_at_snap) 303 | 304 | if ( cmd == 'buybtc' ): 305 | # Pack memo 306 | memo_for_exin = gen_memo_ExinBuy(BTC_ASSET_ID) 307 | 308 | btcInfo = mixinApiNewUserInstance.getAsset(USDT_ASSET_ID) 309 | remainUSDT = btcInfo.get("data").get("balance") 310 | print("You have : " + remainUSDT + " USDT") 311 | this_uuid = str(uuid.uuid1()) 312 | print("uuid is: " + this_uuid) 313 | confirm_payUSDT = input("Input Yes to pay " + remainUSDT + " to ExinCore to buy Bitcoin") 314 | if ( confirm_payUSDT == "Yes" ): 315 | input_pin = input("pin code:") 316 | transfer_result = mixinApiNewUserInstance.transferTo(EXINCORE_UUID, USDT_ASSET_ID, remainUSDT, memo_for_exin, this_uuid, input_pin) 317 | snapShotID = transfer_result.get("data").get("snapshot_id") 318 | print("Pay USDT to ExinCore to buy BTC by uuid:" + this_uuid + ", you can verify the result on https://mixin.one/snapshots/" + snapShotID) 319 | if ( cmd == 'create' ): 320 | key = RSA.generate(1024) 321 | pubkey = key.publickey() 322 | print(key.exportKey()) 323 | print(pubkey.exportKey()) 324 | private_key = key.exportKey() 325 | session_key = pubkeyContent(pubkey.exportKey()) 326 | # print(session_key) 327 | input_session = session_key.decode() 328 | account_name = "Tom Bot" 329 | print(session_key.decode()) 330 | body = { 331 | "session_secret": input_session, 332 | "full_name": account_name 333 | } 334 | token_from_freeweb = mixinApiBotInstance.fetchTokenForCreateUser(body, "http://freemixinapptoken.myrual.me/token") 335 | userInfo = mixinApiBotInstance.createUser(input_session, account_name, token_from_freeweb) 336 | print(userInfo.get("data").get("user_id")) 337 | with open('new_users.csv', 'a', newline='') as csvfile: 338 | csvwriter = csv.writer(csvfile) 339 | csvwriter.writerow([private_key.decode(), 340 | userInfo.get("data").get("pin_token"), 341 | userInfo.get("data").get("session_id"), 342 | userInfo.get("data").get("user_id"), 343 | ""]) 344 | mixinApiNewUserInstance = generateMixinAPI(private_key.decode(), 345 | userInfo.get("data").get("pin_token"), 346 | userInfo.get("data").get("session_id"), 347 | userInfo.get("data").get("user_id"), 348 | "","") 349 | defauled_pin = input("input pin:") 350 | pinInfo = mixinApiNewUserInstance.updatePin(defauled_pin,"") 351 | print(pinInfo) 352 | time.sleep(3) 353 | pinInfo2 = mixinApiNewUserInstance.verifyPin(defauled_pin) 354 | print(pinInfo2) 355 | 356 | # c6d0c728-2624-429b-8e0d-d9d19b6592fa 357 | if ( cmd == 'allmoney' ): 358 | AssetsInfo = mixinApiNewUserInstance.getMyAssets() 359 | availableAssset = [] 360 | my_pin = input("pin:") 361 | for eachAssetInfo in AssetsInfo: 362 | if (eachAssetInfo.get("balance") == "0"): 363 | continue 364 | if (float(eachAssetInfo.get("balance")) > 0): 365 | availableAssset.append(eachAssetInfo) 366 | print("You have : " + eachAssetInfo.get("balance") + eachAssetInfo.get("name")) 367 | this_uuid = str(uuid.uuid1()) 368 | print("uuid is: " + this_uuid) 369 | confirm_pay= input("type YES to pay " + eachAssetInfo.get("balance")+ " to MASTER:") 370 | if ( confirm_pay== "YES" ): 371 | transfer_result = mixinApiNewUserInstance.transferTo(MASTER_UUID, eachAssetInfo.get("asset_id"), eachAssetInfo.get("balance"), "", this_uuid, my_pin) 372 | snapShotID = transfer_result.get("data").get("snapshot_id") 373 | created_at = transfer_result.get("data").get("created_at") 374 | print(created_at + ":Pay BTC to Master ID with trace id:" + this_uuid + ", you can verify the result on https://mixin.one/snapshots/" + snapShotID) 375 | if ( cmd == 'listaddress' ): 376 | BTC_withdraw_addresses_result = mixinApiNewUserInstance.withdrawals_address(BTC_ASSET_ID) 377 | BTC_withdraw_addresses = BTC_withdraw_addresses_result.get("data") 378 | print("BTC address is:=======") 379 | for eachAddress in BTC_withdraw_addresses: 380 | btcAddress = strPresent_of_btc_withdrawaddress(eachAddress) 381 | print(btcAddress) 382 | print("USDT address is:=======") 383 | USDT_withdraw_addresses_result = mixinApiNewUserInstance.withdrawals_address(USDT_ASSET_ID) 384 | USDT_withdraw_addresses = USDT_withdraw_addresses_result.get("data") 385 | for eachAddress in USDT_withdraw_addresses: 386 | usdtAddress = strPresent_of_btc_withdrawaddress(eachAddress) 387 | print(usdtAddress) 388 | 389 | if ( cmd == 'addbitcoinaddress' ): 390 | BTC_depost_address = input("Bitcoin withdraw address:") 391 | Confirm = input(BTC_depost_address + ", Type YES to confirm") 392 | if (Confirm == "YES"): 393 | tag_content = input("write a tag") 394 | input_pin = input("pin:") 395 | add_BTC_withdraw_addresses_result = mixinApiNewUserInstance.createAddress(BTC_ASSET_ID, BTC_depost_address, tag_content, asset_pin = input_pin) 396 | address_id = add_BTC_withdraw_addresses_result.get("data").get("address_id") 397 | print("the address :" + BTC_depost_address + " is added to your account with id:" + address_id) 398 | if ( cmd == 'addusdtaddress' ): 399 | USDT_depost_address = input("usdt withdraw address:") 400 | Confirm = input(USDT_depost_address + ", Type YES to confirm") 401 | if (Confirm == "YES"): 402 | tag_content = input("tag:") 403 | input_pin = input("pin:") 404 | 405 | USDT_withdraw_addresses = mixinApiNewUserInstance.createAddress(USDT_ASSET_ID, USDT_depost_address, tag_content, asset_pin = input_pin) 406 | address_id = USDT_withdraw_addresses.get("data").get("address_id") 407 | print("the address :" + BTC_depost_address + " is added to your account with id:" + address_id) 408 | 409 | if ( cmd == 'removebtcaddress' ): 410 | remove_withdraw_address_of(mixinApiNewUserInstance, BTC_ASSET_ID, "BTC") 411 | 412 | if ( cmd == "removeusdtaddress"): 413 | remove_withdraw_address_of(mixinApiNewUserInstance, USDT_ASSET_ID, "USDT") 414 | 415 | if ( cmd == 'withdrawbtc' ): 416 | result = withdraw_asset(BTC_ASSET_ID, "BTC") 417 | if (result != None): 418 | print(result) 419 | 420 | if ( cmd == 'withdrawusdt' ): 421 | withdraw_asset_id = USDT_ASSET_ID 422 | withdraw_asset_name = "usdt" 423 | result = withdraw_asset(USDT_ASSET_ID, "USDT") 424 | if (result != None): 425 | print(result) 426 | 427 | if ( cmd == 'verifypin' ): 428 | input_pin = input("input your account pin:") 429 | print(mixinApiNewUserInstance.verifyPin(input_pin)) 430 | if ( cmd == 'updatepin' ): 431 | newPin = input("input new pin:") 432 | oldPin = input("input old pin:") 433 | print(mixinApiNewUserInstance.updatePin(newPin,oldPin)) 434 | -------------------------------------------------------------------------------- /Bitcoin_python.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenewzhang/mixin_labs-python-bot/3ba2734693512097f421416807624f8100990bc6/Bitcoin_python.jpg -------------------------------------------------------------------------------- /README-russian.md: -------------------------------------------------------------------------------- 1 | # Руководство Python Bitcoin для Mixin Network 2 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 3 | В этом руководстве будет создан бот для мессенджера Mixin. Бот написан на языке python и позволяет пересылать сообщения и Bitcoin от пользователя. 4 | 5 | Полный список сетевых ресурсов Mixin [список](https://github.com/awesome-mixin-network/index_of_Mixin_Network_resource) 6 | 7 | ## Что Вы изучите в этом руководстве 8 | 1. [Как создать бота для мессенджера Mixin и ответить на сообщение пользователя](https://github.com/wenewzhang/mixin_labs-python-bot#create-bot-and-receive-message-from-user) 9 | 2. [Как получить и отправить Bitcoin в мессенджере Mixin](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README2.md) 10 | 3. [Как создать кошелек Bitcoin при помощи Mixin Network API](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README3.md) 11 | ## Как создать бота для мессенджера Mixin и ответить на сообщение пользователя 12 | ## Установка Python 3: 13 | Это руководство написано для Python 3.7.2, поэтому Вы должны установить Python 3.7.2 или более новый. 14 | 15 | macOS 16 | ```bash 17 | brew upgrade 18 | brew install python@3 19 | ``` 20 | 21 | Ubuntu, установка python 3.7.2 из сторонних репозитариев. 22 | ```bash 23 | sudo apt update 24 | sudo apt install software-properties-common 25 | sudo add-apt-repository ppa:deadsnakes/ppa 26 | ``` 27 | 28 | Нажмите Enter для продолжения 29 | ```bash 30 | Press [ENTER] to continue or Ctrl-c to cancel adding it. 31 | ``` 32 | Обновите список репозитариев, затем установите python3.7, python3.7-venv 33 | ```bash 34 | sudo apt update 35 | sudo apt install python3.7 python3.7-venv 36 | sudo ln -s /usr/bin/python3.7 /usr/bin/python3 37 | ``` 38 | 39 | Проверьте, что оба компонента python3 and python3-venv установлены 40 | ```bash 41 | $ python3 -V 42 | Python 3.7.2 43 | ``` 44 | 45 | ```bash 46 | root@n2:~ python3 -m venv -h 47 | usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear] 48 | [--upgrade] [--without-pip] [--prompt PROMPT] 49 | ENV_DIR [ENV_DIR ...] 50 | Создает виртуальное окружение Python в одной или нескольких целевых папках: 51 | Creates virtual Python environments in one or more target directories. 52 | необходимый аргумент: 53 | ENV_DIR Папка, для которой создается окружение. 54 | 55 | дополнительные аргументы: 56 | -h, --help показывает этот хелп и закрывает окно 57 | --system-site-packages 58 | Дает виртуальному окружению доступ к папке с системными пакетами 59 | --symlinks Пытаться использовать символические ссылки, а не копии, 60 | даже когда симлинки не используются платформой "по умолчанию". 61 | --copies Пытаться использовать копии, на не символические ссылки, 62 | даже если символические ссылки используют платформой "по умолчанию". 63 | --clear Удалить содержимое папки окружения, если она существует, 64 | перед созданием окружения. 65 | --upgrade Обновить папку окружения для использования этой версии 66 | Python, при условии что он был обновлен "на месте". 67 | --without-pip Пропустить или обновить установку pip в виртуальном 68 | окружении (pip загружается по умолчанию) 69 | --prompt PROMPT Предоставляет альтернативный префикс приглашения для 70 | этой виртуальной среды. 71 | 72 | Как только окружение создано, Вы можете активировать его, например, 73 | путем поиска сценария активации в его папке bin. 74 | ``` 75 | 76 | ## Создаем проект mixin_labs-python-bot 77 | 78 | Вы должны создать папку проекта, сделать ее как виртуальное окружение python и установить необходимые пакеты. 79 | ```bash 80 | mkdir mixin_labs-python-bot 81 | cd mixin_labs-python-bot 82 | python3 -m venv ./ 83 | ``` 84 | 85 | Запускаем **python3 -m venv** , следующие файл и папка будут созданы: 86 | ```bash 87 | wenewzha:mixin_labs-python-bot wenewzhang$ ls 88 | bin include lib pyvenv.cfg 89 | ``` 90 | 91 | Как только виртуальное окружение создано, его можно "активировать", используя скрипт в папке bin виртуального окружения. 92 | ```bash 93 | wenewzha:mixin_labs-python-bot wenewzhang$ source ./bin/activate 94 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ 95 | ``` 96 | Так как “python” или "pip" вызываются из виртуального окружения, Вы можете запускать установленные скрипты без указания их полного пути. 97 | 98 | ## Установка необходимых пакетов с помощью "виртуального окружения" 99 | 100 | Создайте список требуемого 101 | > requirements.txt 102 | ```txt 103 | cryptography==2.4.2 104 | pycparser==2.19 105 | pycryptodome==3.7.2 106 | PyJWT==1.7.1 107 | python-dateutil==2.7.5 108 | PyYAML==3.13 109 | requests==2.21.0 110 | websocket-client==0.54.0 111 | ``` 112 | Используйте "pip" для обновления самомго себя и установите требующиеся пакеты из списка 113 | ```bash 114 | pip install --upgrade pip 115 | pip install -r requirements.txt 116 | ``` 117 | 118 | ## Загрузка mixin-network api 119 | ```bash 120 | wget https://github.com/wenewzhang/mixin-python3-sdk/raw/master/mixin_ws_api.py 121 | wget https://github.com/wenewzhang/mixin-python3-sdk/raw/master/mixin_api.py 122 | wget https://github.com/wenewzhang/mixin-python3-sdk/raw/master/mixin_config.py 123 | ``` 124 | 125 | ## Hello, world при помощи Python 126 | 127 | ### Создайте свое первое приложение в дашборде разработчика Mixin Network 128 | Вам необходимо создать приложение в дашборде. Это [руководство](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account) может помочь Вам. 129 | 130 | ### Генерация параметров приложения в дашборде 131 | После создания приложения в дашборде Вы должны [генерировать параметры](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account#generate-secure-parameter-for-your-app) и написать необходимое содержимое, это содержимое будет записано в файл mixin_config.py. 132 | 133 | ![mixin_network-keys](https://github.com/wenewzhang/mixin_labs-php-bot/raw/master/mixin_network-keys.jpg) 134 | В папке создайте файл: mixin_config.py. Скопируйте следующее содержимое в этот файл. 135 | > mixin_config.py 136 | ```python 137 | client_id= 'ed882a39-0b0c-4413-bbd9-221cdeee56bf' 138 | client_secret = '8d7ec7b9c8261b6c7bd6309210496ca4b72bce9efc7e49be14a428ce49ff7202' 139 | 140 | 141 | pay_pin = '599509' 142 | pay_session_id = 'bd53b6a4-e79a-49e5-ad04-36da518354f6' 143 | pin_token = "nVREh0/Ys9vzNFCQT2+PKcDN2OYAUSH8CidwHqDQLOCvokE7o6wtvLypjW9iks/RsnBM6N4SPF/P3bBW254YHGuDZXhitDEWOGkXs7v8BxMQxf+9454qTkMSpR9xbjAzgMXnSyHrNVoBtsc/Y+NvemB3VxPfsCOFHasiMqAa5DU=" 144 | 145 | 146 | private_key = """-----BEGIN RSA PRIVATE KEY----- 147 | MIICXQIBAAKBgQCnaoO1SdPxggEpAIUdM/8Ll4FOqlXK7vwURHr4FFi6hnQ1I79g 148 | pZSlJdzjr24WcIuNi6kVdXVIpyzZJGXS2I72dpGs5h1jKxL8AWIUVL2axZXqTJNi 149 | c4wj6GJ4gDRP2U9I9gae+S/frM6KP8TioV0OcbmrlfrwI0OElLH3363y1wIDAQAB 150 | AoGAduaGLi4F8cMkMculvqzcGY57jrQZBGyg6YANWb2Rmr+9LrR5yhkvLe9rJuXE 151 | KPm7k0a6SnxGVNguWPWpv4qAVVGAJ0eb8ETXTRO20HlKmcbxfFdDtHBDV3QufNa1 152 | h3mNEsqWDNCDdAm7p/EZwfG2F9+nmeXLfip7R1I72qbK0wkCQQDiJR6NEGVwbj8H 153 | K8kRpzY1D9lPqp1ZMrma5AFYGZIb5voTxLjRpYdxQJHi7CCdE1zgqJOXvA3jj/io 154 | f7bMIJY7AkEAvYSSC5H+fUKAjyjeCTGJBBKoPDsq+aALAYLWf77sGXE9BBmhhY0l 155 | iwmbj8X6/qZtQ0yEzdT/OSdiYL86CcrgFQJBALz/sMzMSzrvqJVhrqWmTdOC72d5 156 | fA+0KRKeQ9FRbZ8MJyymWKA96zhncoVoOsmMCS9pNBC4BhONm4+XTTrEcUkCQQCo 157 | DWB8Bg/G/yuExtZtDJHVHL41+rmW9UYNJvoR+TjfLrzOX/QMuyapbfGVwhdZrDaD 158 | UN0KsG9JPRVNeQR8HnwpAkACrr9cNp1H1bytHG9a6L+5cVHkRhqqEYWVO41MhgZF 159 | 5bIKx5OXCJB2VwY7fjFet2KxTHGfEZt/khjFNZzVX7lN 160 | -----END RSA PRIVATE KEY-----""" 161 | ``` 162 | Замените значеня содержимым, сгенерированным в дашборде. Создайте файл app-mini.py и заполните его содержимое текстом ниже: 163 | > app-mini.py 164 | ```python 165 | from mixin_ws_api import MIXIN_WS_API 166 | from mixin_api import MIXIN_API 167 | import mixin_config 168 | 169 | import json 170 | import time 171 | from io import BytesIO 172 | import base64 173 | import gzip 174 | 175 | try: 176 | import thread 177 | except ImportError: 178 | import _thread as thread 179 | 180 | 181 | def on_message(ws, message): 182 | inbuffer = BytesIO(message) 183 | 184 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 185 | rdata_injson = f.read() 186 | rdata_obj = json.loads(rdata_injson) 187 | print("-------json object begin---------") 188 | print(rdata_obj) 189 | print("-------json object end---------") 190 | action = rdata_obj["action"] 191 | 192 | if rdata_obj["data"] is not None: 193 | print("data in message:",rdata_obj["data"]) 194 | 195 | if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 196 | print(rdata_obj["data"]["category"]) 197 | 198 | if action == "CREATE_MESSAGE": 199 | 200 | data = rdata_obj["data"] 201 | msgid = data["message_id"] 202 | typeindata = data["type"] 203 | categoryindata = data["category"] 204 | userId = data["user_id"] 205 | conversationId = data["conversation_id"] 206 | dataindata = data["data"] 207 | 208 | realData = base64.b64decode(dataindata) 209 | 210 | MIXIN_WS_API.replayMessage(ws, msgid) 211 | 212 | if 'error' in rdata_obj: 213 | return 214 | 215 | if categoryindata == "PLAIN_TEXT": 216 | realData = realData.decode('utf-8') 217 | print("dataindata",realData) 218 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 219 | 220 | 221 | if __name__ == "__main__": 222 | 223 | # mixin_api = MIXIN_API(mixin_config) 224 | while True: 225 | mixin_ws = MIXIN_WS_API(on_message=on_message) 226 | mixin_ws.run() 227 | ``` 228 | 229 | Запустите app-mini.py, НЕ ЗАБУДЬТЕ активировать перед этим "виртуальное окружение" python" 230 | ```bash 231 | cd mixin_labs-python-bot 232 | wenewzha:mixin_labs-python-bot wenewzhang$ source ./bin/activate 233 | ``` 234 | ```bash 235 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app-mini.py 236 | ... 237 | ``` 238 | Если в консоли выводится следующее сообщение, значит, все прошло успешно. 239 | ```bash 240 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app-mini.py 241 | ws open 242 | -------json object begin--------- 243 | {'id': '1c798948-30eb-11e9-a20e-20c9d08850cd', 'action': 'LIST_PENDING_MESSAGES'} 244 | -------json object end--------- 245 | ``` 246 | 247 | Добавьте бота(например, id этого бота 7000101639) как Вашего друга в [Mixin Messenger](https://mixin.one/messenger) и отправьте ему сообщение. 248 | 249 | ![mixin_messenger](https://github.com/wenewzhang/mixin_labs-php-bot/raw/master/helloworld.jpeg) 250 | 251 | ### Объяснение исходного кода 252 | Следующий код создает websocket клиент. 253 | ```python 254 | if __name__ == "__main__": 255 | 256 | # mixin_api = MIXIN_API(mixin_config) 257 | while True: 258 | mixin_ws = MIXIN_WS_API(on_message=on_message) 259 | mixin_ws.run() 260 | ``` 261 | 262 | Отправьте сообщение операции READ на сервер, чтобы он знал, что сообщение прочитано. Иначе бот получит повторное сообщение, когда он подключится к серверу, если не будет отметки о прочтении. 263 | 264 | ```python 265 | MIXIN_WS_API.replayMessage(ws, msgid) 266 | ``` 267 | Бот повторяет любой текст от пользователя 268 | ```python 269 | if categoryindata == "PLAIN_TEXT": 270 | realData = realData.decode('utf-8') 271 | print("dataindata",realData) 272 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 273 | ``` 274 | 275 | Не только текст, но и картинки, и другие типы сообщений могут передаваться Вашему боту. Вы можете узнать подробнее [тут](https://developers.mixin.one/api/beta-mixin-message/websocket-messages/) о сообщениях Messenger. 276 | 277 | ### Окончание 278 | Теперь Ваш бот работает. И Вы можете взломать это. 279 | 280 | Полный код [здесь](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/app-mini.py) 281 | -------------------------------------------------------------------------------- /README-zhchs.md: -------------------------------------------------------------------------------- 1 | # 基于Mixin Network的Python比特币开发教程 2 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 3 | [Mixin Network](https://mixin.one) 是一个免费的 极速的端对端加密数字货币交易系统. 4 | 在本章中,你可以按教程在Mixin Messenger中创建一个bot来接收用户消息, 学到如何给机器人转**比特币** 或者 让机器人给你转**比特币**. 5 | 6 | [Mixin Network的开发资源汇编](https://github.com/awesome-mixin-network/index_of_Mixin_Network_resource) 7 | 8 | ## 课程简介 9 | 1. [创建一个接受消息的机器人](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README-zhchs.md) 10 | 2. [机器人接受比特币并立即退还用户](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README2-zhchs.md) 11 | 3. [创建比特币钱包](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README3-zhchs.md) 12 | 13 | ## 创建一个接受消息的机器人 14 | 15 | 通过本教程,你将学会如何用PHP创建一个机器人APP,让它能接受消息. 16 | 17 | ## Python 3 安装: 18 | 本教程基于Python 3.7.2, 所以你需要安装Python 3.7.2 或 以上的版本. 19 | 20 | on macOS 21 | ```bash 22 | brew upgrade 23 | brew install python@3 24 | ``` 25 | 26 | on Ubuntu, 从第三方的APT源中安装. 27 | ```bash 28 | sudo apt update 29 | sudo apt install software-properties-common 30 | sudo add-apt-repository ppa:deadsnakes/ppa 31 | ``` 32 | 33 | 当出现下面的提示时,按"回车"继续. 34 | ```bash 35 | Press [ENTER] to continue or Ctrl-c to cancel adding it. 36 | ``` 37 | 重新更新一次apt源, 再安装python3.7, python3.7-venv 38 | ```bash 39 | sudo apt update 40 | sudo apt install python3.7 python3.7-venv 41 | sudo ln -s /usr/bin/python3.7 /usr/bin/python3 42 | ``` 43 | 检查安装是否成功了,需要检查python3与python3-venv, 正确的提示如下: 44 | ```bash 45 | $ python3 -V 46 | Python 3.7.2 47 | ``` 48 | 49 | ```bash 50 | root@n2:~ python3 -m venv -h 51 | usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear] 52 | [--upgrade] [--without-pip] [--prompt PROMPT] 53 | ENV_DIR [ENV_DIR ...] 54 | Creates virtual Python environments in one or more target directories. 55 | positional arguments: 56 | ENV_DIR A directory to create the environment in. 57 | 58 | optional arguments: 59 | -h, --help show this help message and exit 60 | --system-site-packages 61 | Give the virtual environment access to the system 62 | site-packages dir. 63 | --symlinks Try to use symlinks rather than copies, when symlinks 64 | are not the default for the platform. 65 | --copies Try to use copies rather than symlinks, even when 66 | symlinks are the default for the platform. 67 | --clear Delete the contents of the environment directory if it 68 | already exists, before environment creation. 69 | --upgrade Upgrade the environment directory to use this version 70 | of Python, assuming Python has been upgraded in-place. 71 | --without-pip Skips installing or upgrading pip in the virtual 72 | environment (pip is bootstrapped by default) 73 | --prompt PROMPT Provides an alternative prompt prefix for this 74 | environment. 75 | 76 | Once an environment has been created, you may wish to activate it, e.g. by 77 | sourcing an activate script in its bin directory 78 | ``` 79 | 80 | ## 创建 mixin_labs-python-bot 项目 81 | 82 | 你首先需要创建项目目录,初始化"虚拟环境",然后安装需要的软件包. 83 | ```bash 84 | mkdir mixin_labs-python-bot 85 | cd mixin_labs-python-bot 86 | python3 -m venv ./ 87 | ``` 88 | 89 | 在 **python3 -m venv** 指令完成之后, 项目目录如下: 90 | ```bash 91 | wenewzha:mixin_labs-python-bot wenewzhang$ ls 92 | bin include lib pyvenv.cfg 93 | ``` 94 | 95 | 当"虚拟环境"创建成功后,需要激活它, 通过执行bin目录下相应的activate文件完成. 96 | ```bash 97 | wenewzha:mixin_labs-python-bot wenewzhang$ source ./bin/activate 98 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ 99 | ``` 100 | 成功激活后,可以直接执行python或pip了,这时,不再需要输入他们的完整路径了. 101 | 102 | ## 在"虚拟环境"里安装必需的包 103 | 104 | 创建一个必需包的list 105 | > requirements.txt 106 | ```txt 107 | cryptography==2.4.2 108 | pycparser==2.19 109 | pycryptodome==3.7.2 110 | PyJWT==1.7.1 111 | python-dateutil==2.7.5 112 | PyYAML==3.13 113 | requests==2.21.0 114 | websocket-client==0.54.0 115 | ``` 116 | 通过pip升级pip包本身, 并安装必需包. 117 | ```bash 118 | pip install --upgrade pip 119 | pip install -r requirements.txt 120 | ``` 121 | 122 | ## 下载 Mixin Network的python 3的API 123 | ```bash 124 | wget https://github.com/includeleec/mixin-python3-sdk/raw/master/mixin_ws_api.py 125 | wget https://github.com/includeleec/mixin-python3-sdk/raw/master/mixin_api.py 126 | wget https://github.com/includeleec/mixin-python3-sdk/raw/master/mixin_config.py 127 | ``` 128 | 129 | ## 你好,世界! 130 | 131 | ### 创建第一个机器人APP 132 | 按下面的提示,到mixin.one创建一个APP[tutorial](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account). 133 | 134 | ### 生成相应的参数 135 | 记下这些[生成的参数](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account#generate-secure-parameter-for-your-app) 136 | 它们将用于mixin_config.py中. 137 | 138 | ![mixin_network-keys](https://github.com/wenewzhang/mixin_labs-php-bot/raw/master/mixin_network-keys.jpg) 139 | 在项目目录下,创建mixin_config.py,将生成的参数,替换成你的! 140 | 141 | > mixin_config.py 142 | ```python 143 | client_id= 'ed882a39-0b0c-4413-bbd9-221cdeee56bf' 144 | client_secret = '8d7ec7b9c8261b6c7bd6309210496ca4b72bce9efc7e49be14a428ce49ff7202' 145 | 146 | 147 | pay_pin = '599509' 148 | pay_session_id = 'bd53b6a4-e79a-49e5-ad04-36da518354f6' 149 | pin_token = "nVREh0/Ys9vzNFCQT2+PKcDN2OYAUSH8CidwHqDQLOCvokE7o6wtvLypjW9iks/RsnBM6N4SPF/P3bBW254YHGuDZXhitDEWOGkXs7v8BxMQxf+9454qTkMSpR9xbjAzgMXnSyHrNVoBtsc/Y+NvemB3VxPfsCOFHasiMqAa5DU=" 150 | 151 | 152 | private_key = """-----BEGIN RSA PRIVATE KEY----- 153 | MIICXQIBAAKBgQCnaoO1SdPxggEpAIUdM/8Ll4FOqlXK7vwURHr4FFi6hnQ1I79g 154 | pZSlJdzjr24WcIuNi6kVdXVIpyzZJGXS2I72dpGs5h1jKxL8AWIUVL2axZXqTJNi 155 | c4wj6GJ4gDRP2U9I9gae+S/frM6KP8TioV0OcbmrlfrwI0OElLH3363y1wIDAQAB 156 | AoGAduaGLi4F8cMkMculvqzcGY57jrQZBGyg6YANWb2Rmr+9LrR5yhkvLe9rJuXE 157 | KPm7k0a6SnxGVNguWPWpv4qAVVGAJ0eb8ETXTRO20HlKmcbxfFdDtHBDV3QufNa1 158 | h3mNEsqWDNCDdAm7p/EZwfG2F9+nmeXLfip7R1I72qbK0wkCQQDiJR6NEGVwbj8H 159 | K8kRpzY1D9lPqp1ZMrma5AFYGZIb5voTxLjRpYdxQJHi7CCdE1zgqJOXvA3jj/io 160 | f7bMIJY7AkEAvYSSC5H+fUKAjyjeCTGJBBKoPDsq+aALAYLWf77sGXE9BBmhhY0l 161 | iwmbj8X6/qZtQ0yEzdT/OSdiYL86CcrgFQJBALz/sMzMSzrvqJVhrqWmTdOC72d5 162 | fA+0KRKeQ9FRbZ8MJyymWKA96zhncoVoOsmMCS9pNBC4BhONm4+XTTrEcUkCQQCo 163 | DWB8Bg/G/yuExtZtDJHVHL41+rmW9UYNJvoR+TjfLrzOX/QMuyapbfGVwhdZrDaD 164 | UN0KsG9JPRVNeQR8HnwpAkACrr9cNp1H1bytHG9a6L+5cVHkRhqqEYWVO41MhgZF 165 | 5bIKx5OXCJB2VwY7fjFet2KxTHGfEZt/khjFNZzVX7lN 166 | -----END RSA PRIVATE KEY-----""" 167 | ``` 168 | 需要替换的参数包括: client_id, client_secret, pay_pin, pin_token, pay_session_id, private key. 169 | 170 | 创建 app-mini.py 文件, 内容如下: 171 | > app-mini.py 172 | ```python 173 | from mixin_ws_api import MIXIN_WS_API 174 | from mixin_api import MIXIN_API 175 | import mixin_config 176 | 177 | import json 178 | import time 179 | from io import BytesIO 180 | import base64 181 | import gzip 182 | 183 | try: 184 | import thread 185 | except ImportError: 186 | import _thread as thread 187 | 188 | 189 | def on_message(ws, message): 190 | inbuffer = BytesIO(message) 191 | 192 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 193 | rdata_injson = f.read() 194 | rdata_obj = json.loads(rdata_injson) 195 | print("-------json object begin---------") 196 | print(rdata_obj) 197 | print("-------json object end---------") 198 | action = rdata_obj["action"] 199 | 200 | if rdata_obj["data"] is not None: 201 | print("data in message:",rdata_obj["data"]) 202 | 203 | if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 204 | print(rdata_obj["data"]["category"]) 205 | 206 | if action == "CREATE_MESSAGE": 207 | 208 | data = rdata_obj["data"] 209 | msgid = data["message_id"] 210 | typeindata = data["type"] 211 | categoryindata = data["category"] 212 | userId = data["user_id"] 213 | conversationId = data["conversation_id"] 214 | dataindata = data["data"] 215 | 216 | realData = base64.b64decode(dataindata) 217 | 218 | MIXIN_WS_API.replayMessage(ws, msgid) 219 | 220 | if 'error' in rdata_obj: 221 | return 222 | 223 | if categoryindata == "PLAIN_TEXT": 224 | realData = realData.decode('utf-8') 225 | print("dataindata",realData) 226 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 227 | 228 | 229 | if __name__ == "__main__": 230 | 231 | # mixin_api = MIXIN_API(mixin_config) 232 | while True: 233 | mixin_ws = MIXIN_WS_API(on_message=on_message) 234 | mixin_ws.run() 235 | ``` 236 | 237 | 运行 app-mini.py, 记得要先激活“虚拟环境”哦! 238 | ```bash 239 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app-mini.py 240 | ... 241 | ``` 242 | 如果一切正常,将会有如下提示: 243 | ```bash 244 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app-mini.py 245 | ws open 246 | -------json object begin--------- 247 | {'id': '1c798948-30eb-11e9-a20e-20c9d08850cd', 'action': 'LIST_PENDING_MESSAGES'} 248 | -------json object end--------- 249 | ``` 250 | 251 | 在手机安装 [Mixin Messenger](https://mixin.one/),增加机器人为好友,(比如这个机器人是7000101639) 然后发送消息给它,效果如下! 252 | 253 | ![mixin_messenger](https://github.com/wenewzhang/mixin_labs-php-bot/raw/master/helloworld.jpeg) 254 | 255 | 256 | ## 源代码解释 257 | WebSocket是建立在TCP基础之上的全双工通讯方式,我们需要建立一个loop循环来维持通迅。 258 | 259 | ```python 260 | if __name__ == "__main__": 261 | 262 | # mixin_api = MIXIN_API(mixin_config) 263 | while True: 264 | mixin_ws = MIXIN_WS_API(on_message=on_message) 265 | mixin_ws.run() 266 | ``` 267 | 268 | 每接收到一个消息,需要按消息编号(message_id)给服务器回复一个"已读"的消息,避免服务器在机器人重新登入后,再次发送处理过的消息! 269 | 270 | ```python 271 | MIXIN_WS_API.replayMessage(ws, msgid) 272 | ``` 273 | 机器人程序完整回复用户的信息 274 | ```python 275 | if categoryindata == "PLAIN_TEXT": 276 | realData = realData.decode('utf-8') 277 | print("dataindata",realData) 278 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 279 | ``` 280 | Mixin Messenger支持的消息类型很多,具体可到下面链接查看: [WebSocket消息类型](https://developers.mixin.one/api/beta-mixin-message/websocket-messages/). 281 | 282 | ### 完成 283 | 现在你的机器人APP运行起来了,你打算如何改造你的机器人呢? 284 | 285 | 完整的代码[在这儿](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/app-mini.py) 286 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Command line bitcoin wallet written in python script based on Mixin Network 2 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 3 | This a command line bitcoin/altcoin wallet written in python script based on Mixin Network. 4 | 5 | Full Mixin network resource [index](https://github.com/awesome-mixin-network/index_of_Mixin_Network_resource) 6 | 7 | ## Python 3 installation: 8 | This tool is written in Python 3.7.2 So you need to install Python 3.7.2 or above. 9 | 10 | macOS 11 | ```bash 12 | brew upgrade 13 | brew install python@3 14 | ``` 15 | 16 | Ubuntu, install python 3.7.2 from the third apt source. 17 | ```bash 18 | sudo apt update 19 | sudo apt install software-properties-common 20 | sudo add-apt-repository ppa:deadsnakes/ppa 21 | ``` 22 | 23 | When prompt like below, press Enter to continue: 24 | ```bash 25 | Press [ENTER] to continue or Ctrl-c to cancel adding it. 26 | ``` 27 | Update the source, then install python3.7, python3.7-venv 28 | ```bash 29 | sudo apt update 30 | sudo apt install python3.7 python3.7-venv 31 | sudo ln -s /usr/bin/python3.7 /usr/bin/python3 32 | ``` 33 | 34 | check both python3 and python3-venv are installed 35 | ```bash 36 | $ python3 -V 37 | Python 3.7.2 38 | ``` 39 | 40 | ```bash 41 | root@n2:~ python3 -m venv -h 42 | usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear] 43 | [--upgrade] [--without-pip] [--prompt PROMPT] 44 | ENV_DIR [ENV_DIR ...] 45 | Creates virtual Python environments in one or more target directories. 46 | positional arguments: 47 | ENV_DIR A directory to create the environment in. 48 | 49 | optional arguments: 50 | -h, --help show this help message and exit 51 | --system-site-packages 52 | Give the virtual environment access to the system 53 | site-packages dir. 54 | --symlinks Try to use symlinks rather than copies, when symlinks 55 | are not the default for the platform. 56 | --copies Try to use copies rather than symlinks, even when 57 | symlinks are the default for the platform. 58 | --clear Delete the contents of the environment directory if it 59 | already exists, before environment creation. 60 | --upgrade Upgrade the environment directory to use this version 61 | of Python, assuming Python has been upgraded in-place. 62 | --without-pip Skips installing or upgrading pip in the virtual 63 | environment (pip is bootstrapped by default) 64 | --prompt PROMPT Provides an alternative prompt prefix for this 65 | environment. 66 | 67 | Once an environment has been created, you may wish to activate it, e.g. by 68 | sourcing an activate script in its bin directory 69 | ``` 70 | 71 | ## Create mixin_labs-python-bot project 72 | 73 | You need create project directory, make it as a python's “virtual environment”, and install the required packages. 74 | ```bash 75 | git clone https://github.com/myrual/mixin_labs-python-bot 76 | cd mixin_labs-python-bot 77 | python3 -m venv ./ 78 | ``` 79 | 80 | Run **python3 -m venv** , following file and folder are created: 81 | ```bash 82 | wenewzha:mixin_labs-python-bot wenewzhang$ ls 83 | bin include lib pyvenv.cfg 84 | ``` 85 | 86 | Once a virtual environment has been created, it can be “activated” using a script in the virtual environment’s binary directory. 87 | ```bash 88 | wenewzha:mixin_labs-python-bot wenewzhang$ source ./bin/activate 89 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ 90 | ``` 91 | So that “python” or "pip" invoke from the virtual environment, and you can run installed scripts without having to use their full path. 92 | 93 | ## Install required packages by "virtual environment" 94 | 95 | Create the requirement list. 96 | > requirements.txt 97 | ```txt 98 | cryptography==2.4.2 99 | pycparser==2.19 100 | pycryptodome==3.7.2 101 | PyJWT==1.7.1 102 | python-dateutil==2.7.5 103 | PyYAML==3.13 104 | requests==2.21.0 105 | websocket-client==0.54.0 106 | ``` 107 | Use pip to upgrade pip itself, and install required packages. 108 | ```bash 109 | pip install --upgrade pip 110 | pip install -r requirements.txt 111 | ``` 112 | 113 | ## Download the mixin-network api. 114 | ```bash 115 | wget https://github.com/wenewzhang/mixin-python3-sdk/raw/master/mixin_ws_api.py 116 | wget https://github.com/wenewzhang/mixin-python3-sdk/raw/master/mixin_api.py 117 | wget https://github.com/wenewzhang/mixin-python3-sdk/raw/master/mixin_config.py 118 | ``` 119 | 120 | ## Hello, world in Python 121 | 122 | ### Create your first app in Mixin Network developer dashboard 123 | You need to create an app in dashboard. This [tutorial](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account) can help you. 124 | 125 | ### Generate parameter of your app in dashboard 126 | After app is created in dashboard, you still need to [generate parameter](https://mixin-network.gitbook.io/mixin-network/mixin-messenger-app/create-bot-account#generate-secure-parameter-for-your-app) 127 | and write down required content, these content will be written into mixin_config.py file. 128 | 129 | ![mixin_network-keys](https://github.com/wenewzhang/mixin_labs-php-bot/raw/master/mixin_network-keys.jpg) 130 | In the folder, create a file: mixin_config.py. Copy the following content into it. 131 | > mixin_config.py 132 | ```python 133 | client_id= 'ed882a39-0b0c-4413-bbd9-221cdeee56bf' 134 | client_secret = '8d7ec7b9c8261b6c7bd6309210496ca4b72bce9efc7e49be14a428ce49ff7202' 135 | 136 | 137 | pay_pin = '599509' 138 | pay_session_id = 'bd53b6a4-e79a-49e5-ad04-36da518354f6' 139 | pin_token = "nVREh0/Ys9vzNFCQT2+PKcDN2OYAUSH8CidwHqDQLOCvokE7o6wtvLypjW9iks/RsnBM6N4SPF/P3bBW254YHGuDZXhitDEWOGkXs7v8BxMQxf+9454qTkMSpR9xbjAzgMXnSyHrNVoBtsc/Y+NvemB3VxPfsCOFHasiMqAa5DU=" 140 | 141 | 142 | private_key = """-----BEGIN RSA PRIVATE KEY----- 143 | MIICXQIBAAKBgQCnaoO1SdPxggEpAIUdM/8Ll4FOqlXK7vwURHr4FFi6hnQ1I79g 144 | pZSlJdzjr24WcIuNi6kVdXVIpyzZJGXS2I72dpGs5h1jKxL8AWIUVL2axZXqTJNi 145 | c4wj6GJ4gDRP2U9I9gae+S/frM6KP8TioV0OcbmrlfrwI0OElLH3363y1wIDAQAB 146 | AoGAduaGLi4F8cMkMculvqzcGY57jrQZBGyg6YANWb2Rmr+9LrR5yhkvLe9rJuXE 147 | KPm7k0a6SnxGVNguWPWpv4qAVVGAJ0eb8ETXTRO20HlKmcbxfFdDtHBDV3QufNa1 148 | h3mNEsqWDNCDdAm7p/EZwfG2F9+nmeXLfip7R1I72qbK0wkCQQDiJR6NEGVwbj8H 149 | K8kRpzY1D9lPqp1ZMrma5AFYGZIb5voTxLjRpYdxQJHi7CCdE1zgqJOXvA3jj/io 150 | f7bMIJY7AkEAvYSSC5H+fUKAjyjeCTGJBBKoPDsq+aALAYLWf77sGXE9BBmhhY0l 151 | iwmbj8X6/qZtQ0yEzdT/OSdiYL86CcrgFQJBALz/sMzMSzrvqJVhrqWmTdOC72d5 152 | fA+0KRKeQ9FRbZ8MJyymWKA96zhncoVoOsmMCS9pNBC4BhONm4+XTTrEcUkCQQCo 153 | DWB8Bg/G/yuExtZtDJHVHL41+rmW9UYNJvoR+TjfLrzOX/QMuyapbfGVwhdZrDaD 154 | UN0KsG9JPRVNeQR8HnwpAkACrr9cNp1H1bytHG9a6L+5cVHkRhqqEYWVO41MhgZF 155 | 5bIKx5OXCJB2VwY7fjFet2KxTHGfEZt/khjFNZzVX7lN 156 | -----END RSA PRIVATE KEY-----""" 157 | ``` 158 | Replace the value with content generated in dashboard. Create an app-mini.py file, fill it by the content below: 159 | > app-mini.py 160 | ```python 161 | from mixin_ws_api import MIXIN_WS_API 162 | from mixin_api import MIXIN_API 163 | import mixin_config 164 | 165 | import json 166 | import time 167 | from io import BytesIO 168 | import base64 169 | import gzip 170 | 171 | try: 172 | import thread 173 | except ImportError: 174 | import _thread as thread 175 | 176 | 177 | def on_message(ws, message): 178 | inbuffer = BytesIO(message) 179 | 180 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 181 | rdata_injson = f.read() 182 | rdata_obj = json.loads(rdata_injson) 183 | print("-------json object begin---------") 184 | print(rdata_obj) 185 | print("-------json object end---------") 186 | action = rdata_obj["action"] 187 | 188 | if rdata_obj["data"] is not None: 189 | print("data in message:",rdata_obj["data"]) 190 | 191 | if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 192 | print(rdata_obj["data"]["category"]) 193 | 194 | if action == "CREATE_MESSAGE": 195 | 196 | data = rdata_obj["data"] 197 | msgid = data["message_id"] 198 | typeindata = data["type"] 199 | categoryindata = data["category"] 200 | userId = data["user_id"] 201 | conversationId = data["conversation_id"] 202 | dataindata = data["data"] 203 | 204 | realData = base64.b64decode(dataindata) 205 | 206 | MIXIN_WS_API.replayMessage(ws, msgid) 207 | 208 | if 'error' in rdata_obj: 209 | return 210 | 211 | if categoryindata == "PLAIN_TEXT": 212 | realData = realData.decode('utf-8') 213 | print("dataindata",realData) 214 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 215 | 216 | 217 | if __name__ == "__main__": 218 | 219 | # mixin_api = MIXIN_API(mixin_config) 220 | while True: 221 | mixin_ws = MIXIN_WS_API(on_message=on_message) 222 | mixin_ws.run() 223 | ``` 224 | 225 | Run the app-mini.py, DO NOT forget active the python "virtual environment" before!" 226 | ```bash 227 | cd mixin_labs-python-bot 228 | wenewzha:mixin_labs-python-bot wenewzhang$ source ./bin/activate 229 | ``` 230 | ```bash 231 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app-mini.py 232 | ... 233 | ``` 234 | If console output following message, congratulations. 235 | ```bash 236 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app-mini.py 237 | ws open 238 | -------json object begin--------- 239 | {'id': '1c798948-30eb-11e9-a20e-20c9d08850cd', 'action': 'LIST_PENDING_MESSAGES'} 240 | -------json object end--------- 241 | ``` 242 | 243 | Add the bot(for example, this bot id is 7000101639) as your friend in [Mixin Messenger](https://mixin.one/messenger) and send your messages. 244 | 245 | ![mixin_messenger](https://github.com/wenewzhang/mixin_labs-php-bot/raw/master/helloworld.jpeg) 246 | 247 | ### Source code explanation 248 | The code creates a websocket client. 249 | ```python 250 | if __name__ == "__main__": 251 | 252 | # mixin_api = MIXIN_API(mixin_config) 253 | while True: 254 | mixin_ws = MIXIN_WS_API(on_message=on_message) 255 | mixin_ws.run() 256 | ``` 257 | 258 | Send a READ operation message to the server let it knows this message has been read. The bot will receive the duplicated message when the bot connected to server again if bot don't send response. 259 | 260 | ```python 261 | MIXIN_WS_API.replayMessage(ws, msgid) 262 | ``` 263 | The bot echo every text from user 264 | ```python 265 | if categoryindata == "PLAIN_TEXT": 266 | realData = realData.decode('utf-8') 267 | print("dataindata",realData) 268 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 269 | ``` 270 | 271 | Not only texts, images and other type message will be pushed to your bot. You can find more [details](https://developers.mixin.one/api/beta-mixin-message/websocket-messages/) about Messenger message. 272 | 273 | ### End 274 | Now your bot worked. You can hack it. 275 | 276 | Full code is [here](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/app-mini.py) 277 | -------------------------------------------------------------------------------- /README2-russian.md: -------------------------------------------------------------------------------- 1 | # Python Bitcoin Руководство для Mixin Network: получение и отправка Bitcoin в Mixin Messenger 2 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 3 | В [предыдущем руководстве](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README_russian.md), мы создали наше первое приложение, где пользователь отправляет "Hello,world!", а бот отвечает таким же сообщением. 4 | 5 | 6 | 7 | > app.py 8 | ```python 9 | from mixin_ws_api import MIXIN_WS_API 10 | from mixin_api import MIXIN_API 11 | import mixin_config 12 | 13 | import json 14 | import time 15 | from io import BytesIO 16 | import base64 17 | import gzip 18 | 19 | try: 20 | import thread 21 | except ImportError: 22 | import _thread as thread 23 | 24 | 25 | def on_message(ws, message): 26 | inbuffer = BytesIO(message) 27 | 28 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 29 | rdata_injson = f.read() 30 | rdata_obj = json.loads(rdata_injson) 31 | print("-------json object begin---------") 32 | print(rdata_obj) 33 | print("-------json object end---------") 34 | action = rdata_obj["action"] 35 | 36 | if rdata_obj["data"] is not None: 37 | print("data in message:",rdata_obj["data"]) 38 | 39 | if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 40 | print(rdata_obj["data"]["category"]) 41 | 42 | if action == "CREATE_MESSAGE": 43 | 44 | data = rdata_obj["data"] 45 | msgid = data["message_id"] 46 | typeindata = data["type"] 47 | categoryindata = data["category"] 48 | userId = data["user_id"] 49 | conversationId = data["conversation_id"] 50 | dataindata = data["data"] 51 | created_at = data["created_at"] 52 | updated_at = data["updated_at"] 53 | 54 | realData = base64.b64decode(dataindata) 55 | 56 | MIXIN_WS_API.replayMessage(ws, msgid) 57 | 58 | print('userId', userId) 59 | print("created_at",created_at) 60 | 61 | if categoryindata == "PLAIN_TEXT": 62 | realData = realData.decode('utf-8') 63 | print("dataindata",realData) 64 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 65 | elif categoryindata == "SYSTEM_ACCOUNT_SNAPSHOT": 66 | rdJs = json.loads(realData) 67 | if ( float(rdJs["amount"]) > 0 ): 68 | mixin_api.transferTo(userId, rdJs["asset_id"], rdJs["amount"], "") 69 | 70 | if __name__ == "__main__": 71 | 72 | mixin_api = MIXIN_API(mixin_config) 73 | 74 | mixin_ws = MIXIN_WS_API(on_message=on_message) 75 | 76 | mixin_ws.run() 77 | 78 | ``` 79 | ### Hello Bitcoin! 80 | Запустите **python app.py** в папке проекта. 81 | ```bash 82 | cd mixin_labs-python-bot 83 | source ./bin/activate 84 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app.py 85 | ws open 86 | -------json object begin--------- 87 | {'id': 'fd6ce766-331a-11e9-92a9-20c9d08850cd', 'action': 'LIST_PENDING_MESSAGES'} 88 | -------json object end--------- 89 | ``` 90 | Разработчик может отправить Bitcoin своим ботам в панели сообщения. Бот получает Bitcoin и отправляет назад немедленно. 91 | ![передача и токены](https://github.com/wenewzhang/mixin_network-nodejs-bot2/raw/master/transfer-any-tokens.jpg) 92 | 93 | Пользователь может оплатить 0.001 Bitcoin боту одним нажатием кнопки и 0.001 BTC будет возвращено в 1 секунду. Фактически, таким образом пользователь может оплачивать любым токеном. 94 | ![ссылка на оплату](https://github.com/wenewzhang/mixin_network-nodejs-bot2/raw/master/Pay_and_refund_quickly.jpg) 95 | 96 | ## Итоговый исходный код 97 | ```python 98 | elif categoryindata == "SYSTEM_ACCOUNT_SNAPSHOT": 99 | rdJs = json.loads(realData) 100 | if ( float(rdJs["amount"]) > 0 ): 101 | mixin_api.transferTo(userId, rdJs["asset_id"], rdJs["amount"], "") 102 | ``` 103 | * rdJs["amount"] отрицательно, если бот отправил Bitcoin пользователю успешно. 104 | * rdJs["amount"] положительно, если бот получил Bitcoin от пользователя. 105 | Вызывайте mixin_api.transferTo для возврата монет обратно пользователю. 106 | 107 | ## Продвинутое использование 108 | #### APP_BUTTON_GROUP 109 | В некоторых сценариях использования, например: 110 | Обмен монет предлагает услугу конвертации, которая меняет BTC на EOS, ETH, BCH и т.п. 111 | Вы хотите показать клиентам несколько ссылок на оплату с различным количеством, конструкция APP_BUTTON_GROUP может помочь в этом. 112 | ```python 113 | print('send a link APP_BUTTON_GROUP') 114 | btnBTC = MIXIN_WS_API.packButton(mixin_config.client_id, BTC_ASSET_ID, "0.0001","BTC pay") 115 | btnEOS = MIXIN_WS_API.packButton(mixin_config.client_id, EOS_ASSET_ID, "0.01","EOS pay","#0080FF") 116 | buttons = [btnBTC,btnEOS] 117 | MIXIN_WS_API.sendAppButtonGroup(ws, conversationId, userId, buttons) 118 | ``` 119 | Здесь клиентам показаны 2 кнопки для EOS и BTC, Вы можете добавить больше таким же способом. 120 | 121 | #### APP_CARD 122 | Может быть, группа кнопок слишком просто для Вас, в этом случае можно использовать ссылку оплаты, которая покажет иконку: APP_CARD. 123 | ```python 124 | print('send a link APP_CARD') 125 | MIXIN_WS_API.sendAppCard(ws, conversationId, mixin_config.client_id, 126 | BTC_ASSET_ID, "0.0001", 127 | "https://images.mixin.one/HvYGJsV5TGeZ-X9Ek3FEQohQZ3fE9LBEBGcOcn4c4BNHovP4fW4YB97Dg5LcXoQ1hUjMEgjbl1DPlKg1TW7kK6XP=s128", 128 | "Pay BTC 0.0001","topay") 129 | ``` 130 | ![APP_CARD](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/app_card.jpg) 131 | 132 | [Полный исходный код](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/app.py) 133 | -------------------------------------------------------------------------------- /README2-zhchs.md: -------------------------------------------------------------------------------- 1 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 2 | 3 | 在 [上一篇教程中](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README-zhchs.md), 我们创建了自动回复消息的机器人,当用户发送消息"Hello,World!"时,机器人会自动回复同一条消息! 4 | 5 | # 第二课: 机器人接受比特币并立即退还用户 6 | 按本篇教程后学习后完成后,你的机器人将会接受用户发送过来的加密货币,然后立即转回用户。 7 | 完整代码如下: 8 | > app.py 9 | ```python 10 | from mixin_ws_api import MIXIN_WS_API 11 | from mixin_api import MIXIN_API 12 | import mixin_config 13 | 14 | import json 15 | import time 16 | from io import BytesIO 17 | import base64 18 | import gzip 19 | 20 | try: 21 | import thread 22 | except ImportError: 23 | import _thread as thread 24 | 25 | 26 | def on_message(ws, message): 27 | inbuffer = BytesIO(message) 28 | 29 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 30 | rdata_injson = f.read() 31 | rdata_obj = json.loads(rdata_injson) 32 | print("-------json object begin---------") 33 | print(rdata_obj) 34 | print("-------json object end---------") 35 | action = rdata_obj["action"] 36 | 37 | if rdata_obj["data"] is not None: 38 | print("data in message:",rdata_obj["data"]) 39 | 40 | if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 41 | print(rdata_obj["data"]["category"]) 42 | 43 | if action == "CREATE_MESSAGE": 44 | 45 | data = rdata_obj["data"] 46 | msgid = data["message_id"] 47 | typeindata = data["type"] 48 | categoryindata = data["category"] 49 | userId = data["user_id"] 50 | conversationId = data["conversation_id"] 51 | dataindata = data["data"] 52 | created_at = data["created_at"] 53 | updated_at = data["updated_at"] 54 | 55 | realData = base64.b64decode(dataindata) 56 | 57 | MIXIN_WS_API.replayMessage(ws, msgid) 58 | 59 | print('userId', userId) 60 | print("created_at",created_at) 61 | 62 | if categoryindata == "PLAIN_TEXT": 63 | realData = realData.decode('utf-8') 64 | print("dataindata",realData) 65 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 66 | elif categoryindata == "SYSTEM_ACCOUNT_SNAPSHOT": 67 | rdJs = json.loads(realData) 68 | if ( float(rdJs["amount"]) > 0 ): 69 | mixin_api.transferTo(userId, rdJs["asset_id"], rdJs["amount"], "") 70 | 71 | if __name__ == "__main__": 72 | 73 | mixin_api = MIXIN_API(mixin_config) 74 | 75 | mixin_ws = MIXIN_WS_API(on_message=on_message) 76 | 77 | mixin_ws.run() 78 | 79 | ``` 80 | ### Hello Bitcoin! 81 | 在项目目录下,执行 **python app.py** 82 | ```bash 83 | cd mixin_labs-python-bot 84 | source ./bin/activate 85 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app.py 86 | ws open 87 | -------json object begin--------- 88 | {'id': 'fd6ce766-331a-11e9-92a9-20c9d08850cd', 'action': 'LIST_PENDING_MESSAGES'} 89 | -------json object end--------- 90 | ``` 91 | 开发者可以通过消息面板,给机器人转比特币,当机器人收到比特币后,马上返还给用户! 92 | ![transfer and tokens](https://github.com/wenewzhang/mixin_network-nodejs-bot2/raw/master/transfer-any-tokens.jpg) 93 | 94 | 事实上,用户可以发送任意的币种给机器人,它都能马上返还! 95 | ![pay-link](https://github.com/wenewzhang/mixin_network-nodejs-bot2/raw/master/Pay_and_refund_quickly.jpg) 96 | 97 | ## 源代码解释 98 | ```python 99 | elif categoryindata == "SYSTEM_ACCOUNT_SNAPSHOT": 100 | rdJs = json.loads(realData) 101 | if ( float(rdJs["amount"]) > 0 ): 102 | mixin_api.transferTo(userId, rdJs["asset_id"], rdJs["amount"], "") 103 | ``` 104 | 如果机器人收到币,rdJs["amount"] 大于零;如果机器人支付币给用户,接收到的消息是一样的,唯一不同的是,rdJs["amount"]是一个负数. 105 | 最后一步,调用SDK的 mixin_api.transferTo 将币返还用户! 106 | 107 | ## 高级用法 108 | #### APP_BUTTON_GROUP 109 | 在一些应用场景,比如:有一个交易所想提供换币服务,将比特币换成以太坊,EOS,比特币现金等, 110 | 你想显示给用户一组按钮,它们分别代表不同的币与不同的数量,APP_BUTTON_GROUP可以帮你做到这一点. 111 | ```python 112 | print('send a link APP_BUTTON_GROUP') 113 | btnBTC = MIXIN_WS_API.packButton(mixin_config.client_id, BTC_ASSET_ID, "0.0001","BTC pay") 114 | btnEOS = MIXIN_WS_API.packButton(mixin_config.client_id, EOS_ASSET_ID, "0.01","EOS pay","#0080FF") 115 | buttons = [btnBTC,btnEOS] 116 | MIXIN_WS_API.sendAppButtonGroup(ws, conversationId, userId, buttons) 117 | ``` 118 | 这里演示给用户BTC与EOS两种,你还可以增加更多的按钮. 119 | 120 | #### APP_CARD 121 | 如果你觉得一组按钮太单调了,可以试一下APP_CARD,它提供一个图标的链接 122 | ```python 123 | print('send a link APP_CARD') 124 | MIXIN_WS_API.sendAppCard(ws, conversationId, mixin_config.client_id, 125 | BTC_ASSET_ID, "0.0001", 126 | "https://images.mixin.one/HvYGJsV5TGeZ-X9Ek3FEQohQZ3fE9LBEBGcOcn4c4BNHovP4fW4YB97Dg5LcXoQ1hUjMEgjbl1DPlKg1TW7kK6XP=s128", 127 | "Pay BTC 0.0001","topay") 128 | ``` 129 | ![APP_CARD](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/app_card.jpg) 130 | 131 | [Full source code](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/app.py) 132 | -------------------------------------------------------------------------------- /README2.md: -------------------------------------------------------------------------------- 1 | # Python Bitcoin tutorial based on Mixin Network: Receive and send Bitcoin in Mixin Messenger 2 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 3 | In [the previous chapter](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README.md), we created our first app, when user sends "Hello,world!", the bot reply the same message. 4 | 5 | 6 | 7 | > app.py 8 | ```python 9 | from mixin_ws_api import MIXIN_WS_API 10 | from mixin_api import MIXIN_API 11 | import mixin_config 12 | 13 | import json 14 | import time 15 | from io import BytesIO 16 | import base64 17 | import gzip 18 | 19 | try: 20 | import thread 21 | except ImportError: 22 | import _thread as thread 23 | 24 | 25 | def on_message(ws, message): 26 | inbuffer = BytesIO(message) 27 | 28 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 29 | rdata_injson = f.read() 30 | rdata_obj = json.loads(rdata_injson) 31 | print("-------json object begin---------") 32 | print(rdata_obj) 33 | print("-------json object end---------") 34 | action = rdata_obj["action"] 35 | 36 | if rdata_obj["data"] is not None: 37 | print("data in message:",rdata_obj["data"]) 38 | 39 | if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 40 | print(rdata_obj["data"]["category"]) 41 | 42 | if action == "CREATE_MESSAGE": 43 | 44 | data = rdata_obj["data"] 45 | msgid = data["message_id"] 46 | typeindata = data["type"] 47 | categoryindata = data["category"] 48 | userId = data["user_id"] 49 | conversationId = data["conversation_id"] 50 | dataindata = data["data"] 51 | created_at = data["created_at"] 52 | updated_at = data["updated_at"] 53 | 54 | realData = base64.b64decode(dataindata) 55 | 56 | MIXIN_WS_API.replayMessage(ws, msgid) 57 | 58 | print('userId', userId) 59 | print("created_at",created_at) 60 | 61 | if categoryindata == "PLAIN_TEXT": 62 | realData = realData.decode('utf-8') 63 | print("dataindata",realData) 64 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 65 | elif categoryindata == "SYSTEM_ACCOUNT_SNAPSHOT": 66 | rdJs = json.loads(realData) 67 | if ( float(rdJs["amount"]) > 0 ): 68 | mixin_api.transferTo(userId, rdJs["asset_id"], rdJs["amount"], "") 69 | 70 | if __name__ == "__main__": 71 | 72 | mixin_api = MIXIN_API(mixin_config) 73 | 74 | mixin_ws = MIXIN_WS_API(on_message=on_message) 75 | 76 | mixin_ws.run() 77 | 78 | ``` 79 | ### Hello Bitcoin! 80 | Execute **python app.py** in the project directory. 81 | ```bash 82 | cd mixin_labs-python-bot 83 | source ./bin/activate 84 | (mixin_labs-python-bot) wenewzha:mixin_labs-python-bot wenewzhang$ python app.py 85 | ws open 86 | -------json object begin--------- 87 | {'id': 'fd6ce766-331a-11e9-92a9-20c9d08850cd', 'action': 'LIST_PENDING_MESSAGES'} 88 | -------json object end--------- 89 | ``` 90 | Developer can send Bitcoin to their bots in message panel. The bot receive the Bitcoin and then send back immediately. 91 | ![transfer and tokens](https://github.com/wenewzhang/mixin_network-nodejs-bot2/raw/master/transfer-any-tokens.jpg) 92 | 93 | User can pay 0.001 Bitcoin to bot by click the button and the 0.001 Bitcoin will be refunded in 1 second,In fact, user can pay any coin either. 94 | ![pay-link](https://github.com/wenewzhang/mixin_network-nodejs-bot2/raw/master/Pay_and_refund_quickly.jpg) 95 | 96 | ## Source code summary 97 | ```python 98 | elif categoryindata == "SYSTEM_ACCOUNT_SNAPSHOT": 99 | rdJs = json.loads(realData) 100 | if ( float(rdJs["amount"]) > 0 ): 101 | mixin_api.transferTo(userId, rdJs["asset_id"], rdJs["amount"], "") 102 | ``` 103 | * rdJs["amount"] is negative if bot sends Bitcoin to user successfully. 104 | * rdJs["amount"] is positive if bot receive Bitcoin from user. 105 | Call mixin_api.transferTo to refund the coins back to user. 106 | 107 | ## Advanced usage 108 | #### APP_BUTTON_GROUP 109 | In some payment scenario, for example: 110 | The coin exchange provides coin-exchange service which transfer BTC to EOS ETH, BCH etc, 111 | you want show the clients many pay links with different amount, APP_BUTTON_GROUP can help you here. 112 | ```python 113 | print('send a link APP_BUTTON_GROUP') 114 | btnBTC = MIXIN_WS_API.packButton(mixin_config.client_id, BTC_ASSET_ID, "0.0001","BTC pay") 115 | btnEOS = MIXIN_WS_API.packButton(mixin_config.client_id, EOS_ASSET_ID, "0.01","EOS pay","#0080FF") 116 | buttons = [btnBTC,btnEOS] 117 | MIXIN_WS_API.sendAppButtonGroup(ws, conversationId, userId, buttons) 118 | ``` 119 | Here show clients two buttons for EOS and BTC, you can add more buttons in this way. 120 | 121 | #### APP_CARD 122 | Maybe a group of buttons too simple for you, try a pay link which show a icon: APP_CARD. 123 | ```python 124 | print('send a link APP_CARD') 125 | MIXIN_WS_API.sendAppCard(ws, conversationId, mixin_config.client_id, 126 | BTC_ASSET_ID, "0.0001", 127 | "https://images.mixin.one/HvYGJsV5TGeZ-X9Ek3FEQohQZ3fE9LBEBGcOcn4c4BNHovP4fW4YB97Dg5LcXoQ1hUjMEgjbl1DPlKg1TW7kK6XP=s128", 128 | "Pay BTC 0.0001","topay") 129 | ``` 130 | ![APP_CARD](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/app_card.jpg) 131 | 132 | [Full source code](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/app.py) 133 | -------------------------------------------------------------------------------- /README3-russian.md: -------------------------------------------------------------------------------- 1 | # Руководство Python Bitcoin для Mixin Network: Создание кошелька Bitcoin 2 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 3 | Мы уже создали бота в [echo message](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README.md) и [echo Bitcoin](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README2.md). 4 | 5 | # Что мы изучим в данном руководстве 6 | 1. Как создать кошелек Bitcoin 7 | 2. Как прочитать баланс Bitcoin 8 | 3. Как отправить Bitcoin с нулевой комиссией и получить подтверждение за 1 секунду 9 | 4. Как отправить Bitcoin на другой кошелек 10 | 11 | 12 | Требования: Вы должны иметь аккаунт в Mixin Network. Создание аккаунта может быть выполнено одной командой: 13 | 14 | ```python 15 | userInfo = mixinApiBotInstance.createUser(session_key.decode(),"Tom Bot") 16 | ``` 17 | Функция в Python SDK создает пару RSA ключей автоматически, затем вызывает Mixin Network для создания аккаунта. По завершении функция возвращает всю информацию об аккаунте. 18 | 19 | ```python 20 | // Интерфейс Create User включает всю информацию об аккаунте 21 | userInfo.get("data").get("pin_token"), 22 | userInfo.get("data").get("session_id"), 23 | userInfo.get("data").get("user_id"), 24 | ``` 25 | 26 | Результат команды createUser: 27 | ```python 28 | {'data': {'type': 'user', 'user_id': '2f25b669-15e7-392c-a1d5-fe7ba43bdf37', 'identity_number': '0', 29 | 'full_name': 'Tom Bot', 'avatar_url': '', 'relationship': '', 'mute_until': '0001-01-01T00:00:00Z', 30 | 'created_at': '2019-02-22T06:23:41.754573722Z', 'is_verified': False, 31 | 'session_id': '284c7b39-3284-4cf6-9354-87df30ec7d57', 'phone': '', 32 | 'pin_token':'g4upUgBXa8ATk7yxL6B94HgI4GV4sG4t8Wyn6uTu2Q2scH11UMQ5bYDb6Md+3LRQqRjEdRFcLlHijXGBihRweTaKTZjHQqolWbZcffesVIias6WppV/QMu4TzXCuKa5xpj3uhjL+yPyfWTLGUaVJTJN9n7PQmHSIUBXrovbfodk=', 33 | 'invitation_code': '', 'code_id': '', 'code_url': '', 'has_pin': False, 34 | 'receive_message_source': 'EVERYBODY', 'accept_conversation_source': 'EVERYBODY'}} 35 | ``` 36 | 37 | Теперь Вам нужно внимательно хранить информацию аккаунта. Вам нужна эта информация, чтобы прочитать баланс и другое содержимое. 38 | ### Создание кошелька Bitcoin для аккаунта Mixin Network 39 | Кошелек Bitcoin не создается автоматически при создании аккаунта в сети Mixin Network. Прочитайте данные актива Bitcoin один раз для генерации кошелька Bitcoin. 40 | ```python 41 | def readAssetAddress(asset_id,isBTC = True): 42 | with open('new_users.csv', newline='') as csvfile: 43 | reader = csv.reader(csvfile) 44 | for row in reader: 45 | pin = row.pop() 46 | userid = row.pop() 47 | session_id = row.pop() 48 | pin_token = row.pop() 49 | private_key = row.pop() 50 | mixinApiNewUserInstance = generateMixinAPI(private_key, 51 | pin_token, 52 | session_id, 53 | userid, 54 | pin,"") 55 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 56 | print(btcInfo) 57 | if isBTC: 58 | print("Account %s \'s Bitcoin wallet address is %s " %(userid,btcInfo.get("data").get("public_key"))) 59 | else: 60 | print("Account %s \'s EOS account name is %s, wallet address is %s " %(userid, 61 | btcInfo.get("data").get("account_name"), 62 | btcInfo.get("data").get("account_tag"))) 63 | ``` 64 | Вы можете найти информацию об активе Bitcoin в аккаунте. Публичный ключ - это Bitcoin-адрес. Полный ответ при чтении данных об активах Bitcoin у аккаунта: 65 | ```python 66 | {'data': {'type': 'asset', 'asset_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 67 | 'chain_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 'symbol': 'BTC', 'name': 'Bitcoin', 68 | 'icon_url': 'https://images.mixin.one/HvYGJsV5TGeZ-X9Ek3FEQohQZ3fE9LBEBGcOcn4c4BNHovP4fW4YB97Dg5LcXoQ1hUjMEgjbl1DPlKg1TW7kK6XP=s128', 'balance': '0', 69 | 'public_key': '1AYAMaRi3j5rXoFLmhJBFxvUEgGt8zeF4k', 'account_name': '', 'account_tag': '', 70 | 'price_btc': '1', 'price_usd': '3979.12975801', 'change_btc': '0', 71 | 'change_usd': '-0.0018925165548280905', 'asset_key': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 72 | 'confirmations': 12, 'capitalization': 0}} 73 | ``` 74 | API обеспечивает получение много другой информации об активе Bitcoin. 75 | * Адрес кошелька:[public_key] 76 | * Логотип: [icon_url] 77 | * Имя актива:[name] 78 | * uuid актива в Mixin network: [asset_key] 79 | * Цена в USD из Coinmarketcap.com: [price_usd] 80 | * Последний подтвержденный блок перед размещением депозита в Mixin network:[confirmations] 81 | 82 | 83 | ### Приватный ключ? 84 | Где приватный ключ кошелька Bitcoin? Приватный ключ защищен муультиподписью внутри Mixin Network, так что он невидим для пользователя. Актив Bitcoin может быть выведен на другой адрес, только если пользователь обеспечит корректную подпись приватного RSA ключа, PIN код and ключ Session. 85 | 86 | ### Не только Bitcoin, но также и Ethereum, EOS 87 | Аккаунт содержит не только кошелек Bitcoin, но и кошелек Ethereum, EOS, etc. Полный [список](https://mixin.one/network/chains) поддерживаемых блокчейнов. Все ERC20 токены and EOS токены также поддерживаются аккаунтом. 88 | 89 | Создать кошелек другого актива также просто, как и создание Bitcoin кошелька: просто прочитать его данные. 90 | #### Mixin Network поддерживает криптовалюты (2019-02-19): 91 | 92 | |crypto |uuid in Mixin Network 93 | |---|--- 94 | |EOS|6cfe566e-4aad-470b-8c9a-2fd35b49c68d 95 | |CNB|965e5c6e-434c-3fa9-b780-c50f43cd955c 96 | |BTC|c6d0c728-2624-429b-8e0d-d9d19b6592fa 97 | |ETC|2204c1ee-0ea2-4add-bb9a-b3719cfff93a 98 | |XRP|23dfb5a5-5d7b-48b6-905f-3970e3176e27 99 | |XEM|27921032-f73e-434e-955f-43d55672ee31 100 | |ETH|43d61dcd-e413-450d-80b8-101d5e903357 101 | |DASH|6472e7e3-75fd-48b6-b1dc-28d294ee1476 102 | |DOGE|6770a1e5-6086-44d5-b60f-545f9d9e8ffd 103 | |LTC|76c802a2-7c88-447f-a93e-c29c9e5dd9c8 104 | |SC|990c4c29-57e9-48f6-9819-7d986ea44985 105 | |ZEN|a2c5d22b-62a2-4c13-b3f0-013290dbac60 106 | |ZEC|c996abc9-d94e-4494-b1cf-2a3fd3ac5714 107 | |BCH|fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0 108 | 109 | Если Вы читаете адрес EOS, он будет состоять из двух частей: account_name и метка (account_tag). Когда Вы переводите EOS токены на Ваш аккаунт в Mixin network, Вы должны заполнить два параметра: account_name и memo. Содержимым memo и будет значение 'account_tag'. 110 | Результат чтения актива EOS: 111 | ```python 112 | {'data': {'type': 'asset', 'asset_id': '6cfe566e-4aad-470b-8c9a-2fd35b49c68d', 113 | 'chain_id': '6cfe566e-4aad-470b-8c9a-2fd35b49c68d', 114 | 'symbol': 'EOS', 'name': 'EOS', 115 | 'icon_url': 'https://images.mixin.one/a5dtG-IAg2IO0Zm4HxqJoQjfz-5nf1HWZ0teCyOnReMd3pmB8oEdSAXWvFHt2AJkJj5YgfyceTACjGmXnI-VyRo=s128', 116 | 'balance': '0', 'public_key': '', 117 | 'account_name': 'eoswithmixin', 'account_tag': '185b27f83d76dad3033ee437195aac11', 118 | 'price_btc': '0.00096903', 'price_usd': '3.8563221', 'change_btc': '0.00842757579765049', 119 | 'change_usd': '0.0066057628802373095', 'asset_key': 'eosio.token:EOS', 120 | 'confirmations': 64, 'capitalization': 0}} 121 | ``` 122 | 123 | ### Размещение Bitcoin и чтение баланса 124 | Теперь Вы можете внести Bitcoin на Ваш адрес. 125 | 126 | Это может быть слишком дорого в рамках данного руководства. Есть бесплатное и очень быстрое решение для внесения Bitcoin: добавьте этот адрес в Ваш аккаунт Mixin messenger как адрес для вывода and выведите небольшое количество Bitcoin из Вашего аккаунта на указанный адрес. Это бесплатно и будет быстро подтверждено, потому что оба адреса будут в Mixin Network. 127 | 128 | Теперь Вы можете прочитать баланс Bitcoin аккаунта. 129 | ```python 130 | btcInfo = mixinApiNewUserInstance.getAsset("c6d0c728-2624-429b-8e0d-d9d19b6592fa"); 131 | print(btcInfo); 132 | ``` 133 | ### Отправка Bitcoin внутри Mixin Network, чтобы убедиться в быстрых подтверждениях и нулевой комиссии 134 | Любая транзакция между аккаунтами Mixin Network бесплатна и подтверждается 1 секунду. 135 | 136 | #### Отправка Bitcoin другому аккаунту Mixin Network 137 | Мы можем отправлять Bitcoin нашему боту через Mixin Messenger и затем передавать Bitcoin от бота новому пользователю. 138 | 139 | ```python 140 | mixinApiBotInstance = MIXIN_API(mixin_config) 141 | //$user_info["user_id"] создается при помощи команды create user; 142 | mixinApiBotInstance.transferTo(userid, BTC_ASSET_ID, AMOUNT, "") 143 | ``` 144 | 145 | Прочитаем баланс Bitcoin у бота для подтверждения транзакции. 146 | Предупреждение: **mixinApiNewUserInstance** используется для НОВЫХ пользователей! 147 | ```python 148 | def readAssetBalance(asset_id): 149 | with open('new_users.csv', newline='') as csvfile: 150 | reader = csv.reader(csvfile) 151 | for row in reader: 152 | pin = row.pop() 153 | userid = row.pop() 154 | session_id = row.pop() 155 | pin_token = row.pop() 156 | private_key = row.pop() 157 | mixinApiNewUserInstance = generateMixinAPI(private_key, 158 | pin_token, 159 | session_id, 160 | userid, 161 | pin,"") 162 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 163 | print("Account %s \'s balance is %s " %(userid,btcInfo.get("data").get("balance"))) 164 | ``` 165 | 166 | ### Отправка Bitcoin на другие Bitcoin биржу или кошелек 167 | Если Вы хотите отправить Bitcoin на другие биржу или кошелек, Вы должны узнать адрес назначения, затем добавить этот адрес в список адресов для вывода в аккаунте Mixin Network. 168 | 169 | Подготовительные задачи: добавить адрес для вывода и узнать комиссию за вывод Bitcoin 170 | 171 | #### Добавление адреса назначения в список адресов для вывода 172 | Вызываем createAddress, ID адреса будет возвращено как результат API и вскоре потребуется. 173 | ```python 174 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 175 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 176 | BTC_WALLET_ADDR = "14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C"; 177 | btcInfo = mixinApiBotInstance.createAddress(BTC_ASSET_ID, BTC_WALLET_ADDR,"BTC","","") 178 | print(btcInfo) 179 | ``` 180 | Значение **14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C** это адрес кошелька Bitcoin, результат выполнения указан ниже, он содержит ID адреса вывода и значение комиссии 0.0034802 BTC. 181 | ```python 182 | {'data': {'type': 'address', 'address_id': '47998e2f-2761-45ce-9a6c-6f167b20c78b', 183 | 'asset_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 184 | 'public_key': '14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C', 'label': 'BTC', 185 | 'account_name': '', 'account_tag': '', 186 | 'fee': '0.0034802', 'reserve': '0', 'dust': '0.0001', 187 | 'updated_at': '2019-02-26T00:03:05.028140704Z'}} 188 | ``` 189 | Если Вы хотите создать адрес EOS, вызов будет выглядеть так: 190 | ```python 191 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 192 | EOS_WALLET_ADDR = "3e2f70914c8e8abbf60040207c8aae62"; 193 | EOS_ACCOUNT_NAME = "eoswithmixin"; 194 | eosInfo = mixinApiBotInstance.createAddress(EOS_ASSET_ID, "","",EOS_ACCOUNT_NAME,EOS_WALLET_ADDR) 195 | print(eosInfo) 196 | ``` 197 | #### Узнать комиссию на вывод можно в любое время 198 | ```python 199 | addr_id = btcInfo.get("data").get("address_id") 200 | addrInfo = mixinApiBotInstance.getAddress(addr_id) 201 | print(addrInfo) 202 | ``` 203 | 204 | #### Отправка Bitcoin на адрес назначения 205 | Подтверждаем запрос вывода к Mixin Network, the btcInfo.get("data").get("address_id") - это id адреса, который возвращается функцией createAddress 206 | ```python 207 | mixinApiBotInstance.withdrawals(btcInfo.get("data").get("address_id"),AMOUNT,"") 208 | ``` 209 | #### Проверяем транзакцию в блокчейн эксплорере 210 | 211 | [Полный исходный код](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/call_apis.py) 212 | -------------------------------------------------------------------------------- /README3-zhchs.md: -------------------------------------------------------------------------------- 1 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 2 | 我们已经创建过一个[回复消息](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README-zhchs.md)的机器人和一个能自动[支付比特币](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README2-zhchs.md)的机器人. 3 | 4 | # 通过本教程的学习,你可以学到如下内容 5 | 1. 如何创建一个比特币钱包. 6 | 2. 如何读取比特币钱包的余额. 7 | 3. 如何实现免手续费支付比特币并1秒到账 8 | 4. 如何将Mixin Network的比特币提现到你的冷钱包或第三方交易所. 9 | 10 | ## 通过Mixin Network Python SDK创建一个比特币钱包 11 | 前期准备:你要有一个Mixin Network账户。如果没有账户,一行代码就能创建一个 12 | ```python 13 | userInfo = mixinApiBotInstance.createUser(session_key.decode(),"Tom Bot") 14 | ``` 15 | 上面的语句会在本地创建一个RSA密钥对,然后调用Mixin Network来创建帐号,最后输出帐号信息. 16 | ```python 17 | //Create User api include all account information 18 | userInfo.get("data").get("pin_token"), 19 | userInfo.get("data").get("session_id"), 20 | userInfo.get("data").get("user_id"), 21 | ``` 22 | 23 | 帐号创建成功后结果如下: 24 | 25 | ```python 26 | {'data': {'type': 'user', 'user_id': '2f25b669-15e7-392c-a1d5-fe7ba43bdf37', 27 | 'identity_number': '0', 'full_name': 'Tom Bot', 'avatar_url': '', 28 | 'relationship': '', 'mute_until': '0001-01-01T00:00:00Z', 29 | 'created_at': '2019-02-22T06:23:41.754573722Z', 'is_verified': False, 30 | 'session_id': '284c7b39-3284-4cf6-9354-87df30ec7d57', 'phone': '', 31 | 'pin_token': 'g4upUgBXa8ATk7yxL6B94HgI4GV4sG4t8Wyn6uTu2Q2scH11UMQ5bYDb6Md+3LRQqRjEdRFcLlHijXGBihRweTaKTZjHQqolWbZcffesVIias6WppV/QMu4TzXCuKa5xpj3uhjL+yPyfWTLGUaVJTJN9n7PQmHSIUBXrovbfodk=', 32 | 'invitation_code': '', 'code_id': '', 'code_url': '', 'has_pin': False, 33 | 'receive_message_source': 'EVERYBODY', 'accept_conversation_source': 'EVERYBODY'}} 34 | ``` 35 | 36 | 现在你需要小心保管好你的帐号信息,在读取该账户的比特币资产余额或者进行其他操作时,将需要用到这些信息. 37 | ### 给新建的帐号创建一个比特币钱包 38 | 新账号并不默认内置比特币钱包, 现在读一下比特币余额就可以创建一个比特币钱包。 39 | ```python 40 | def readAssetAddress(asset_id,isBTC = True): 41 | with open('new_users.csv', newline='') as csvfile: 42 | reader = csv.reader(csvfile) 43 | for row in reader: 44 | pin = row.pop() 45 | userid = row.pop() 46 | session_id = row.pop() 47 | pin_token = row.pop() 48 | private_key = row.pop() 49 | mixinApiNewUserInstance = generateMixinAPI(private_key, 50 | pin_token, 51 | session_id, 52 | userid, 53 | pin,"") 54 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 55 | print(btcInfo) 56 | if isBTC: 57 | print("Account %s \'s Bitcoin wallet address is %s " %(userid,btcInfo.get("data").get("public_key"))) 58 | else: 59 | print("Account %s \'s EOS account name is %s, wallet address is %s " %(userid, 60 | btcInfo.get("data").get("account_name"), 61 | btcInfo.get("data").get("account_tag"))) 62 | ``` 63 | 64 | 创建的帐号的比特币资产详细信息如下,其中public key就是比特币的存币地址: 65 | ```python 66 | {'data': {'type': 'asset', 'asset_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 67 | 'chain_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 68 | 'symbol': 'BTC', 'name': 'Bitcoin', 69 | 'icon_url': 'https://images.mixin.one/HvYGJsV5TGeZ-X9Ek3FEQohQZ3fE9LBEBGcOcn4c4BNHovP4fW4YB97Dg5LcXoQ1hUjMEgjbl1DPlKg1TW7kK6XP=s128', 70 | 'balance': '0', 71 | 'public_key': '12sJHR7HJPMt33KwSHyxQvYqGGUEbVGREf', 72 | 'account_name': '', 'account_tag': '', 'price_btc': '1', 73 | 'price_usd': '3879.88117389', 'change_btc': '0', 74 | 'change_usd': '0.017333475714793264', 75 | 'asset_key': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 76 | 'confirmations': 12, 'capitalization': 0}} 77 | Account a8cefb2e-cb93-338f-aba7-32a3a635ad02 's Bitcoin wallet address is 12sJHR7HJPMt33KwSHyxQvYqGGUEbVGREf 78 | ``` 79 | 80 | 这个API能够提供若干与比特币有关的信息: 81 | * 存币地址:[public_key] 82 | * Logo: [icon_url] 83 | * 资产名字:[name] 84 | * 资产在Mixin Network的uuid: [asset_key] 85 | * 对美元的价格(Coinmarketcap.com提供): [price_usd] 86 | * 存币时确认的区块数量:[confirmations] 87 | 88 | 89 | ### 比特币私钥呢? 90 | 比特币的私钥呢?这个私钥被Mixin Network通过多重签名保护,所以对用户来说是不可见的,比特币资产的提现和转账都需要用户提供正确的的RSA签名,PIN代码与会话密钥才能完成. 91 | 92 | ### 不只是比特币,还有以太坊,EOS等 93 | 这个帐号不只支持比特币,还支持以太坊,EOS等, 完整的区块链支持[列表](https://mixin.one/network/chains). 这个账户同时也支持所有的 ERC20 代币与 EOS 代币. 94 | 95 | 创建其它的币的钱包与创建比特币钱包过程一样,读对应的资产余额就可以. 96 | 97 | #### Mixin Network 当前支持的加密货币 (2019-02-19) 98 | 99 | |crypto |uuid in Mixin Network 100 | |---|--- 101 | |EOS|6cfe566e-4aad-470b-8c9a-2fd35b49c68d 102 | |CNB|965e5c6e-434c-3fa9-b780-c50f43cd955c 103 | |BTC|c6d0c728-2624-429b-8e0d-d9d19b6592fa 104 | |ETC|2204c1ee-0ea2-4add-bb9a-b3719cfff93a 105 | |XRP|23dfb5a5-5d7b-48b6-905f-3970e3176e27 106 | |XEM|27921032-f73e-434e-955f-43d55672ee31 107 | |ETH|43d61dcd-e413-450d-80b8-101d5e903357 108 | |DASH|6472e7e3-75fd-48b6-b1dc-28d294ee1476 109 | |DOGE|6770a1e5-6086-44d5-b60f-545f9d9e8ffd 110 | |LTC|76c802a2-7c88-447f-a93e-c29c9e5dd9c8 111 | |SC|990c4c29-57e9-48f6-9819-7d986ea44985 112 | |ZEN|a2c5d22b-62a2-4c13-b3f0-013290dbac60 113 | |ZEC|c996abc9-d94e-4494-b1cf-2a3fd3ac5714 114 | |BCH|fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0 115 | 116 | EOS的存币地址与其它的币有些不同,它由两部分组成: account_name and account tag, 如果你向Mixin Network存入EOS,你需要填两项数据: account name 是**eoswithmixin**,备注里输入你的account_tag,比如**0aa2b00fad2c69059ca1b50de2b45569**. 117 | 118 | EOS的资产余额返回结果如下: 119 | ```python 120 | {'data': {'type': 'asset', 'asset_id': '6cfe566e-4aad-470b-8c9a-2fd35b49c68d', 121 | 'chain_id': '6cfe566e-4aad-470b-8c9a-2fd35b49c68d', 122 | 'symbol': 'EOS', 'name': 'EOS', 123 | 'icon_url': 'https://images.mixin.one/a5dtG-IAg2IO0Zm4HxqJoQjfz-5nf1HWZ0teCyOnReMd3pmB8oEdSAXWvFHt2AJkJj5YgfyceTACjGmXnI-VyRo=s128', 124 | 'balance': '0', 'public_key': '', 125 | 'account_name': 'eoswithmixin', 'account_tag': '70dae97b661ca9f80cb0e6549feeba6c', 126 | 'price_btc': '0.00092392', 'price_usd': '3.58276497', 127 | 'change_btc': '-0.019294922814297986', 'change_usd': '-0.0033825963089133683', 128 | 'asset_key': 'eosio.token:EOS', 'confirmations': 64, 'capitalization': 0}} 129 | Account a8cefb2e-cb93-338f-aba7-32a3a635ad02 's EOS account name is eoswithmixin, wallet address is 70dae97b661ca9f80cb0e6549feeba6c 130 | ``` 131 | 132 | ### 存入比特币与读取比特币余额 133 | 现在,你可以向比特币的钱包存币了。 134 | 135 | 当然,在比特币网络里转币,手续费是相当贵的,费用的中位数在0.001BTC,按当前4000美元的价格,在4美元左右,有一个方便的办法,如果你有[Mixin Messenger](https://mixin.one/messenger)帐号,里面并且有比特币的话,可以直接提现比特币到新创建的帐号的比特币充值地址,它们在同一个Mixin Network网络内,手续费为0,而且1秒到账。 136 | 137 | 下面的代码,可以读取比特币钱包余额. 138 | ```python 139 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 140 | print("Account %s \'s balance is %s " %(userid,btcInfo.get("data").get("balance"))) 141 | ``` 142 | ### Mixin Network网内免手续费的,并且即时确认 143 | 任何币在Mixin Network内部的交易,都是无手续费的,并且立刻到账。 144 | 前期准备: 账户设置了PIN 145 | 146 | 对于新创建的帐号,我们通过updatePin来设置新PIN码, 代码如下: 147 | ```python 148 | pinInfo = mixinApiNewUserInstance.updatePin(PIN,"") 149 | print(pinInfo) 150 | ``` 151 | #### Mixin Network帐号之间的比特币支付 152 | 通过Mixin Messenger,我们可以先转比特币给机器人,然后让机器人转币给新用户。 153 | ```python 154 | mixinApiNewUserInstance = generateMixinAPI(private_key, 155 | pin_token, 156 | session_id, 157 | userid, 158 | pin,"") 159 | btcInfo = mixinApiBotInstance.transferTo(MASTER_UUID, BTC_ASSET_ID, AMOUNT, "") 160 | print(btcInfo) 161 | ``` 162 | 163 | 读取Bitcoin的余额,来确认比特币是不是转成功了! 注意**$mixinSdkNew**是新用户的。 164 | ```python 165 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 166 | print("Account %s \'s balance is %s " %(userid,btcInfo.get("data").get("balance"))) 167 | ``` 168 | ### 如何将比特币存入你的冷钱包或者第三方交易所 169 | 如果你希望将币存入你的冷钱包或者第三方交易所, 先要得到冷钱包或者你在第三方交易所的钱包地址,然后将钱包地址提交到Mixin Network. 170 | 171 | - **要点提示**: 提现是需要支付收续费的,准备好比特币包地址! 172 | 173 | #### 增加目的钱包地址到Mixin Network 174 | 调用createAddress API, 将会返回一个address_id,下一步的提现操作会用到这个id。 175 | ```python 176 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 177 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 178 | BTC_WALLET_ADDR = "14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C"; 179 | btcInfo = mixinApiBotInstance.createAddress(BTC_ASSET_ID, BTC_WALLET_ADDR,"BTC","","") 180 | print(btcInfo) 181 | ``` 182 | 这里的 **14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C** 就是一个比特币钱包地址, 如下所示,提现费用是0.0034802 BTC, address_id 是"345855b5-56a5-4f3b-ba9e-d99601ef86c1". 183 | ```python 184 | {'data': {'type': 'address', 185 | 'address_id': '47998e2f-2761-45ce-9a6c-6f167b20c78b', 186 | 'asset_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 187 | 'public_key': '14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C', 'label': 'BTC', 188 | 'account_name': '', 'account_tag': '', 189 | 'fee': '0.0034802', 'reserve': '0', 'dust': '0.0001', 190 | 'updated_at': '2019-02-26T00:03:05.028140704Z'}} 191 | ``` 192 | 193 | 如果你操作的是EOS, 示例代码如下: 194 | ```python 195 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 196 | EOS_WALLET_ADDR = "3e2f70914c8e8abbf60040207c8aae62"; 197 | EOS_ACCOUNT_NAME = "eoswithmixin"; 198 | eosInfo = mixinApiBotInstance.createAddress(EOS_ASSET_ID, "","",EOS_ACCOUNT_NAME,EOS_WALLET_ADDR) 199 | print(eosInfo) 200 | ``` 201 | 202 | #### 创建提现地址成功后,你可以用readAddress读取最新的提现费。 203 | ```python 204 | addr_id = btcInfo.get("data").get("address_id") 205 | addrInfo = mixinApiBotInstance.getAddress(addr_id) 206 | print(addrInfo) 207 | ``` 208 | #### 提交提现请求,Mixin Network会即时处理提现请求. 209 | 提交提现请求到Mixin Network, $btcInfo["address_id"]就是createAddress创建的。 210 | ```python 211 | mixinApiBotInstance.withdrawals(btcInfo.get("data").get("address_id"),AMOUNT,"") 212 | ``` 213 | #### 可以通过blockchain explore来查看进度. 214 | 215 | [完整的代码在这儿](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/call_apis.py) 216 | -------------------------------------------------------------------------------- /README3.md: -------------------------------------------------------------------------------- 1 | # Python Bitcoin tutorial based on Mixin Network: Create a Bitcoin wallet 2 | ![cover](https://github.com/wenewzhang/mixin_labs-python-bot/raw/master/Bitcoin_python.jpg) 3 | We have created a bot to [echo message](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README.md) and [echo Bitcoin](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/README2.md). 4 | 5 | # What you will learn from this chapter 6 | 1. How to create Bitcoin wallet 7 | 2. How to read Bitcoin balance 8 | 3. How to send Bitcoin with zero transaction fee and confirmed in 1 second 9 | 4. How to send Bitcoin to other wallet 10 | 11 | 12 | Pre-request: You should have a Mixin Network account. Create an account can be done by one line code: 13 | 14 | ```python 15 | userInfo = mixinApiBotInstance.createUser(session_key.decode(),"Tom Bot") 16 | ``` 17 | The function in Python SDK create a RSA key pair automatically, then call Mixin Network to create an account. last the function return all account information. 18 | 19 | ```python 20 | //Create User api include all account information 21 | userInfo.get("data").get("pin_token"), 22 | userInfo.get("data").get("session_id"), 23 | userInfo.get("data").get("user_id"), 24 | ``` 25 | 26 | Result of createUser is: 27 | ```python 28 | {'data': {'type': 'user', 'user_id': '2f25b669-15e7-392c-a1d5-fe7ba43bdf37', 'identity_number': '0', 29 | 'full_name': 'Tom Bot', 'avatar_url': '', 'relationship': '', 'mute_until': '0001-01-01T00:00:00Z', 30 | 'created_at': '2019-02-22T06:23:41.754573722Z', 'is_verified': False, 31 | 'session_id': '284c7b39-3284-4cf6-9354-87df30ec7d57', 'phone': '', 32 | 'pin_token':'g4upUgBXa8ATk7yxL6B94HgI4GV4sG4t8Wyn6uTu2Q2scH11UMQ5bYDb6Md+3LRQqRjEdRFcLlHijXGBihRweTaKTZjHQqolWbZcffesVIias6WppV/QMu4TzXCuKa5xpj3uhjL+yPyfWTLGUaVJTJN9n7PQmHSIUBXrovbfodk=', 33 | 'invitation_code': '', 'code_id': '', 'code_url': '', 'has_pin': False, 34 | 'receive_message_source': 'EVERYBODY', 'accept_conversation_source': 'EVERYBODY'}} 35 | ``` 36 | 37 | Now you need to carefully keep the account information. You need these information to read asset balance and other content. 38 | ### Create Bitcoin wallet for the Mixin Network account 39 | The Bitcoin wallet is not generated automatically at same time when we create Mixin Network account. Read Bitcoin asset once to generate a Bitcoin wallet. 40 | ```python 41 | def readAssetAddress(asset_id,isBTC = True): 42 | with open('new_users.csv', newline='') as csvfile: 43 | reader = csv.reader(csvfile) 44 | for row in reader: 45 | pin = row.pop() 46 | userid = row.pop() 47 | session_id = row.pop() 48 | pin_token = row.pop() 49 | private_key = row.pop() 50 | mixinApiNewUserInstance = generateMixinAPI(private_key, 51 | pin_token, 52 | session_id, 53 | userid, 54 | pin,"") 55 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 56 | print(btcInfo) 57 | if isBTC: 58 | print("Account %s \'s Bitcoin wallet address is %s " %(userid,btcInfo.get("data").get("public_key"))) 59 | else: 60 | print("Account %s \'s EOS account name is %s, wallet address is %s " %(userid, 61 | btcInfo.get("data").get("account_name"), 62 | btcInfo.get("data").get("account_tag"))) 63 | ``` 64 | You can found information about Bitcoin asset in the account. Public key is the Bitcoin deposit address. Full response of read Bitcoin asset is 65 | ```python 66 | {'data': {'type': 'asset', 'asset_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 67 | 'chain_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 'symbol': 'BTC', 'name': 'Bitcoin', 68 | 'icon_url': 'https://images.mixin.one/HvYGJsV5TGeZ-X9Ek3FEQohQZ3fE9LBEBGcOcn4c4BNHovP4fW4YB97Dg5LcXoQ1hUjMEgjbl1DPlKg1TW7kK6XP=s128', 'balance': '0', 69 | 'public_key': '1AYAMaRi3j5rXoFLmhJBFxvUEgGt8zeF4k', 'account_name': '', 'account_tag': '', 70 | 'price_btc': '1', 'price_usd': '3979.12975801', 'change_btc': '0', 71 | 'change_usd': '-0.0018925165548280905', 'asset_key': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 72 | 'confirmations': 12, 'capitalization': 0}} 73 | ``` 74 | The API provide many information about Bitcoin asset. 75 | * Deposit address:[public_key] 76 | * Logo: [icon_url] 77 | * Asset name:[name] 78 | * Asset uuid in Mixin network: [asset_key] 79 | * Price in USD from Coinmarketcap.com: [price_usd] 80 | * Least confirmed blocks before deposit is accepted by Mixin network:[confirmations] 81 | 82 | 83 | ### Private key? 84 | Where is Bitcoin private key? The private key is protected by multi signature inside Mixin Network so it is invisible for user. Bitcoin asset can only be withdraw to other address when user provide correct RSA private key signature, PIN code and Session key. 85 | 86 | ### Not only Bitcoin, but also Ethereum, EOS 87 | The account not only contain a Bitcoin wallet, but also contains wallet for Ethereum, EOS, etc. Full blockchain support [list](https://mixin.one/network/chains). All ERC20 Token and EOS token are supported by the account. 88 | 89 | Create other asset wallet is same as create Bitcoin wallet, just read the asset. 90 | #### Mixin Network support cryptocurrencies (2019-02-19) 91 | 92 | |crypto |uuid in Mixin Network 93 | |---|--- 94 | |EOS|6cfe566e-4aad-470b-8c9a-2fd35b49c68d 95 | |CNB|965e5c6e-434c-3fa9-b780-c50f43cd955c 96 | |BTC|c6d0c728-2624-429b-8e0d-d9d19b6592fa 97 | |ETC|2204c1ee-0ea2-4add-bb9a-b3719cfff93a 98 | |XRP|23dfb5a5-5d7b-48b6-905f-3970e3176e27 99 | |XEM|27921032-f73e-434e-955f-43d55672ee31 100 | |ETH|43d61dcd-e413-450d-80b8-101d5e903357 101 | |DASH|6472e7e3-75fd-48b6-b1dc-28d294ee1476 102 | |DOGE|6770a1e5-6086-44d5-b60f-545f9d9e8ffd 103 | |LTC|76c802a2-7c88-447f-a93e-c29c9e5dd9c8 104 | |SC|990c4c29-57e9-48f6-9819-7d986ea44985 105 | |ZEN|a2c5d22b-62a2-4c13-b3f0-013290dbac60 106 | |ZEC|c996abc9-d94e-4494-b1cf-2a3fd3ac5714 107 | |BCH|fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0 108 | 109 | If you read EOS deposit address, the deposit address is composed of two parts: account_name and account tag. When you transfer EOS token to your account in Mixin network, you should fill both account name and memo. The memo content is value of 'account_tag'. 110 | Result of read EOS asset is: 111 | ```python 112 | {'data': {'type': 'asset', 'asset_id': '6cfe566e-4aad-470b-8c9a-2fd35b49c68d', 113 | 'chain_id': '6cfe566e-4aad-470b-8c9a-2fd35b49c68d', 114 | 'symbol': 'EOS', 'name': 'EOS', 115 | 'icon_url': 'https://images.mixin.one/a5dtG-IAg2IO0Zm4HxqJoQjfz-5nf1HWZ0teCyOnReMd3pmB8oEdSAXWvFHt2AJkJj5YgfyceTACjGmXnI-VyRo=s128', 116 | 'balance': '0', 'public_key': '', 117 | 'account_name': 'eoswithmixin', 'account_tag': '185b27f83d76dad3033ee437195aac11', 118 | 'price_btc': '0.00096903', 'price_usd': '3.8563221', 'change_btc': '0.00842757579765049', 119 | 'change_usd': '0.0066057628802373095', 'asset_key': 'eosio.token:EOS', 120 | 'confirmations': 64, 'capitalization': 0}} 121 | ``` 122 | 123 | ### Deposit Bitcoin and read balance 124 | Now you can deposit Bitcoin into the deposit address. 125 | 126 | This is maybe too expensive for this tutorial. There is a free and lightening fast solution to deposit Bitcoin: add the address in your Mixin messenger account withdrawal address and withdraw small amount Bitcoin from your account to the address. It is free and confirmed instantly because they are both on Mixin Network. 127 | 128 | Now you can read Bitcoin balance of the account. 129 | ```python 130 | btcInfo = mixinApiNewUserInstance.getAsset("c6d0c728-2624-429b-8e0d-d9d19b6592fa"); 131 | print(btcInfo); 132 | ``` 133 | ### Send Bitcoin inside Mixin Network to enjoy instant confirmation and ZERO transaction fee 134 | Any transaction happen between Mixin network account is free and is confirmed in 1 second. 135 | 136 | #### Send Bitcoin to another Mixin Network account 137 | We can send Bitcoin to our bot through Mixin Messenger, and then transfer Bitcoin from bot to new user. 138 | 139 | ```python 140 | mixinApiBotInstance = MIXIN_API(mixin_config) 141 | //$user_info["user_id"] generated by create user; 142 | mixinApiBotInstance.transferTo(userid, BTC_ASSET_ID, AMOUNT, "") 143 | ``` 144 | 145 | Read bot's Bitcoin balance to confirm the transaction. 146 | Caution: **mixinApiNewUserInstance** is for the New User! 147 | ```python 148 | def readAssetBalance(asset_id): 149 | with open('new_users.csv', newline='') as csvfile: 150 | reader = csv.reader(csvfile) 151 | for row in reader: 152 | pin = row.pop() 153 | userid = row.pop() 154 | session_id = row.pop() 155 | pin_token = row.pop() 156 | private_key = row.pop() 157 | mixinApiNewUserInstance = generateMixinAPI(private_key, 158 | pin_token, 159 | session_id, 160 | userid, 161 | pin,"") 162 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 163 | print("Account %s \'s balance is %s " %(userid,btcInfo.get("data").get("balance"))) 164 | ``` 165 | 166 | ### Send Bitcoin to another Bitcoin exchange or wallet 167 | If you want to send Bitcoin to another exchange or wallet, you need to know the destination deposit address, then add the address in withdraw address list of the Mixin network account. 168 | 169 | Pre-request: Withdrawal address is added and know the Bitcoin withdrawal fee 170 | 171 | #### Add destination address to withdrawal address list 172 | Call createAddress, the ID of address will be returned in result of API and is required soon. 173 | ```python 174 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 175 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 176 | BTC_WALLET_ADDR = "14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C"; 177 | btcInfo = mixinApiBotInstance.createAddress(BTC_ASSET_ID, BTC_WALLET_ADDR,"BTC","","") 178 | print(btcInfo) 179 | ``` 180 | The **14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C** is a Bitcoin wallet address, Output like below, The API result contains the withdrawal address ID, fee is 0.0034802 BTC. 181 | ```python 182 | {'data': {'type': 'address', 'address_id': '47998e2f-2761-45ce-9a6c-6f167b20c78b', 183 | 'asset_id': 'c6d0c728-2624-429b-8e0d-d9d19b6592fa', 184 | 'public_key': '14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C', 'label': 'BTC', 185 | 'account_name': '', 'account_tag': '', 186 | 'fee': '0.0034802', 'reserve': '0', 'dust': '0.0001', 187 | 'updated_at': '2019-02-26T00:03:05.028140704Z'}} 188 | ``` 189 | If you want create a EOS address, call it like below: 190 | ```python 191 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 192 | EOS_WALLET_ADDR = "3e2f70914c8e8abbf60040207c8aae62"; 193 | EOS_ACCOUNT_NAME = "eoswithmixin"; 194 | eosInfo = mixinApiBotInstance.createAddress(EOS_ASSET_ID, "","",EOS_ACCOUNT_NAME,EOS_WALLET_ADDR) 195 | print(eosInfo) 196 | ``` 197 | #### Read withdraw fee anytime 198 | ```python 199 | addr_id = btcInfo.get("data").get("address_id") 200 | addrInfo = mixinApiBotInstance.getAddress(addr_id) 201 | print(addrInfo) 202 | ``` 203 | 204 | #### Send Bitcoin to destination address 205 | Submit the withdrawal request to Mixin Network, the btcInfo.get("data").get("address_id") is the address id it's return by createAddress 206 | ```python 207 | mixinApiBotInstance.withdrawals(btcInfo.get("data").get("address_id"),AMOUNT,"") 208 | ``` 209 | #### Confirm the transaction in blockchain explore 210 | 211 | [Full source code](https://github.com/wenewzhang/mixin_labs-python-bot/blob/master/call_apis.py) 212 | -------------------------------------------------------------------------------- /app-mini.py: -------------------------------------------------------------------------------- 1 | from mixin_ws_api import MIXIN_WS_API 2 | from mixin_api import MIXIN_API 3 | import mixin_config 4 | 5 | import json 6 | import time 7 | from io import BytesIO 8 | import base64 9 | import gzip 10 | 11 | try: 12 | import thread 13 | except ImportError: 14 | import _thread as thread 15 | 16 | 17 | def on_message(ws, message): 18 | inbuffer = BytesIO(message) 19 | 20 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 21 | rdata_injson = f.read() 22 | rdata_obj = json.loads(rdata_injson) 23 | print("-------json object begin---------") 24 | print(rdata_obj) 25 | print("-------json object end---------") 26 | action = rdata_obj["action"] 27 | 28 | if rdata_obj["data"] is not None: 29 | print("data in message:",rdata_obj["data"]) 30 | 31 | if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 32 | print(rdata_obj["data"]["category"]) 33 | 34 | if action == "CREATE_MESSAGE": 35 | 36 | data = rdata_obj["data"] 37 | msgid = data["message_id"] 38 | typeindata = data["type"] 39 | categoryindata = data["category"] 40 | userId = data["user_id"] 41 | conversationId = data["conversation_id"] 42 | dataindata = data["data"] 43 | 44 | realData = base64.b64decode(dataindata) 45 | 46 | MIXIN_WS_API.replayMessage(ws, msgid) 47 | 48 | if 'error' in rdata_obj: 49 | return 50 | 51 | if categoryindata == "PLAIN_TEXT": 52 | realData = realData.decode('utf-8') 53 | print("dataindata",realData) 54 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 55 | 56 | 57 | if __name__ == "__main__": 58 | 59 | # mixin_api = MIXIN_API(mixin_config) 60 | while True: 61 | mixin_ws = MIXIN_WS_API(on_message=on_message) 62 | mixin_ws.run() 63 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from mixin_ws_api import MIXIN_WS_API 2 | from mixin_api import MIXIN_API 3 | import mixin_config 4 | 5 | import json 6 | import time 7 | from io import BytesIO 8 | import base64 9 | import gzip 10 | 11 | try: 12 | import thread 13 | except ImportError: 14 | import _thread as thread 15 | 16 | MASTER_UUID = "0b4f49dc-8fb4-4539-9a89-fb3afc613747"; 17 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 18 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 19 | 20 | def on_message(ws, message): 21 | inbuffer = BytesIO(message) 22 | 23 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 24 | rdata_injson = f.read() 25 | rdata_obj = json.loads(rdata_injson) 26 | print("-------json object begin---------") 27 | print(rdata_obj) 28 | print("-------json object end---------") 29 | action = rdata_obj["action"] 30 | if action == "ERROR": 31 | return; 32 | 33 | # if rdata_obj["data"] is not None: 34 | # print("data in message:",rdata_obj["data"]) 35 | # 36 | # if rdata_obj["data"] is not None and rdata_obj["data"]["category"] is not None: 37 | # print(rdata_obj["data"]["category"]) 38 | 39 | if action == "CREATE_MESSAGE": 40 | 41 | data = rdata_obj["data"] 42 | msgid = data["message_id"] 43 | typeindata = data["type"] 44 | categoryindata = data["category"] 45 | userId = data["user_id"] 46 | conversationId = data["conversation_id"] 47 | dataindata = data["data"] 48 | created_at = data["created_at"] 49 | updated_at = data["updated_at"] 50 | 51 | realData = base64.b64decode(dataindata) 52 | 53 | MIXIN_WS_API.replayMessage(ws, msgid) 54 | 55 | print('userId', userId) 56 | print("created_at",created_at) 57 | 58 | if categoryindata == "PLAIN_TEXT": 59 | realData = realData.decode('utf-8') 60 | print("dataindata",realData) 61 | if 'a' == realData: 62 | print('send a link APP_CARD') 63 | MIXIN_WS_API.sendAppCard(ws, conversationId, mixin_config.client_id, 64 | BTC_ASSET_ID, "0.0001", 65 | "https://images.mixin.one/HvYGJsV5TGeZ-X9Ek3FEQohQZ3fE9LBEBGcOcn4c4BNHovP4fW4YB97Dg5LcXoQ1hUjMEgjbl1DPlKg1TW7kK6XP=s128", 66 | "Pay BTC 0.0001","topay") 67 | return 68 | if 'g' == realData: 69 | print('send a link APP_BUTTON_GROUP') 70 | btnBTC = MIXIN_WS_API.packButton(mixin_config.client_id, BTC_ASSET_ID, "0.0001","BTC pay") 71 | btnEOS = MIXIN_WS_API.packButton(mixin_config.client_id, EOS_ASSET_ID, "0.01","EOS pay","#0080FF") 72 | buttons = [btnBTC,btnEOS] 73 | MIXIN_WS_API.sendAppButtonGroup(ws, conversationId, userId, buttons) 74 | return 75 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, realData) 76 | elif categoryindata == "SYSTEM_ACCOUNT_SNAPSHOT": 77 | rdJs = json.loads(realData) 78 | if ( float(rdJs["amount"]) > 0 ): 79 | mixin_api.transferTo(userId, rdJs["asset_id"], rdJs["amount"], "") 80 | 81 | if __name__ == "__main__": 82 | 83 | mixin_api = MIXIN_API(mixin_config) 84 | 85 | mixin_ws = MIXIN_WS_API(on_message=on_message) 86 | 87 | mixin_ws.run() 88 | -------------------------------------------------------------------------------- /app_card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenewzhang/mixin_labs-python-bot/3ba2734693512097f421416807624f8100990bc6/app_card.jpg -------------------------------------------------------------------------------- /button_test.py: -------------------------------------------------------------------------------- 1 | from mixin_ws_api import MIXIN_WS_API 2 | import base64 3 | 4 | MASTER_UUID = "0b4f49dc-8fb4-4539-9a89-fb3afc613747"; 5 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 6 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 7 | 8 | btnBTC = MIXIN_WS_API.packButton(MASTER_UUID, BTC_ASSET_ID, "0.0001","BTC pay") 9 | btnEOS = MIXIN_WS_API.packButton(MASTER_UUID, EOS_ASSET_ID, "0.01","EOS pay","#0080FF") 10 | print(btnBTC) 11 | print(btnEOS) 12 | buttons = [btnBTC,btnEOS] 13 | print(type(buttons)) 14 | buttons_utf = '[' + ','.join(str(btn) for btn in buttons) + ']' 15 | print(buttons_utf) 16 | enButtons = base64.b64encode(buttons_utf.encode('utf-8')).decode(encoding='utf-8') 17 | print(enButtons) 18 | -------------------------------------------------------------------------------- /call_apis.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | from mixin_api import MIXIN_API 3 | import mixin_config 4 | import json 5 | import csv 6 | import time 7 | 8 | PIN = "945689"; 9 | PIN2 = "845689"; 10 | MASTER_ID = "37222956"; 11 | MASTER_UUID = "0b4f49dc-8fb4-4539-9a89-fb3afc613747"; 12 | BTC_ASSET_ID = "c6d0c728-2624-429b-8e0d-d9d19b6592fa"; 13 | EOS_ASSET_ID = "6cfe566e-4aad-470b-8c9a-2fd35b49c68d"; 14 | BTC_WALLET_ADDR = "14T129GTbXXPGXXvZzVaNLRFPeHXD1C25C"; 15 | AMOUNT = "0.001"; 16 | 17 | # // Mixin Network support cryptocurrencies (2019-02-19) 18 | # // |EOS|6cfe566e-4aad-470b-8c9a-2fd35b49c68d 19 | # // |CNB|965e5c6e-434c-3fa9-b780-c50f43cd955c 20 | # // |BTC|c6d0c728-2624-429b-8e0d-d9d19b6592fa 21 | # // |ETC|2204c1ee-0ea2-4add-bb9a-b3719cfff93a 22 | # // |XRP|23dfb5a5-5d7b-48b6-905f-3970e3176e27 23 | # // |XEM|27921032-f73e-434e-955f-43d55672ee31 24 | # // |ETH|43d61dcd-e413-450d-80b8-101d5e903357 25 | # // |DASH|6472e7e3-75fd-48b6-b1dc-28d294ee1476 26 | # // |DOGE|6770a1e5-6086-44d5-b60f-545f9d9e8ffd 27 | # // |LTC|76c802a2-7c88-447f-a93e-c29c9e5dd9c8 28 | # // |SC|990c4c29-57e9-48f6-9819-7d986ea44985 29 | # // |ZEN|a2c5d22b-62a2-4c13-b3f0-013290dbac60 30 | # // |ZEC|c996abc9-d94e-4494-b1cf-2a3fd3ac5714 31 | # // |BCH|fd11b6e3-0b87-41f1-a41f-f0e9b49e5bf0 32 | 33 | def pubkeyContent(inputContent): 34 | contentWithoutHeader= inputContent[len("-----BEGIN PUBLIC KEY-----") + 1:] 35 | contentWithoutTail = contentWithoutHeader[:-1 * (len("-----END PUBLIC KEY-----") + 1)] 36 | contentWithoutReturn = contentWithoutTail[:64] + contentWithoutTail[65:129] + contentWithoutTail[130:194] + contentWithoutTail[195:] 37 | return contentWithoutReturn 38 | 39 | def generateMixinAPI(private_key,pin_token,session_id,user_id,pin,client_secret): 40 | mixin_config.private_key = private_key 41 | mixin_config.pin_token = pin_token 42 | mixin_config.pay_session_id = session_id 43 | mixin_config.client_id = user_id 44 | mixin_config.client_secret = client_secret 45 | mixin_config.pay_pin = pin 46 | return MIXIN_API(mixin_config) 47 | 48 | def readAssetBalance(asset_id): 49 | with open('new_users.csv', newline='') as csvfile: 50 | reader = csv.reader(csvfile) 51 | for row in reader: 52 | pin = row.pop() 53 | userid = row.pop() 54 | session_id = row.pop() 55 | pin_token = row.pop() 56 | private_key = row.pop() 57 | mixinApiNewUserInstance = generateMixinAPI(private_key, 58 | pin_token, 59 | session_id, 60 | userid, 61 | pin,"") 62 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 63 | print("Account %s \'s balance is %s " %(userid,btcInfo.get("data").get("balance"))) 64 | 65 | def readAssetAddress(asset_id,isBTC = True): 66 | with open('new_users.csv', newline='') as csvfile: 67 | reader = csv.reader(csvfile) 68 | for row in reader: 69 | pin = row.pop() 70 | userid = row.pop() 71 | session_id = row.pop() 72 | pin_token = row.pop() 73 | private_key = row.pop() 74 | mixinApiNewUserInstance = generateMixinAPI(private_key, 75 | pin_token, 76 | session_id, 77 | userid, 78 | pin,"") 79 | btcInfo = mixinApiNewUserInstance.getAsset(asset_id) 80 | print(btcInfo) 81 | if isBTC: 82 | print("Account %s \'s Bitcoin wallet address is %s " %(userid,btcInfo.get("data").get("public_key"))) 83 | else: 84 | print("Account %s \'s EOS account name is %s, wallet address is %s " %(userid, 85 | btcInfo.get("data").get("account_name"), 86 | btcInfo.get("data").get("account_tag"))) 87 | 88 | # print(btcInfo.get("data").get("public_key")) 89 | 90 | mixinApiBotInstance = MIXIN_API(mixin_config) 91 | 92 | PromptMsg = "1: Create user and update PIN\n2: Read Bitcoin balance \n3: Read Bitcoin Address\n4: Read EOS balance\n" 93 | PromptMsg += "5: Read EOS address\n6: Transfer Bitcoin from bot to new account\n7: Transfer Bitcoin from new account to Master\n" 94 | PromptMsg += "8: Withdraw bot's Bitcoin\n9: Withdraw bot's EOS\na: Verify Pin\nd: Create Address and Delete it\nr: Create Address and read it\n" 95 | PromptMsg += "gensnap: Generate snapshot for one of my snapshot\n" 96 | PromptMsg += "q: Exit \nMake your choose:" 97 | while ( 1 > 0 ): 98 | cmd = input(PromptMsg) 99 | if (cmd == 'q' ): 100 | exit() 101 | print("Run...") 102 | if ( cmd == 'gensnap'): 103 | snapshot_id = input("snapshot id:") 104 | snapshot = mixinApiBotInstance.account_snapshot_prove(snapshot_id) 105 | print("snapshot information start:") 106 | print(snapshot[0]) 107 | print("snapshot information end :---") 108 | print("\ncurl command to verify it ------") 109 | print(snapshot[1]) 110 | print("\nend ------") 111 | counter_user_id = snapshot[0]["data"]["opponent_id"] 112 | result = mixinApiBotInstance.getUserInfo_prove(counter_user_id) 113 | 114 | userInfo = result[0] 115 | print("counter user id in network:" +userInfo["data"]["user_id"]) 116 | print("counter user id in messenger:" + userInfo["data"]["identity_number"]) 117 | print("counter user name in messenger:" + userInfo["data"]["full_name"]) 118 | print("\ncurl command to veify it: ------") 119 | print(result[1]) 120 | print("\n end ----") 121 | 122 | 123 | 124 | if ( cmd == '1' ): 125 | key = RSA.generate(1024) 126 | pubkey = key.publickey() 127 | print(key.exportKey()) 128 | print(pubkey.exportKey()) 129 | private_key = key.exportKey() 130 | session_key = pubkeyContent(pubkey.exportKey()) 131 | # print(session_key) 132 | print(session_key.decode()) 133 | userInfo = mixinApiBotInstance.createUser(session_key.decode(),"Tom Bot") 134 | print(userInfo.get("data").get("user_id")) 135 | with open('new_users.csv', 'a', newline='') as csvfile: 136 | csvwriter = csv.writer(csvfile) 137 | csvwriter.writerow([private_key.decode(), 138 | userInfo.get("data").get("pin_token"), 139 | userInfo.get("data").get("session_id"), 140 | userInfo.get("data").get("user_id"), 141 | PIN]) 142 | mixinApiNewUserInstance = generateMixinAPI(private_key.decode(), 143 | userInfo.get("data").get("pin_token"), 144 | userInfo.get("data").get("session_id"), 145 | userInfo.get("data").get("user_id"), 146 | PIN,"") 147 | pinInfo = mixinApiNewUserInstance.updatePin(PIN,"") 148 | print(pinInfo) 149 | time.sleep(3) 150 | # mixinApiNewUserInstance.pay_pin = PIN 151 | pinInfo2 = mixinApiNewUserInstance.verifyPin() 152 | print(pinInfo2) 153 | if ( cmd == '2' ): 154 | print("Read Bitcoin(uuid:%s) balance" %(BTC_ASSET_ID)) 155 | readAssetBalance(BTC_ASSET_ID) 156 | if ( cmd == '3' ): 157 | print("Read Bitcoin(uuid:%s) address" %(BTC_ASSET_ID)) 158 | readAssetAddress(BTC_ASSET_ID) 159 | if ( cmd == '4' ): 160 | print("Read EOS(uuid:%s) balance" %(EOS_ASSET_ID)) 161 | readAssetBalance(EOS_ASSET_ID) 162 | if ( cmd == '5' ): 163 | print("Read Bitcoin(uuid:%s) address" %(EOS_ASSET_ID)) 164 | readAssetAddress(EOS_ASSET_ID,False) 165 | if ( cmd == '6' ): 166 | with open('new_users.csv', newline='') as csvfile: 167 | reader = csv.reader(csvfile) 168 | for row in reader: 169 | row.pop() 170 | userid = row.pop() 171 | mixinApiBotInstance.transferTo(userid, BTC_ASSET_ID, AMOUNT, "") 172 | if ( cmd == '7' ): 173 | with open('new_users.csv', newline='') as csvfile: 174 | reader = csv.reader(csvfile) 175 | for row in reader: 176 | pin = row.pop() 177 | userid = row.pop() 178 | session_id = row.pop() 179 | pin_token = row.pop() 180 | private_key = row.pop() 181 | mixinApiNewUserInstance = generateMixinAPI(private_key, 182 | pin_token, 183 | session_id, 184 | userid, 185 | pin,"") 186 | btcInfo = mixinApiBotInstance.transferTo(MASTER_UUID, BTC_ASSET_ID, AMOUNT, "") 187 | print(btcInfo) 188 | if ( cmd == '8' ): 189 | with open('new_users.csv', newline='') as csvfile: 190 | reader = csv.reader(csvfile) 191 | for row in reader: 192 | pin = row.pop() 193 | userid = row.pop() 194 | session_id = row.pop() 195 | pin_token = row.pop() 196 | private_key = row.pop() 197 | print(pin) 198 | mixinApiNewUserInstance = generateMixinAPI(private_key, 199 | pin_token, 200 | session_id, 201 | userid, 202 | pin,"") 203 | btcInfo = mixinApiBotInstance.createAddress(BTC_ASSET_ID, BTC_WALLET_ADDR,"BTC") 204 | # mixinApiBotInstance.withdrawals(btcInfo.get("data").get("address_id"),AMOUNT,"") 205 | print(btcInfo) 206 | if ( cmd == '9' ): 207 | with open('new_users.csv', newline='') as csvfile: 208 | reader = csv.reader(csvfile) 209 | for row in reader: 210 | pin = row.pop() 211 | userid = row.pop() 212 | session_id = row.pop() 213 | pin_token = row.pop() 214 | private_key = row.pop() 215 | print(pin) 216 | mixinApiNewUserInstance = generateMixinAPI(private_key, 217 | pin_token, 218 | session_id, 219 | userid, 220 | pin,"") 221 | eosInfo = mixinApiBotInstance.createAddressEOS(EOS_ASSET_ID,"eoswithmixin","d80363afcc466fbaf2daa7328ae2adfa") 222 | # mixinApiBotInstance.withdrawals(btcInfo.get("data").get("address_id"),AMOUNT,"") 223 | print(eosInfo) 224 | if ( cmd == 'a' ): 225 | with open('new_users.csv', newline='') as csvfile: 226 | reader = csv.reader(csvfile) 227 | for row in reader: 228 | pin = row.pop() 229 | userid = row.pop() 230 | session_id = row.pop() 231 | pin_token = row.pop() 232 | private_key = row.pop() 233 | print(pin) 234 | mixinApiNewUserInstance = generateMixinAPI(private_key, 235 | pin_token, 236 | session_id, 237 | userid, 238 | pin,"") 239 | btcInfo = mixinApiNewUserInstance.verifyPin() 240 | print(btcInfo) 241 | if ( cmd == 'd' ): 242 | with open('new_users.csv', newline='') as csvfile: 243 | reader = csv.reader(csvfile) 244 | for row in reader: 245 | pin = row.pop() 246 | userid = row.pop() 247 | session_id = row.pop() 248 | pin_token = row.pop() 249 | private_key = row.pop() 250 | print(pin) 251 | mixinApiNewUserInstance = generateMixinAPI(private_key, 252 | pin_token, 253 | session_id, 254 | userid, 255 | pin,"") 256 | btcInfo = mixinApiBotInstance.createAddress(BTC_ASSET_ID, BTC_WALLET_ADDR,"BTC","","") 257 | addr_id = btcInfo.get("data").get("address_id") 258 | print(addr_id) 259 | delInfo = mixinApiBotInstance.delAddress(addr_id) 260 | print(delInfo) 261 | if ( cmd == 'r' ): 262 | with open('new_users.csv', newline='') as csvfile: 263 | reader = csv.reader(csvfile) 264 | for row in reader: 265 | pin = row.pop() 266 | userid = row.pop() 267 | session_id = row.pop() 268 | pin_token = row.pop() 269 | private_key = row.pop() 270 | print(pin) 271 | mixinApiNewUserInstance = generateMixinAPI(private_key, 272 | pin_token, 273 | session_id, 274 | userid, 275 | pin,"") 276 | btcInfo = mixinApiBotInstance.createAddress(BTC_ASSET_ID, BTC_WALLET_ADDR,"BTC","","") 277 | addr_id = btcInfo.get("data").get("address_id") 278 | print(addr_id) 279 | addrInfo = mixinApiBotInstance.getAddress(addr_id) 280 | print(addrInfo) 281 | -------------------------------------------------------------------------------- /createUser.py: -------------------------------------------------------------------------------- 1 | from Crypto.PublicKey import RSA 2 | from mixin_api import MIXIN_API 3 | # from random_word import RandomWords 4 | import mixin_config 5 | import json 6 | def pubkeyContent(inputContent): 7 | contentWithoutHeader= inputContent[len("-----BEGIN PUBLIC KEY-----") + 1:] 8 | contentWithoutTail = contentWithoutHeader[:-1 * (len("-----END PUBLIC KEY-----") + 1)] 9 | contentWithoutReturn = contentWithoutTail[:64] + contentWithoutTail[65:129] + contentWithoutTail[130:194] + contentWithoutTail[195:] 10 | return contentWithoutReturn 11 | 12 | mixin_api = MIXIN_API(mixin_config) 13 | 14 | key = RSA.generate(1024) 15 | pubkey = key.publickey() 16 | print(key.exportKey()) 17 | print(pubkey.exportKey()) 18 | private_key = key.exportKey() 19 | session_key = pubkeyContent(pubkey.exportKey()) 20 | # print(session_key) 21 | print(session_key.decode()) 22 | userInfo = mixin_api.createUser(session_key.decode(),"Tom Bot") 23 | print(userInfo) 24 | # r = RandomWords() 25 | # r.get_random_word() 26 | -------------------------------------------------------------------------------- /csv-test.py: -------------------------------------------------------------------------------- 1 | import csv 2 | # with open('eggs.csv', 'a', newline='') as csvfile: 3 | # spamwriter = csv.writer(csvfile) 4 | # spamwriter.writerow(['Spam'] * 5 + ['Baked Beans']) 5 | # spamwriter.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam']) 6 | 7 | with open('new_users.csv', newline='') as f: 8 | reader = csv.reader(f) 9 | # reader = csv.DictReader(f) 10 | for row in reader: 11 | print(row.pop()) 12 | print(row.pop()) 13 | # for value in row.items(): 14 | # print(value) 15 | -------------------------------------------------------------------------------- /freetoken.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request 3 | import uuid 4 | from mixin_api import MIXIN_API 5 | import mixin_config 6 | import json 7 | from flask import jsonify 8 | 9 | app = Flask(__name__) 10 | 11 | @app.route("/") 12 | def hello(): 13 | return "Hello World!" 14 | 15 | @app.route("/token", methods = ["POST"]) 16 | def freetoken(): 17 | if (request.is_json): 18 | content = request.get_json() 19 | print(content) 20 | if (('session_secret' in content) and ('full_name' in content)): 21 | print (content['session_secret']) 22 | mixinApiBotInstance = MIXIN_API(mixin_config) 23 | body_in_json = json.dumps(content) 24 | 25 | token = mixinApiBotInstance.genPOSTJwtToken("/users", body_in_json, str(uuid.uuid4()), 20).decode('utf8') 26 | return jsonify({"token":token}) 27 | return "OK" 28 | -------------------------------------------------------------------------------- /json-test.py: -------------------------------------------------------------------------------- 1 | import json 2 | body = { 3 | "session_secret": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLHhlK0GZCjE6o6/seNz8x0X7r+1zYtACrgJT60GHr5ol9SUFHrTt8qTPfDphxcVA9S8LN4MIowXfIabhP/5FJX3G3wdR4U+U18cFqEiYB+i7uF9ME9Q8RIk/orzeimID97F/sn0XVk8lCCaKUuL1FOHN3J67ox2RWkvMCrIJlrQIDAQAB", 4 | "full_name": "Tom Bot " 5 | } 6 | print(body) 7 | body_in_json = json.dumps(body) 8 | print(body_in_json) 9 | -------------------------------------------------------------------------------- /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(days = 365) 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 | return {"httpfailed":r.status_code} 273 | return (r.json()) 274 | """ 275 | ============ 276 | MESSENGER PRIVATE APIs 277 | ============ 278 | auth token need request 'https://api.mixin.one/me' to get. 279 | """ 280 | 281 | 282 | """ 283 | Read user's all assets. 284 | """ 285 | def getMyAssets(self, auth_token=""): 286 | 287 | assets_result = self.__genNetworkGetRequest('/assets', auth_token) 288 | return assets_result 289 | 290 | """ 291 | Read self profile. 292 | """ 293 | def getMyProfile(self, auth_token): 294 | return self.__genNetworkGetRequest('/me', auth_token) 295 | 296 | """ 297 | ? 298 | Update my preferences. 299 | """ 300 | def updateMyPerference(self,receive_message_source="EVERYBODY",accept_conversation_source="EVERYBODY"): 301 | 302 | body = { 303 | "receive_message_source": receive_message_source, 304 | "accept_conversation_source": accept_conversation_source 305 | } 306 | 307 | return self.__genPostRequest('/me/preferences', body) 308 | 309 | 310 | """ 311 | ? 312 | Update my profile. 313 | """ 314 | def updateMyProfile(self, full_name, auth_token, avatar_base64=""): 315 | 316 | body = { 317 | "full_name": full_name, 318 | "avatar_base64": avatar_base64 319 | } 320 | 321 | return self.__genPostRequest('/me', body, auth_token) 322 | 323 | """ 324 | Get users information by IDs. 325 | """ 326 | def getUsersInfo(self, user_ids, auth_token): 327 | return self.__genPostRequest('/users/fetch', user_ids, auth_token) 328 | 329 | """ 330 | Get user's information by ID. 331 | """ 332 | def getUserInfo(self, user_id, auth_token): 333 | return self.__genNetworkGetRequest('/users/' + user_id, None, auth_token) 334 | 335 | def getUserInfo_prove(self, user_id): 336 | path = '/users/' + user_id 337 | url = self.__genUrl(path) 338 | token = self.genGETJwtToken(path, "", str(uuid.uuid4())) 339 | auth_token = token.decode('utf8') 340 | r = requests.get(url, headers={"Authorization": "Bearer " + auth_token, 'Content-Type': 'application/json', 'Content-length': '0'}) 341 | finalCurlString = "curl -i --header " + "\"Authorization: Bearer " + auth_token + "\" --header \"Content-Type: application/json\" --header \"Content-length: 0\" " 342 | finalCurlString += " \"" 343 | finalCurlString += url 344 | finalCurlString += "\"" 345 | result_obj = r.json() 346 | return (result_obj, finalCurlString) 347 | 348 | 349 | """ 350 | Search user by Mixin ID or Phone Number. 351 | """ 352 | def SearchUser(self, q): 353 | return self.__genNetworkGetRequest('/search/' + q) 354 | 355 | """ 356 | Rotate user’s code_id. 357 | """ 358 | def rotateUserQR(self, auth_token): 359 | return self.__genGetRequest('/me/code', auth_token) 360 | 361 | """ 362 | Get my friends. 363 | """ 364 | def getMyFriends(self, auth_token): 365 | return self.__genGetRequest('/friends', auth_token) 366 | 367 | """ 368 | Create a GROUP or CONTACT conversation. 369 | """ 370 | def createConv(self, category, conversation_id, participants, action, role, user_id, auth_token): 371 | 372 | body = { 373 | "category": category, 374 | "conversation_id": conversation_id, 375 | "participants": participants, 376 | "action": action, 377 | "role": role, 378 | "user_id": user_id 379 | } 380 | 381 | return self.__genPostRequest('/conversations', body, auth_token) 382 | 383 | """ 384 | Read conversation by conversation_id. 385 | """ 386 | def getConv(self, conversation_id, auth_token): 387 | return self.__genGetRequest('/conversations/' + conversation_id, auth_token) 388 | 389 | 390 | """ 391 | ============ 392 | NETWORK PRIVATE APIs 393 | ============ 394 | auth token need robot related param to generate. 395 | """ 396 | 397 | """ 398 | PIN is used to manage user’s addresses, assets and etc. There’s no default PIN for a Mixin Network user (except APP). 399 | if auth_token is empty, it create robot' pin. 400 | if auth_token is set, it create messenger user pin. 401 | """ 402 | def updatePin(self, new_pin, old_pin, auth_token=""): 403 | if old_pin == "": 404 | newEncrypedPin = self.genEncrypedPin_withPin(new_pin) 405 | body = { 406 | "old_pin": "", 407 | "pin": newEncrypedPin.decode() 408 | } 409 | else: 410 | oldEncryptedPin = self.genEncrypedPin_withPin(old_pin) 411 | newEncrypedPin = self.genEncrypedPin_withPin(new_pin) 412 | 413 | body = { 414 | "old_pin": oldEncryptedPin.decode(), 415 | "pin": newEncrypedPin.decode() 416 | } 417 | return self.__genNetworkPostRequest('/pin/update', body, auth_token) 418 | 419 | """ 420 | Verify PIN if is valid or not. For example, you can verify PIN before updating it. 421 | if auth_token is empty, it verify robot' pin. 422 | if auth_token is set, it verify messenger user pin. 423 | """ 424 | def verifyPin(self, input_pin, auth_token=""): 425 | enPin = self.genEncrypedPin_withPin(input_pin) 426 | body = { 427 | "pin": enPin.decode() 428 | } 429 | 430 | return self.__genNetworkPostRequest('/pin/verify', body, auth_token) 431 | 432 | """ 433 | Grant an asset's deposit address, usually it is public_key, but account_name and account_tag is used for EOS. 434 | """ 435 | def deposit(self, asset_id): 436 | return self.__genNetworkGetRequest(' /assets/' + asset_id) 437 | """ 438 | Read an asset's withdraw address, usually it is public_key, but account_name and account_tag is used for EOS. 439 | """ 440 | def withdrawals_address(self, asset_id): 441 | return self.__genNetworkGetRequest('/assets/' + asset_id + '/addresses') 442 | 443 | 444 | 445 | """ 446 | withdrawals robot asset to address_id 447 | Tips:Get assets out of Mixin Network, neet to create an address for withdrawal. 448 | """ 449 | def withdrawals(self, address_id, amount, memo, trace_id, asset_pin): 450 | encrypted_pin = self.genEncrypedPin_withPin(asset_pin).decode() 451 | 452 | body = { 453 | "address_id": address_id, 454 | "pin": encrypted_pin, 455 | "amount": amount, 456 | "trace_id": trace_id, 457 | "memo": memo 458 | } 459 | if trace_id == "": 460 | body['trace_id'] = str(uuid.uuid1()) 461 | 462 | 463 | return self.__genNetworkPostRequest('/withdrawals', body) 464 | 465 | 466 | """ 467 | Create an address for withdrawal, you can only withdraw through an existent address. 468 | """ 469 | def createAddress(self, asset_id, public_key = "", label = "", asset_pin = "", account_name = "", account_tag = ""): 470 | 471 | if (asset_pin == ""): 472 | encrypted_pin = self.genEncrypedPin().decode() 473 | else: 474 | encrypted_pin = self.genEncrypedPin_withPin(asset_pin).decode() 475 | body = { 476 | "asset_id": asset_id, 477 | "pin": encrypted_pin, 478 | "public_key": public_key, 479 | "label": label, 480 | "account_name": account_name, 481 | "account_tag": account_tag, 482 | } 483 | return self.__genNetworkPostRequest('/addresses', body) 484 | 485 | def createAddressEOS(self, asset_id, account_name, account_tag, label = ""): 486 | 487 | body = { 488 | "asset_id": asset_id, 489 | "pin": self.genEncrypedPin().decode(), 490 | "account_name": account_name, 491 | "account_tag": account_tag, 492 | "label": label, 493 | } 494 | return self.__genNetworkPostRequest('/addresses', body) 495 | """ 496 | Delete an address by ID. 497 | """ 498 | def delAddress(self, address_id, asset_pin = ""): 499 | 500 | if(asset_pin == ""): 501 | encrypted_pin = self.genEncrypedPin().decode() 502 | else: 503 | encrypted_pin = self.genEncrypedPin_withPin(asset_pin).decode() 504 | 505 | body = {"pin": encrypted_pin} 506 | 507 | return self.__genNetworkPostRequest('/addresses/' + address_id + '/delete', body) 508 | 509 | 510 | """ 511 | Read an address by ID. 512 | """ 513 | def getAddress(self, address_id): 514 | return self.__genNetworkGetRequest('/addresses/' + address_id) 515 | 516 | """ 517 | Transfer of assets between Mixin Network users. 518 | """ 519 | def transferTo(self, to_user_id, to_asset_id, to_asset_amount, memo, trace_uuid="", input_pin = "", input_encrypted_pin = ""): 520 | 521 | if input_encrypted_pin == "": 522 | # generate encrypted pin 523 | if (input_pin == ""): 524 | encrypted_pin = self.genEncrypedPin() 525 | else: 526 | encrypted_pin = self.genEncrypedPin_withPin(input_pin) 527 | 528 | else: 529 | encrypted_pin = input_encrypted_pin 530 | body = {'asset_id': to_asset_id, 'opponent_id': to_user_id, 'amount': str(to_asset_amount), 531 | 'pin': encrypted_pin.decode('utf8'), 'trace_id': trace_uuid, 'memo': memo} 532 | if trace_uuid == "": 533 | body['trace_id'] = str(uuid.uuid1()) 534 | 535 | return self.__genNetworkPostRequest('/transfers', body) 536 | 537 | def transferTo_MainNet(self, to_account_key, to_asset_id, to_asset_amount, memo, trace_uuid="", input_pin = "", input_encrypted_pin = ""): 538 | 539 | if input_encrypted_pin == "": 540 | # generate encrypted pin 541 | if (input_pin == ""): 542 | encrypted_pin = self.genEncrypedPin() 543 | else: 544 | encrypted_pin = self.genEncrypedPin_withPin(input_pin) 545 | 546 | else: 547 | encrypted_pin = input_encrypted_pin 548 | body = {'asset_id': to_asset_id, 'opponent_key': to_account_key, 'amount': str(to_asset_amount), 549 | 'pin': encrypted_pin.decode('utf8'), 'trace_id': trace_uuid, 'memo': memo} 550 | if trace_uuid == "": 551 | body['trace_id'] = str(uuid.uuid1()) 552 | 553 | return self.__genNetworkPostRequest('/transactions', body) 554 | 555 | """ 556 | Read transfer by trace ID. 557 | """ 558 | def getTransfer(self, trace_id): 559 | return self.__genNetworkGetRequest('/transfers/trace/' + trace_id) 560 | 561 | """ 562 | Verify a transfer, payment status if it is 'paid' or 'pending'. 563 | """ 564 | def verifyPayment(self, asset_id, opponent_id, amount, trace_id): 565 | 566 | body = { 567 | "asset_id": asset_id, 568 | "opponent_id": opponent_id, 569 | "amount": amount, 570 | "trace_id": trace_id 571 | } 572 | 573 | return self.__genNetworkPostRequest('/payments', body) 574 | 575 | """ 576 | Read asset by asset ID. 577 | """ 578 | def getAsset(self, asset_id): 579 | return self.__genNetworkGetRequest('/assets/' + asset_id) 580 | 581 | """ 582 | Read external transactions (pending deposits) by public_key and asset_id, use account_tag for EOS. 583 | """ 584 | def extTrans(self, asset_id, public_key, account_tag, account_name, limit, offset): 585 | 586 | body = { 587 | "asset": asset_id, 588 | "public_key": public_key, 589 | "account_tag": account_tag, 590 | "account_name": account_name, 591 | "limit": limit, 592 | "offset": offset 593 | } 594 | 595 | return self.__genNetworkGetRequest('/external/transactions', body) 596 | def fetchTokenForCreateUser(self, body, url): 597 | body_in_json = json.dumps(body) 598 | headers = { 599 | 'Content-Type' : 'application/json', 600 | } 601 | r = requests.post(url, json=body, headers=headers) 602 | result_obj = r.json() 603 | print(result_obj) 604 | return result_obj.get("token") 605 | 606 | 607 | 608 | """ 609 | 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. 610 | """ 611 | def createUser(self, session_secret, full_name, auth_token = ""): 612 | 613 | body = { 614 | "session_secret": session_secret, 615 | "full_name": full_name 616 | } 617 | 618 | return self.__genNetworkPostRequest('/users', body, auth_token) 619 | 620 | 621 | """ 622 | =========== 623 | NETWORK PUBLIC APIs 624 | =========== 625 | """ 626 | 627 | """ 628 | Read top valuable assets of Mixin Network. 629 | """ 630 | def topAssets(self): 631 | return self.__genGetRequest('/network') 632 | 633 | """ 634 | Read public snapshots of Mixin Network. 635 | """ 636 | def 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 | finalURL = "/network/snapshots?offset=%s&asset=%s&order=%s&limit=%d" % (offset, asset_id, order, limit) 645 | 646 | 647 | return self.__genNetworkGetRequest_snapshots(finalURL) 648 | def snapshots_after(self, offset, asset_id, limit=100): 649 | return self.snapshots(offset, asset_id, "ASC", limit) 650 | def snapshots_before(self, offset, asset_id, limit=100): 651 | return self.snapshots(offset, asset_id, "DESC", limit) 652 | 653 | 654 | """ 655 | Read public snapshots of Mixin Network by ID. 656 | """ 657 | def snapshot(self, snapshot_id): 658 | return self.__genGetRequest('/network/snapshots/' + snapshot_id) 659 | """ 660 | """ 661 | 662 | def account_snapshot(self, snapshot_id): 663 | return self.__genNetworkGetRequest_snapshots('/network/snapshots/' + snapshot_id) 664 | def account_snapshot_prove(self, snapshot_id): 665 | path = '/network/snapshots/' + snapshot_id 666 | url = self.__genUrl(path) 667 | token = self.genGETJwtToken(path, "", str(uuid.uuid4())) 668 | auth_token = token.decode('utf8') 669 | r = requests.get(url, headers={"Authorization": "Bearer " + auth_token, 'Content-Type': 'application/json', 'Content-length': '0'}) 670 | finalCurlString = "curl -i --header " + "\"Authorization: Bearer " + auth_token + "\" --header \"Content-Type: application/json\" --header \"Content-length: 0\" " 671 | finalCurlString += " \"" 672 | finalCurlString += url 673 | finalCurlString += "\"" 674 | result_obj = r.json() 675 | return (result_obj, finalCurlString) 676 | 677 | 678 | 679 | """ 680 | Read this account snapshots of Mixin Network. Beaer token is required 681 | """ 682 | def account_snapshots(self, offset, asset_id, order='DESC',limit=100): 683 | # TODO: SET offset default(UTC TIME) 684 | body = { 685 | "limit":limit, 686 | "offset":offset, 687 | "asset":asset_id, 688 | "order":order 689 | } 690 | 691 | 692 | result_json = self.__genNetworkGetRequest_snapshots("/network/snapshots", body) 693 | return result_json 694 | def account_snapshots_before(self, offset, asset_id, limit=100): 695 | return self.account_snapshots(offset, asset_id, order = "DESC", limit = limit) 696 | def account_snapshots_after(self, offset, asset_id, limit=100): 697 | return self.account_snapshots(offset, asset_id, order = "ASC", limit = limit) 698 | 699 | 700 | -------------------------------------------------------------------------------- /mixin_config-example.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= 'ed882a39-0b0c-4413-bbd9-221cdeee56bf' 10 | client_secret = '8d7ec7b9c8261b6c7bd6309210496ca4b72bce9efc7e49be14a428ce49ff7202' 11 | 12 | 13 | pay_pin = '599509' 14 | pay_session_id = 'bd53b6a4-e79a-49e5-ad04-36da518354f6' 15 | pin_token = "nVREh0/Ys9vzNFCQT2+PKcDN2OYAUSH8CidwHqDQLOCvokE7o6wtvLypjW9iks/RsnBM6N4SPF/P3bBW254YHGuDZXhitDEWOGkXs7v8BxMQxf+9454qTkMSpR9xbjAzgMXnSyHrNVoBtsc/Y+NvemB3VxPfsCOFHasiMqAa5DU=" 16 | 17 | 18 | private_key = """-----BEGIN RSA PRIVATE KEY----- 19 | MIICXQIBAAKBgQCnaoO1SdPxggEpAIUdM/8Ll4FOqlXK7vwURHr4FFi6hnQ1I79g 20 | pZSlJdzjr24WcIuNi6kVdXVIpyzZJGXS2I72dpGs5h1jKxL8AWIUVL2axZXqTJNi 21 | c4wj6GJ4gDRP2U9I9gae+S/frM6KP8TioV0OcbmrlfrwI0OElLH3363y1wIDAQAB 22 | AoGAduaGLi4F8cMkMculvqzcGY57jrQZBGyg6YANWb2Rmr+9LrR5yhkvLe9rJuXE 23 | KPm7k0a6SnxGVNguWPWpv4qAVVGAJ0eb8ETXTRO20HlKmcbxfFdDtHBDV3QufNa1 24 | h3mNEsqWDNCDdAm7p/EZwfG2F9+nmeXLfip7R1I72qbK0wkCQQDiJR6NEGVwbj8H 25 | K8kRpzY1D9lPqp1ZMrma5AFYGZIb5voTxLjRpYdxQJHi7CCdE1zgqJOXvA3jj/io 26 | f7bMIJY7AkEAvYSSC5H+fUKAjyjeCTGJBBKoPDsq+aALAYLWf77sGXE9BBmhhY0l 27 | iwmbj8X6/qZtQ0yEzdT/OSdiYL86CcrgFQJBALz/sMzMSzrvqJVhrqWmTdOC72d5 28 | fA+0KRKeQ9FRbZ8MJyymWKA96zhncoVoOsmMCS9pNBC4BhONm4+XTTrEcUkCQQCo 29 | DWB8Bg/G/yuExtZtDJHVHL41+rmW9UYNJvoR+TjfLrzOX/QMuyapbfGVwhdZrDaD 30 | UN0KsG9JPRVNeQR8HnwpAkACrr9cNp1H1bytHG9a6L+5cVHkRhqqEYWVO41MhgZF 31 | 5bIKx5OXCJB2VwY7fjFet2KxTHGfEZt/khjFNZzVX7lN 32 | -----END RSA PRIVATE KEY-----""" 33 | -------------------------------------------------------------------------------- /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= 'd3a3d30c-b3db-4f55-9c31-3dee5137b45f' 10 | client_secret = '' 11 | 12 | 13 | pay_pin = '' 14 | pay_session_id = '899a994a-47fa-4a62-bbd8-d07e6600bbf8' 15 | pin_token = "" 16 | 17 | 18 | private_key = """-----BEGIN RSA PRIVATE KEY----- 19 | MIICXgIBAAKBgQCEUUi1OWGwtWmZy9+/6fEFNGvX6kkODgQ9R1LUvbkrWbmyEK7E 20 | ednsZimyfrNwohyRLkBYcCbUqBj1lXLpp8CZeeSftHLWETsnebXmOI3xjuNh7lan 21 | rjrfIhXU2SumRwNKYv5N2SCHVopOdzqA/Py1F+MZ4iGxGsEhQt6pj7BtQwIDAQAB 22 | AoGBAIC4Pn4mG1X3tYprXfjp0ZGZpIwAnB8zHYarevYAt0Fn4RFLz2jNjWW/ITNG 23 | tLOO0TGfB/PA7tWTzewi/g42JlMAtZf+d2msB+cfHfmrCcSoTd8tnjU6Z3m0FcUd 24 | v6OFxtn0MOW23AhIM6/GSqtUmj9v1yNjEZydJWnepfWXW32BAkEAxoypwmd/ljO8 25 | gK1KSMlyK9atWV/JT7yRjQFxoHsyjRWY7RGJ63bKqSTGeQf7g3NT/ylcsL34pkD+ 26 | MSNX7mhkQQJBAKqaj5TVdoiUG5rZQ8ZpWIvCZR6nZJvKCVrb3tQb9WGGSlqQkZvU 27 | rkwezv8yBV8Lf0q04i+K3yXugOBJgJB9IIMCQQC/QRXxLrNG4zUsGkDZFjsFIXjA 28 | A7WuOi1WT1K9zI5oOqqhieh5yWb0QXlxcpADEl+SBazEj2np8+cnPC1ieGpBAkBT 29 | vcRmsRUa22BldOoQtVsSVxxdnJsE4XoQEEbjZMng0hMjOz1N+WqI0MKYf0wlqDYW 30 | TI2N+qOoMdcuwMWLpu/TAkEAxhFzoXoP+Jpqb1C5LXLBvpWK6qArTGo/KXm0Jqcb 31 | HKOiMsKdRFwg1yeocdpbeLyVUDV5arvjcezWMIiM5i4BHA== 32 | -----END RSA PRIVATE KEY-----""" 33 | -------------------------------------------------------------------------------- /mixin_ws_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mixin Python3 Websocket SDK 3 | base on https://github.com/myrual/mixin_client_demo/blob/master/home_of_cnb_robot.py 4 | code by Lee.c 5 | update at 2018.12.2 6 | """ 7 | import json 8 | import uuid 9 | import gzip 10 | import time 11 | from io import BytesIO 12 | import base64 13 | import websocket 14 | import mixin_config 15 | from mixin_api import MIXIN_API 16 | 17 | try: 18 | import thread 19 | except ImportError: 20 | import _thread as thread 21 | 22 | 23 | class MIXIN_WS_API: 24 | 25 | def __init__(self, on_message, on_open=None, on_error=None, on_close=None, on_data=None): 26 | 27 | mixin_api = MIXIN_API(mixin_config) 28 | encoded = mixin_api.genGETJwtToken('/', "", str(uuid.uuid4())) 29 | 30 | if on_open is None: 31 | on_open = MIXIN_WS_API.__on_open 32 | 33 | if on_close is None: 34 | on_close = MIXIN_WS_API.__on_close 35 | 36 | if on_error is None: 37 | on_error = MIXIN_WS_API.__on_error 38 | 39 | if on_data is None: 40 | on_data = MIXIN_WS_API.__on_data 41 | 42 | self.ws = websocket.WebSocketApp("wss://blaze.mixin.one/", 43 | on_message=on_message, 44 | on_error=on_error, 45 | on_close=on_close, 46 | header=["Authorization:Bearer " + encoded.decode()], 47 | subprotocols=["Mixin-Blaze-1"], 48 | on_data=on_data) 49 | 50 | self.ws.on_open = on_open 51 | 52 | """ 53 | run websocket server forever 54 | """ 55 | def run(self): 56 | self.ws.run_forever() 57 | 58 | """ 59 | ======================== 60 | WEBSOCKET DEFAULT METHOD 61 | ======================== 62 | """ 63 | 64 | """ 65 | on_open default 66 | """ 67 | @staticmethod 68 | def __on_open(ws): 69 | 70 | def run(*args): 71 | print("ws open") 72 | Message = {"id": str(uuid.uuid1()), "action": "LIST_PENDING_MESSAGES"} 73 | Message_instring = json.dumps(Message) 74 | 75 | fgz = BytesIO() 76 | gzip_obj = gzip.GzipFile(mode='wb', fileobj=fgz) 77 | gzip_obj.write(Message_instring.encode()) 78 | gzip_obj.close() 79 | ws.send(fgz.getvalue(), opcode=websocket.ABNF.OPCODE_BINARY) 80 | while True: 81 | time.sleep(1) 82 | 83 | thread.start_new_thread(run, ()) 84 | 85 | """ 86 | on_data default 87 | """ 88 | @staticmethod 89 | def __on_data(ws, readableString, dataType, continueFlag): 90 | return 91 | 92 | """ 93 | on_close default 94 | """ 95 | 96 | @staticmethod 97 | def __on_close(ws): 98 | return 99 | 100 | """ 101 | on_error default 102 | """ 103 | 104 | @staticmethod 105 | def __on_error(error): 106 | print(error) 107 | 108 | 109 | """ 110 | ================= 111 | REPLY USER METHOD 112 | ================= 113 | """ 114 | 115 | """ 116 | generate a standard message base on Mixin Messenger format 117 | """ 118 | 119 | @staticmethod 120 | def writeMessage(websocketInstance, action, params): 121 | 122 | message = {"id": str(uuid.uuid1()), "action": action, "params": params} 123 | message_instring = json.dumps(message) 124 | 125 | fgz = BytesIO() 126 | gzip_obj = gzip.GzipFile(mode='wb', fileobj=fgz) 127 | gzip_obj.write(message_instring.encode()) 128 | gzip_obj.close() 129 | websocketInstance.send(fgz.getvalue(), opcode=websocket.ABNF.OPCODE_BINARY) 130 | 131 | """ 132 | when receive a message, must reply to server 133 | ACKNOWLEDGE_MESSAGE_RECEIPT ack server received message 134 | """ 135 | @staticmethod 136 | def replayMessage(websocketInstance, msgid): 137 | parameter4IncomingMsg = {"message_id": msgid, "status": "READ"} 138 | Message = {"id": str(uuid.uuid1()), "action": "ACKNOWLEDGE_MESSAGE_RECEIPT", "params": parameter4IncomingMsg} 139 | Message_instring = json.dumps(Message) 140 | fgz = BytesIO() 141 | gzip_obj = gzip.GzipFile(mode='wb', fileobj=fgz) 142 | gzip_obj.write(Message_instring.encode()) 143 | gzip_obj.close() 144 | websocketInstance.send(fgz.getvalue(), opcode=websocket.ABNF.OPCODE_BINARY) 145 | return 146 | 147 | """ 148 | reply a button to user 149 | """ 150 | @staticmethod 151 | def sendUserAppButton(websocketInstance, in_conversation_id, to_user_id, realLink, text4Link, colorOfLink="#0084ff"): 152 | 153 | btn = '[{"label":"' + text4Link + '","action":"' + realLink + '","color":"' + colorOfLink + '"}]' 154 | 155 | btn = base64.b64encode(btn.encode('utf-8')).decode(encoding='utf-8') 156 | 157 | params = {"conversation_id": in_conversation_id, "recipient_id": to_user_id, "message_id": str(uuid.uuid4()), 158 | "category": "APP_BUTTON_GROUP", "data": btn} 159 | return MIXIN_WS_API.writeMessage(websocketInstance, "CREATE_MESSAGE", params) 160 | 161 | """ 162 | reply a contact card to user 163 | """ 164 | 165 | @staticmethod 166 | def sendUserContactCard(websocketInstance, in_conversation_id, to_user_id, to_share_userid): 167 | 168 | btnJson = json.dumps({"user_id": to_share_userid}) 169 | btnJson = base64.b64encode(btnJson.encode('utf-8')).decode('utf-8') 170 | params = {"conversation_id": in_conversation_id, "recipient_id": to_user_id, "message_id": str(uuid.uuid4()), 171 | "category": "PLAIN_CONTACT", "data": btnJson} 172 | return MIXIN_WS_API.writeMessage(websocketInstance, "CREATE_MESSAGE", params) 173 | 174 | """ 175 | reply a text to user 176 | """ 177 | @staticmethod 178 | def sendUserText(websocketInstance, in_conversation_id, to_user_id, textContent): 179 | 180 | textContent = textContent.encode('utf-8') 181 | textContent = base64.b64encode(textContent).decode(encoding='utf-8') 182 | 183 | params = {"conversation_id": in_conversation_id, "recipient_id": to_user_id, "status": "SENT", 184 | "message_id": str(uuid.uuid4()), "category": "PLAIN_TEXT", 185 | "data": textContent} 186 | return MIXIN_WS_API.writeMessage(websocketInstance, "CREATE_MESSAGE", params) 187 | 188 | """ 189 | send user a pay button 190 | """ 191 | @staticmethod 192 | def sendUserPayAppButton(webSocketInstance, in_conversation_id, to_user_id, inAssetName, inAssetID, inPayAmount, linkColor="#0CAAF5"): 193 | payLink = "https://mixin.one/pay?recipient=" + mixin_config.client_id + "&asset=" + inAssetID + "&amount=" + str( 194 | inPayAmount) + '&trace=' + str(uuid.uuid1()) + '&memo=PRS2CNB' 195 | btn = '[{"label":"' + inAssetName + '","action":"' + payLink + '","color":"' + linkColor + '"}]' 196 | 197 | btn = base64.b64encode(btn.encode('utf-8')).decode(encoding='utf-8') 198 | 199 | gameEntranceParams = {"conversation_id": in_conversation_id, "recipient_id": to_user_id, 200 | "message_id": str(uuid.uuid4()), "category": "APP_BUTTON_GROUP", "data": btn} 201 | return MIXIN_WS_API.writeMessage(webSocketInstance, "CREATE_MESSAGE", gameEntranceParams) 202 | 203 | 204 | @staticmethod 205 | def sendAppCard(websocketInstance, in_conversation_id, to_user_id, asset_id, amount, icon_url, title, description, color="#0080FF", memo=""): 206 | payLink = "https://mixin.one/pay?recipient=" + to_user_id + "&asset=" + asset_id + "&amount=" + \ 207 | amount + "&trace=" + str(uuid.uuid4()) + "&memo=" 208 | card = '{"icon_url":"' + icon_url + '","title":"' + title + \ 209 | '","description":"' + description + '","action":"'+ payLink + '"}' 210 | enCard = base64.b64encode(card.encode('utf-8')).decode(encoding='utf-8') 211 | params = {"conversation_id": in_conversation_id, "message_id": str(uuid.uuid4()), 212 | "category": "APP_CARD", "status": "SENT", "data": enCard} 213 | return MIXIN_WS_API.writeMessage(websocketInstance, "CREATE_MESSAGE", params) 214 | 215 | @staticmethod 216 | def sendAppButtonGroup(websocketInstance, in_conversation_id, to_user_id, buttons): 217 | buttonsStr = '[' + ','.join(str(btn) for btn in buttons) +']' 218 | enButtons = base64.b64encode(buttonsStr.encode('utf-8')).decode(encoding='utf-8') 219 | params = {"conversation_id": in_conversation_id, "recipient_id": to_user_id, 220 | "message_id": str(uuid.uuid4()), 221 | "category": "APP_BUTTON_GROUP", "status": "SENT", "data": enButtons} 222 | return MIXIN_WS_API.writeMessage(websocketInstance, "CREATE_MESSAGE", params) 223 | 224 | @staticmethod 225 | def packButton(to_user_id, asset_id, amount, label, color="#FF8000", memo=""): 226 | payLink = "https://mixin.one/pay?recipient=" + to_user_id + "&asset=" + asset_id + "&amount=" + \ 227 | amount + "&trace=" + str(uuid.uuid4()) + "&memo=" 228 | button = '{"label":"' + label + '","color":"' + color + '","action":"' + payLink + '"}' 229 | return button 230 | -------------------------------------------------------------------------------- /objapi.py: -------------------------------------------------------------------------------- 1 | import mixin_config 2 | class Shark: 3 | def __init__(self, name): 4 | self.name = name 5 | 6 | def swim(self): 7 | print(self.name + " is swimming.") 8 | 9 | def be_awesome(self): 10 | print(self.name + " is being awesome.") 11 | 12 | def main(): 13 | sammy = Shark("Sammy") 14 | sammy.be_awesome() 15 | stevie = Shark("Stevie") 16 | stevie.swim() 17 | 18 | if __name__ == "__main__": 19 | main() 20 | print(type(mixin_config)) 21 | mixin_config.pay_pin = "111111" 22 | print(mixin_config.pay_pin) 23 | -------------------------------------------------------------------------------- /pin-test.py: -------------------------------------------------------------------------------- 1 | # import json 2 | # body = { 3 | # "session_secret": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLHhlK0GZCjE6o6/seNz8x0X7r+1zYtACrgJT60GHr5ol9SUFHrTt8qTPfDphxcVA9S8LN4MIowXfIabhP/5FJX3G3wdR4U+U18cFqEiYB+i7uF9ME9Q8RIk/orzeimID97F/sn0XVk8lCCaKUuL1FOHN3J67ox2RWkvMCrIJlrQIDAQAB", 4 | # "full_name": "Tom Bot " 5 | # } 6 | # print(body) 7 | # body_in_json = json.dumps(body) 8 | # print(body_in_json) 9 | from Crypto.PublicKey import RSA 10 | from mixin_api import MIXIN_API 11 | # from random_word import RandomWords 12 | import mixin_config 13 | import json 14 | 15 | PIN = '832047'; 16 | 17 | mixinApiBotInstance = MIXIN_API(mixin_config) 18 | print("will verify Pin",mixin_config.pay_pin) 19 | pinInfo = mixinApiBotInstance.verifyPin(mixin_config.pay_pin) 20 | print(pinInfo) 21 | # pinInfo = mixinApiBotInstance.updatePin(PIN,mixin_config.pay_pin) 22 | -------------------------------------------------------------------------------- /pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /usr/local/bin 2 | include-system-site-packages = false 3 | version = 3.7.2 4 | -------------------------------------------------------------------------------- /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==5.1 7 | requests==2.21.0 8 | websocket-client==0.54.0 9 | u-msgpack-python 10 | -------------------------------------------------------------------------------- /requirements2.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==5.1 7 | requests==2.21.0 8 | websocket-client==0.54.0 9 | -------------------------------------------------------------------------------- /ws-test2.py: -------------------------------------------------------------------------------- 1 | import websocket 2 | try: 3 | import thread 4 | except ImportError: 5 | import _thread as thread 6 | import time 7 | 8 | def on_message(ws, message): 9 | print(message) 10 | 11 | def on_error(ws, error): 12 | print(error) 13 | 14 | def on_close(ws): 15 | print("### closed ###") 16 | 17 | def on_open(ws): 18 | def run(*args): 19 | print("run...") 20 | for i in range(3): 21 | time.sleep(1) 22 | ws.send("Hello %d" % i) 23 | time.sleep(1) 24 | ws.close() 25 | print("thread terminating...") 26 | print("before thread...") 27 | thread.start_new_thread(run, ()) 28 | 29 | 30 | if __name__ == "__main__": 31 | websocket.enableTrace(True) 32 | ws = websocket.WebSocketApp("wss://blaze.mixin.one/", 33 | on_message = on_message, 34 | on_error = on_error, 35 | on_close = on_close) 36 | ws.on_open = on_open 37 | print("before run_forever") 38 | ws.run_forever() 39 | -------------------------------------------------------------------------------- /ws_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test Mixin Messenger Robot Websocket methods 3 | code by Lee.c 4 | update at 2018.12.2 5 | """ 6 | 7 | from mixin_ws_api import MIXIN_WS_API 8 | from mixin_api import MIXIN_API 9 | import mixin_config 10 | 11 | import json 12 | import time 13 | from io import BytesIO 14 | import base64 15 | import gzip 16 | 17 | try: 18 | import thread 19 | except ImportError: 20 | import _thread as thread 21 | 22 | 23 | def on_message(ws, message): 24 | inbuffer = BytesIO(message) 25 | 26 | f = gzip.GzipFile(mode="rb", fileobj=inbuffer) 27 | rdata_injson = f.read() 28 | rdata_obj = json.loads(rdata_injson) 29 | action = rdata_obj["action"] 30 | 31 | if action not in ["ACKNOWLEDGE_MESSAGE_RECEIPT", "CREATE_MESSAGE", "LIST_PENDING_MESSAGES"]: 32 | print("unknow action",action) 33 | return 34 | 35 | if action == "ACKNOWLEDGE_MESSAGE_RECEIPT": 36 | return 37 | 38 | 39 | if action == "CREATE_MESSAGE": 40 | 41 | data = rdata_obj["data"] 42 | msgid = data["message_id"] 43 | typeindata = data["type"] 44 | categoryindata = data["category"] 45 | userId = data["user_id"] 46 | conversationId = data["conversation_id"] 47 | dataindata = data["data"] 48 | created_at = data["created_at"] 49 | updated_at = data["updated_at"] 50 | 51 | realData = base64.b64decode(dataindata) 52 | 53 | MIXIN_WS_API.replayMessage(ws, msgid) 54 | 55 | print('userId', userId) 56 | print("created_at",created_at) 57 | 58 | 59 | if 'error' in rdata_obj: 60 | return 61 | 62 | if categoryindata not in ["SYSTEM_ACCOUNT_SNAPSHOT", "PLAIN_TEXT", "SYSTEM_CONVERSATION", "PLAIN_STICKER", "PLAIN_IMAGE", "PLAIN_CONTACT"]: 63 | print("unknow category",categoryindata) 64 | return 65 | 66 | if categoryindata == "PLAIN_TEXT" and typeindata == "message": 67 | realData = realData.decode('utf-8') 68 | print("dataindata",realData) 69 | 70 | if 'hi' == realData: 71 | introductionContent = 'welcome to MyFirstRobot\n[hihi] reply n times text\n[c] send a contact card\n[b] send a link button\n[p] you need to pay\n[t] transfer to you' 72 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, introductionContent) 73 | return 74 | 75 | if 'hihi' == realData: 76 | introductionContent = '你好呀 ' 77 | for i in range(3): 78 | MIXIN_WS_API.sendUserText(ws, conversationId, userId, introductionContent + str(i)) 79 | time.sleep(1) 80 | return 81 | 82 | if 'c' == realData: 83 | print('send a contact card') 84 | MIXIN_WS_API.sendUserContactCard(ws, conversationId, userId, "d33f7efd-4b0b-41ff-baa3-b22ea40eb44f") 85 | return 86 | 87 | if 'b' == realData: 88 | print('send a link button') 89 | MIXIN_WS_API.sendUserAppButton(ws, conversationId, userId, "https://github.com/includeleec/mixin-python3-sdk", "点我了解 Mixin Python3 SDK") 90 | return 91 | 92 | if 'p' == realData: 93 | print('you need to pay') 94 | CNB_ASSET_ID = "965e5c6e-434c-3fa9-b780-c50f43cd955c" 95 | MIXIN_WS_API.sendUserPayAppButton(ws, conversationId, userId, "给点钱吧", CNB_ASSET_ID, 1, "#ff0033") 96 | return 97 | 98 | if 't' == realData: 99 | print('transfer to you') 100 | CNB_ASSET_ID = "965e5c6e-434c-3fa9-b780-c50f43cd955c" 101 | mixin_api.transferTo(userId, CNB_ASSET_ID, 2, "滴水之恩") 102 | return 103 | 104 | elif categoryindata == "PLAIN_TEXT": 105 | print("PLAIN_TEXT but unkonw:") 106 | 107 | 108 | 109 | if __name__ == "__main__": 110 | 111 | mixin_api = MIXIN_API(mixin_config) 112 | 113 | mixin_ws = MIXIN_WS_API(on_message=on_message) 114 | 115 | mixin_ws.run() 116 | --------------------------------------------------------------------------------