diff --git a/mcs/__init__.py b/mcs/__init__.py index f337bb0..c92e0cd 100644 --- a/mcs/__init__.py +++ b/mcs/__init__.py @@ -1,4 +1,3 @@ from mcs.api_client import APIClient from mcs.api import OnchainAPI -from mcs.api import BucketAPI -from mcs.contract import ContractClient \ No newline at end of file +from mcs.api import BucketAPI \ No newline at end of file diff --git a/mcs/api/bucket_api.py b/mcs/api/bucket_api.py index e6843c9..2b0fc2c 100644 --- a/mcs/api/bucket_api.py +++ b/mcs/api/bucket_api.py @@ -1,9 +1,13 @@ +from aiohttp import request from mcs.api_client import APIClient from mcs.common.constants import * from hashlib import md5 from queue import Queue import os, threading import urllib.request +import logging +import glob +import shutil from mcs.common.utils import object_to_filename from mcs.object.bucket_storage import Bucket, File @@ -16,41 +20,47 @@ def __init__(self, api_client=None): self.api_client = api_client self.MCS_API = api_client.MCS_API self.token = self.api_client.token + self.gateway = self.get_gateway() def list_buckets(self): - result = self.api_client._request_without_params(GET, BUCKET_LIST, self.MCS_API, self.token) - bucket_info_list = [] - if result['status'] != 'success': - print("\033[31mError: " + result['message'] + "\033[0m" ) + try: + result = self.api_client._request_without_params(GET, BUCKET_LIST, self.MCS_API, self.token) + bucket_info_list = [] + data = result['data'] + for bucket in data: + bucket_info: Bucket = Bucket(bucket) + bucket_info_list.append(bucket_info) + return bucket_info_list + except: + logging.error("\033[31m" + 'error' + "\033[0m") return - data = result['data'] - for bucket in data: - bucket_info: Bucket = Bucket(bucket) - bucket_info_list.append(bucket_info) - - # print(bucket_info_list) - return bucket_info_list def create_bucket(self, bucket_name): params = {'bucket_name': bucket_name} - result = self.api_client._request_with_params(POST, CREATE_BUCKET, self.MCS_API, params, self.token, None) - if result is None: - print("\033[31mError: This bucket already exists\033[0m") - return False - if result['status'] == 'success': - print("\033[32mBucket created successfully\033[0m") - return True + try: + result = self.api_client._request_with_params(POST, CREATE_BUCKET, self.MCS_API, params, self.token, None) + if result['status'] == 'success': + logging.info("\033[32mBucket created successfully\033[0m") + return True + else: + logging.error("\033[31m" + result['message'] + "\033[0m") + except: + logging.error("\033[31mThis bucket already exists\033[0m") + + return False def delete_bucket(self, bucket_name): - bucket_id = self._get_bucket_id(bucket_name) - params = {'bucket_uid': bucket_id} - result = self.api_client._request_with_params(GET, DELETE_BUCKET, self.MCS_API, params, self.token, None) - if result is None: - print("\033[31mError: Can't find this bucket\033[0m") - return False - if result['status'] == 'success': - print("\033[32mBucket delete successfully\033[0m") - return True + try: + bucket_id = self._get_bucket_id(bucket_name) + params = {'bucket_uid': bucket_id} + result = self.api_client._request_with_params(GET, DELETE_BUCKET, self.MCS_API, params, self.token, None) + if result['status'] == 'success': + logging.info("\033[32mBucket delete successfully\033[0m") + return True + except: + if result is None: + logging.error("\033[31mCan't find this bucket\033[0m") + return False def get_bucket(self, bucket_name='', bucket_id=''): bucketlist = self.list_buckets() @@ -66,48 +76,61 @@ def get_bucket(self, bucket_name='', bucket_id=''): for bucket in bucketlist: if bucket.bucket_uid == bucket_id: return bucket - print("\033[31mError: User does not have this bucket\033[0m") + logging.error("\033[31mUser does not have this bucket\033[0m") return None # object name def get_file(self, bucket_name, object_name): - prefix, file_name = object_to_filename(object_name) - file_list = self._get_full_file_list(bucket_name, prefix) - for file in file_list: - if file.name == file_name: - return file - print("\033[31mError: Can't find this object\033[0m") - return None + try: + bucket_id = self._get_bucket_id(bucket_name) + params = {"bucket_uid": bucket_id, "object_name": object_name} + + result = self.api_client._request_with_params(GET, GET_FILE, self.MCS_API, params, self.token, None) + + if result: + return File(result['data'], self.gateway) + except: + print('error') + return def create_folder(self, bucket_name, folder_name, prefix=''): - bucket_id = self._get_bucket_id(bucket_name) - params = {"file_name": folder_name, "prefix": prefix, "bucket_uid": bucket_id} - result = self.api_client._request_with_params(POST, CREATE_FOLDER, self.MCS_API, params, self.token, None) - if result['status'] == 'success': - print("\033[32mFolder created successfully\033[0m") - return True - else: - print("\033[31mError: " + result['message']+ "\033[0m") - return False + try: + bucket_id = self._get_bucket_id(bucket_name) + params = {"file_name": folder_name, "prefix": prefix, "bucket_uid": bucket_id} + result = self.api_client._request_with_params(POST, CREATE_FOLDER, self.MCS_API, params, self.token, None) + if result['status'] == 'success': + logging.info("\033[31mFolder created successfully\033[0m") + return True + else: + logging.error("\033[31m" + result['message'] + "\033[0m") + return False + except: + logging.error("\033[31mCan't create this folder") + return def delete_file(self, bucket_name, object_name): - prefix, file_name = object_to_filename(object_name) - file_list = self._get_full_file_list(bucket_name, prefix) - file_id = '' - for file in file_list: - if file.name == file_name: - file_id = file.id - params = {'file_id': file_id} - if file_id == '': - print("\033[31mError: Can't find the file\033[0m") - return False - result = self.api_client._request_with_params(GET, DELETE_FILE, self.MCS_API, params, self.token, None) - if result['status'] == 'success': - print("\033[32mFile delete successfully\033[0m") - return True - else: - print("\033[31mError: Can't delete the file\033[0m") - return False + try: + prefix, file_name = object_to_filename(object_name) + file_list = self._get_full_file_list(bucket_name, prefix) + + file_id = '' + for file in file_list: + if file.name == file_name: + file_id = file.id + params = {'file_id': file_id} + if file_id == '': + logging.error("\033[31mCan't find the file\033[0m") + return False + result = self.api_client._request_with_params(GET, DELETE_FILE, self.MCS_API, params, self.token, None) + if result['status'] == 'success': + logging.info("\033[32mFile delete successfully\033[0m") + return True + else: + logging.error("\033[31mCan't delete the file\033[0m") + return False + except: + logging.error("\033[31mCan't find this bucket\033[0m") + return def list_files(self, bucket_name, prefix='', limit='10', offset="0"): bucket_id = self._get_bucket_id(bucket_name) @@ -117,27 +140,32 @@ def list_files(self, bucket_name, prefix='', limit='10', offset="0"): files = result['data']['file_list'] file_list = [] for file in files: - file_info: File = File(file) + file_info: File = File(file, self.gateway) file_list.append(file_info) return file_list else: - print("\033[31mError: " + result['message'] + "\033[0m") + logging.error("\033[31m" + result['message'] + "\033[0m") return False - - def upload_file(self, bucket_name, object_name, file_path): + def upload_file(self, bucket_name, object_name, file_path, replace=False): prefix, file_name = object_to_filename(object_name) bucket_id = self._get_bucket_id(bucket_name) - if os.stat(file_path).st_size == 0: - print("\033[31mError:File size cannot be 0\033[0m") - return None + + # if os.stat(file_path).st_size == 0: + # logging.error("\033[31mFile size cannot be 0\033[0m") + # return None + file_size = os.stat(file_path).st_size with open(file_path, 'rb') as file: file_hash = md5(file.read()).hexdigest() result = self._check_file(bucket_id, file_hash, file_name, prefix) if result is None: - print("\033[31mError:Cannot found bucket\033[0m") + logging.error("\033[31mCan't find this bucket\033[0m") return + # Replace file if already existed + if result['data']['file_is_exist'] and replace: + self.delete_file(bucket_name, object_name) + result = self._check_file(bucket_id, file_hash, file_name, prefix) if not (result['data']['file_is_exist']): if not (result['data']['ipfs_is_exist']): with open(file_path, 'rb') as file: @@ -158,37 +186,63 @@ def upload_file(self, bucket_name, object_name, file_path): result = self._merge_file(bucket_id, file_hash, file_name, prefix) file_id = result['data']['file_id'] file_info = self._get_file_info(file_id) - print("\033[32mFile upload successfully\033[0m") + self._create_folders(bucket_name, prefix) + logging.info("\033[32mFile upload successfully\033[0m") return file_info - print("\033[31mError:File already existed\033[0m") + logging.error("\033[31mFile already exists\033[0m") return None + + def _create_folders(self, bucket_name, path): + bucket_id = self._get_bucket_id(bucket_name) + path, folder_name = object_to_filename(path) + while folder_name: + params = {"file_name": folder_name, "prefix": path, "bucket_uid": bucket_id} + self.api_client._request_with_params(POST, CREATE_FOLDER, self.MCS_API, params, self.token, None) + path, folder_name = object_to_filename(path) + + def _upload_to_bucket(self, bucket_name, file_path, prefix=''): + if os.path.isdir(file_path): + return self.upload_folder(bucket_name, file_path, prefix) + else: + file_name = os.path.basename(file_path) + return self.upload_file(bucket_name, os.path.join(prefix, file_name), file_path) + + def upload_folder(self, bucket_name, folder_path, prefix=''): + folder_name = os.path.basename(folder_path) + self.create_folder(bucket_name, folder_name, prefix) + res = [] + files = os.listdir(folder_path) + for f in files: + f_path = os.path.join(folder_path, f) + upload = self._upload_to_bucket(bucket_name, f_path, os.path.join(prefix, folder_name)) + res.append(upload) + + return res - # def upload_folder(self, bucket_id, folder_path, prefix=''): - # path = os.path.basename(folder_path) - # folder_name = os.path.splitext(path)[0] - # self.create_folder(folder_name, bucket_id, prefix) - # files = os.listdir(folder_path) - # success = [] - # for f in files: - # f_path = os.path.join(folder_path, f) - # if os.path.isdir(f_path): - # success.extend(self.upload_folder(bucket_id, f_path, os.path.join(prefix, folder_name))) - # else: - # self.upload_to_bucket(bucket_id, f_path, os.path.join(prefix, folder_name)) - # time.sleep(0.5) - # success.append(f_path) - # return success + # def upload_ipfs_folder(self, bucket_name, object_name, folder_path): + # folder_name = os.path.basename(object_name) or os.path.basename(folder_path) + # prefix = os.path.normpath(os.path.dirname(object_name)) if os.path.dirname(object_name) else '' + # bucket_uid = self._get_bucket_id(bucket_name) + # files = self._read_files(folder_path, folder_name) + # form_data = {"folder_name": folder_name, "prefix": prefix, "bucket_uid": bucket_uid} + # res = self.api_client._request_with_params(POST, PIN_IPFS, self.MCS_API, form_data, self.token, files) + # if res: + # folder = (File(res["data"], self.gateway)) + # return folder + # else: + # logging.error("\033[31mIPFS Folder Upload Error\033[0m") def download_file(self, bucket_name, object_name, local_filename): file = self.get_file(bucket_name, object_name) if file is not None: ipfs_url = file.ipfs_url with open(local_filename, 'wb') as f: - data = urllib.request.urlopen(ipfs_url) - f.write(data.read()) - print("\033[32mFile download successfully\033[0m") + if file.size > 0: + data = urllib.request.urlopen(ipfs_url) + f.write(data.read()) + logging.info("\033[32mFile download successfully\033[0m") return True - print('\033[31mError: File does not exist\033[0m') + logging.error('\033[31mFile does not exist\033[0m') return False def _check_file(self, bucket_id, file_hash, file_name, prefix=''): @@ -234,12 +288,33 @@ def _get_full_file_list(self, bucket_name, prefix=''): self.api_client._request_with_params(GET, FILE_LIST, self.MCS_API, params, self.token, None)['data'][ 'file_list'] for file in result: - file_info: File = File(file) + file_info: File = File(file, self.gateway) file_list.append(file_info) return file_list def _get_file_info(self, file_id): params = {'file_id': file_id} result = self.api_client._request_with_params(GET, FILE_INFO, self.MCS_API, params, self.token, None) - file_info = File(result['data']) + file_info = File(result['data'], self.gateway) return file_info + + def get_gateway(self): + result = self.api_client._request_without_params(GET, GET_GATEWAY, self.MCS_API, self.token) + return 'https://' + result['data'][0] + + def _read_files(self, root_folder, folder_name): + # Create an empty list to store the file tuples + file_dict = [] + + # Use glob to retrieve the file paths in the directory and its subdirectories + file_paths = glob.glob(os.path.join(root_folder, '**', '*'), recursive=True) + + # Loop through each file path and read the contents of the file + for file_path in file_paths: + if os.path.isfile(file_path): + # Get the relative path from the root folder + upload_path = folder_name + "/" + os.path.relpath(file_path, root_folder) + file_dict.append(('files', ( + upload_path, open(file_path, 'rb')))) + + return file_dict diff --git a/mcs/api/onchain_api.py b/mcs/api/onchain_api.py index 6593893..90f253a 100644 --- a/mcs/api/onchain_api.py +++ b/mcs/api/onchain_api.py @@ -1,21 +1,163 @@ from mcs.api_client import APIClient from mcs.common.constants import * +from mcs.common.utils import get_contract_abi, get_amount +from web3 import Web3 +from web3.middleware import geth_poa_middleware +from web3.logs import DISCARD +from eth_account import Account import json +import logging + +from mcs.object.onchain_storage import * class OnchainAPI(object): - def __init__(self, api_client=None): + def __init__(self, api_client=None, private_key=None, rpc_url='https://polygon-rpc.com/'): + if private_key is None: + logging.error("Please provide private_key to use MCS Onchain Storage.") if api_client is None: api_client = APIClient() self.api_client = api_client self.MCS_API = api_client.MCS_API self.token = self.api_client.token + + self.w3 = Web3(Web3.HTTPProvider(rpc_url)) + self.w3.middleware_onion.inject(geth_poa_middleware, layer=0) + self.account = Account.from_key(private_key) + if self._map_chain_name(api_client.chain_name) != self.w3.eth.chain_id: + logging.error(f"\033[31mRPC Chain ID ({self.w3.eth.chain_id}) does not match SDK chain name ({api_client.chain_name})\033[0m") + + self.params = api_client.get_params()["data"] + self.token_contract = self.w3.eth.contract(self.params['usdc_address'], abi=get_contract_abi(USDC_ABI)) + self.payment_contract = self.w3.eth.contract(self.params['payment_contract_address'], abi=get_contract_abi(SWAN_PAYMENT_ABI)) + self.mint_contract = self.w3.eth.contract(self.params['nft_collection_factory_address'], abi=get_contract_abi(MINT_ABI)) + + + def upload(self, file_path, pay=False, params={}): + params['duration'] = '525' + params['storage_copy'] = '5' + params['file'] = (file_path, open(file_path, 'rb')) + upload = self.api_client._request_stream_upload(UPLOAD_FILE, self.MCS_API, params, self.token) + data = upload["data"] + return Upload(data) + + + def pay(self, source_file_upload_id, file_size, amount=''): + payment_info = self.get_payment_info(source_file_upload_id) + + if not amount: + amount = get_amount(float(file_size), self.params["filecoin_price"]) + + decimals = self.token_contract.functions.decimals().call() + contract_amount = int(amount * (10 ** decimals)) + + hash = self._approve_usdc(int(contract_amount * float(self.params['pay_multiply_factor']))) + receipt = '' + + while not receipt: + receipt = self.w3.eth.get_transaction_receipt(hash) + + nonce = self.w3.eth.get_transaction_count(self.account.address) + decimals = self.token_contract.functions.decimals().call() + lock_obj = { + 'id': payment_info.w_cid, + 'minPayment': contract_amount, + 'amount': int(contract_amount * float(self.params['pay_multiply_factor'])), + 'lockTime': 86400 * self.params['lock_time'], + 'recipient': self.params['payment_recipient_address'], + 'size': file_size, + 'copyLimit': 5, + } + + tx = self.payment_contract.functions.lockTokenPayment(lock_obj).build_transaction({ + 'from': self.account.address, + 'nonce': nonce + }) + signed_tx = self.w3.eth.account.sign_transaction(tx, self.account._private_key) + tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=CONTRACT_TIME_OUT) + + return self.w3.toHex(tx_hash) + + def mint(self, source_file_upload_id, nft, collection_address = '', quantity = 1): + if not collection_address: + collection_address = self.params["default_nft_collection_address"] + metadata = self._upload_nft_metadata(nft) + # print(metadata) + + nonce = self.w3.eth.get_transaction_count(self.account.address) + option_obj = { + 'from': self.account.address, + 'nonce': nonce + } + tx = self.mint_contract.functions.mint(collection_address, self.account.address, quantity, str(metadata["data"]["ipfs_url"])).build_transaction(option_obj) + signed_tx = self.w3.eth.account.sign_transaction(tx, self.account._private_key) + tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) + receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=CONTRACT_TIME_OUT) + result = self.mint_contract.events.TransferSingle().processReceipt(receipt, errors=DISCARD) + id = result[0]['args']['id'] + token_id = int(id) + + collection_id = self._get_collection_id(collection_address) + + self._post_mint_info(source_file_upload_id, self.w3.toHex(tx_hash), token_id, collection_address, collection_id, nft.get("name", ""), nft.get("description", "")) + + return {"hash": self.w3.toHex(tx_hash), "tx_hash": self.w3.toHex(tx_hash), "token_id": token_id, "id": token_id } + + def _get_collection_id(self, collection_address): + collections = self.get_collections() + result = [collection.id for collection in collections if collection.address.lower() == collection_address] + if len(result) == 0: + logging.error(f"\033[31mCollection address {collection_address} not found \033[0m") + return + else: + return result[0] + + + def create_collection(self, collection_metadata): + collection_name = collection_metadata["name"] + metadata = self._upload_nft_metadata(collection_metadata) + + nonce = self.w3.eth.get_transaction_count(self.account.address) + option_obj = { + 'from': self.account.address, + 'nonce': nonce + } + tx = self.mint_contract.functions.createCollection(collection_name, str(metadata["data"]["ipfs_url"])).build_transaction(option_obj) + signed_tx = self.w3.eth.account.sign_transaction(tx, self.account._private_key) + tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) + # receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=CONTRACT_TIME_OUT) + # result = self.mint_contract.events.CreateCollection().processReceipt(receipt, errors=DISCARD) + # collection_address = result[0]['args']['collectionAddress'] + + collection_info = collection_metadata + # collection_info['address'] = collection_address + collection_info['tx_hash'] = self.w3.toHex(tx_hash) + + result = self._post_collection_info(collection_info) + result["tx_hash"] = self.w3.toHex(tx_hash) + + return result + + def get_mint_info(self, source_file_upload_id): + params = {} + if source_file_upload_id: + params['source_file_upload_id'] = source_file_upload_id + mint_info = self.api_client._request_with_params(GET, MINT_INFO, self.MCS_API, params, self.token, None) + if not mint_info: + logging.error(f"\033[31mmint info for id {source_file_upload_id} not found \033[0m") + return + # return Payment(payment_data["data"]) def get_payment_info(self, source_file_upload_id): params = {} if source_file_upload_id: params['source_file_upload_id'] = source_file_upload_id - return self.api_client._request_with_params(GET, PAYMENT_INFO, self.MCS_API, params, self.token, None) + payment_data = self.api_client._request_with_params(GET, PAYMENT_INFO, self.MCS_API, params, self.token, None) + if not payment_data: + logging.error(f"\033[31mpayment info for id {source_file_upload_id} not found \033[0m") + return + return Payment(payment_data["data"]) def get_user_tasks_deals(self, page_number=None, page_size=None, file_name=None, status=None): params = {} @@ -27,13 +169,26 @@ def get_user_tasks_deals(self, page_number=None, page_size=None, file_name=None, params['file_name'] = file_name if status: params['status'] = status - return self.api_client._request_with_params(GET, TASKS_DEALS, self.MCS_API, params, self.token, None) + deal_response = self.api_client._request_with_params(GET, TASKS_DEALS, self.MCS_API, params, self.token, None) + deals = deal_response["data"]["source_file_upload"] - def get_mint_info(self, source_file_upload_id, payload_cid, tx_hash, token_id, mint_address): - params = {'source_file_upload_id': source_file_upload_id, 'payload_cid': payload_cid, 'tx_hash': tx_hash, - 'token_id': int(token_id), 'mint_address': mint_address} + deal_list = [] + + for deal_info in deals: + deal_list.append(SourceFile(deal_info)) + return deal_list + + def _post_mint_info(self, source_file_upload_id, tx_hash, token_id, collection_address, collection_id, name, description): + params = {'source_file_upload_id': source_file_upload_id, 'tx_hash': tx_hash, + 'token_id': int(token_id), 'mint_address': collection_address, + 'nft_collection_id': collection_id, 'name': name, 'description': description} return self.api_client._request_with_params(POST, MINT_INFO, self.MCS_API, params, self.token, None) + def _post_collection_info(self, collection_info): + collection_info['seller_fee'] = collection_info.get('seller_fee', 0) + return self.api_client._request_with_params(POST, COLLECTION, self.MCS_API, collection_info, self.token, None) + + def stream_upload_file(self, file_path): params = {} params['duration'] = '525' @@ -45,25 +200,42 @@ def get_deal_detail(self, source_file_upload_id, deal_id='0'): params = {} if source_file_upload_id: params['source_file_upload_id'] = source_file_upload_id - return self.api_client._request_with_params(GET, DEAL_DETAIL + deal_id, self.MCS_API, params, self.token, None) + deal = self.api_client._request_with_params(GET, DEAL_DETAIL + deal_id, self.MCS_API, params, self.token, None) + return Deal(deal["data"]["source_file_upload_deal"]) + + def get_collections(self): + res = self.api_client._request_without_params(GET, COLLECTIONS, self.MCS_API, self.token) + return list(map(lambda collection: Collection(collection) ,res["data"])) - def upload_nft_metadata(self, address, file_name, image_url, tx_hash, size): + def _upload_nft_metadata(self, nft): params = {} - if address: - params['duration'] = '525' - params['file_type'] = '1' - params['wallet_address'] = address - file_url = {} - if image_url: - file_url['name'] = file_name - file_url['image'] = image_url - file_url['tx_hash'] = tx_hash - file_url['attributes'] = [ - { - "trait_type": "Size", - "value": size - } - ] - file_url['external_url'] = image_url - files = {"fileName": "test", "file": json.dumps(file_url)} + params['duration'] = '525' + params['file_type'] = '1' + params['wallet_address'] = self.account.address + + # params['file'] = (nft_name, json.dumps(nft)) + files = {"fileName": nft["name"], "file": json.dumps(nft)} return self.api_client._request_with_params(POST, UPLOAD_FILE, self.MCS_API, params, self.token, files) + + # upload = self.api_client._request_stream_upload(UPLOAD_FILE, self.MCS_API, params, self.token) + # data = upload["data"] + # print(data) + # return Upload(data) + + def _approve_usdc(self, amount): + nonce = self.w3.eth.get_transaction_count(self.account.address) + tx = self.token_contract.functions.approve(self.payment_contract.address, amount).build_transaction({ + 'from': self.account.address, + 'nonce': nonce + }) + signed_tx = self.w3.eth.account.sign_transaction(tx, self.account._private_key) + tx_hash = self.w3.eth.send_raw_transaction(signed_tx.rawTransaction) + self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=CONTRACT_TIME_OUT) + return self.w3.toHex(tx_hash) + + def _map_chain_name(self, chain_name): + if chain_name == 'polygon.mainnet': + return 137 + elif chain_name == 'polygon.mumbai': + return 80001 + return -1 diff --git a/mcs/api_client.py b/mcs/api_client.py index 8464927..737c759 100644 --- a/mcs/api_client.py +++ b/mcs/api_client.py @@ -2,6 +2,7 @@ from mcs.common.params import Params import requests import json +import logging from mcs.common import utils, exceptions from mcs.common import constants as c from requests_toolbelt.multipart.encoder import MultipartEncoder, MultipartEncoderMonitor @@ -22,27 +23,30 @@ def __init__(self, api_key, access_token, chain_name=None, login=True): self.api_key_login() def get_params(self): - return self._request_without_params(GET, MCS_PARAMS, self.MCS_API, None) + return self._request_without_params(GET, MCS_PARAMS, self.MCS_API, self.token) def get_price_rate(self): return self._request_without_params(GET, PRICE_RATE, self.MCS_API, self.token) + + def get_gateway(self): + res = self._request_without_params(GET, GET_GATEWAY, self.MCS_API, self.token) + gateway = res["data"][0] + return gateway def api_key_login(self): params = {'apikey': self.api_key, 'access_token': self.access_token, 'network': self.chain_name} - if params.get('apikey') == '' or params.get('access_token') == '': - print("\033[31mAPIkey or access token does not exist\033[0m") - return False - result = self._request_with_params(POST, APIKEY_LOGIN, self.MCS_API, params, None, None) - if result is None: - print("\033[31mRequest Error\033[0m") - return - if result['status'] != "success": - print("\033[31mError: " + result['message'] + ". \nPlease check your APIkey and access token, or " + # if params.get('apikey') == '' or params.get('access_token') == '' or params.get('chain_name') == '': + # logging.error("\033[31mAPIkey, access token, or chain name does not exist\033[0m") + # return + try: + result = self._request_with_params(POST, APIKEY_LOGIN, self.MCS_API, params, None, None) + self.token = result['data']['jwt_token'] + logging.info("\033[32mLogin successful\033[0m") + return self.token + except: + logging.error("\033[31m Please check your APIkey and access token, or " "check whether the current network environment corresponds to the APIkey.\033[0m") return - self.token = result['data']['jwt_token'] - print("\033[32mLogin successful\033[0m") - return self.token def _request(self, method, request_path, mcs_api, params, token, files=False): if method == c.GET: diff --git a/mcs/common/constants.py b/mcs/common/constants.py index 18b4f48..f6f29ee 100644 --- a/mcs/common/constants.py +++ b/mcs/common/constants.py @@ -18,6 +18,8 @@ USER_LOGIN = "/api/v1/user/login_by_metamask_signature" GENERATE_APIKEY = "/api/v1/user/generate_api_key" APIKEY_LOGIN = "/api/v1/user/login_by_api_key" +COLLECTIONS = "/api/v1/storage/mint/nft_collections" +COLLECTION = "/api/v1/storage/mint/nft_collection" # bucket api CREATE_BUCKET = "/api/v2/bucket/create" BUCKET_LIST = "/api/v2/bucket/get_bucket_list" @@ -29,9 +31,12 @@ UPLOAD_CHUNK = "/api/v2/oss_file/upload" MERGE_FILE = "/api/v2/oss_file/merge" FILE_LIST = "/api/v2/oss_file/get_file_list" +GET_FILE = "/api/v2/oss_file/get_file_by_object_name" +GET_GATEWAY = "/api/v2/gateway/get_gateway" +PIN_IPFS = "/api/v2/oss_file/pin_files_to_ipfs" # contract USDC_ABI = "ERC20.json" SWAN_PAYMENT_ABI = "SwanPayment.json" -MINT_ABI = "SwanNFT.json" +MINT_ABI = "CollectionFactory.json" CONTRACT_TIME_OUT = 300 diff --git a/mcs/common/utils.py b/mcs/common/utils.py index 859fbf5..c668459 100644 --- a/mcs/common/utils.py +++ b/mcs/common/utils.py @@ -42,3 +42,4 @@ def object_to_filename(object_name): prefix = object_name[0:index] file_name = object_name[index + 1:] return prefix, file_name + diff --git a/mcs/contract/abi/CollectionFactory.json b/mcs/contract/abi/CollectionFactory.json new file mode 100644 index 0000000..9322f60 --- /dev/null +++ b/mcs/contract/abi/CollectionFactory.json @@ -0,0 +1,251 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "defaultCollection", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "collectionOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "collectionAddress", + "type": "address" + } + ], + "name": "CreateCollection", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "collectionAddress", + "type": "address" + } + ], + "name": "changeDefaultCollection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "collectionName", + "type": "string" + }, + { + "internalType": "string", + "name": "contractURI", + "type": "string" + } + ], + "name": "createCollection", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "defaultCollectionAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getCollections", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "collection", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "collectionName", + "type": "string" + }, + { + "internalType": "string", + "name": "contractURI", + "type": "string" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "name": "mintToNewCollection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + } +] diff --git a/mcs/contract/abi/SwanNFT.json b/mcs/contract/abi/SwanNFT.json deleted file mode 100644 index 4752d5b..0000000 --- a/mcs/contract/abi/SwanNFT.json +++ /dev/null @@ -1,584 +0,0 @@ -[ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "ids", - "type": "uint256[]" - }, - { - "indexed": false, - "internalType": "uint256[]", - "name": "values", - "type": "uint256[]" - } - ], - "name": "TransferBatch", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "TransferSingle", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "string", - "name": "value", - "type": "string" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "URI", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "accounts", - "type": "address[]" - }, - { - "internalType": "uint256[]", - "name": "ids", - "type": "uint256[]" - } - ], - "name": "balanceOfBatch", - "outputs": [ - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "contractURI", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "exists", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "idCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "isUnique", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "mintMore", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "string", - "name": "newUri", - "type": "string" - } - ], - "name": "mintUnique", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "string", - "name": "newUri", - "type": "string" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "mintUniqueWithData", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "ids", - "type": "uint256[]" - }, - { - "internalType": "uint256[]", - "name": "amounts", - "type": "uint256[]" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "safeBatchTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "string", - "name": "newUri", - "type": "string" - } - ], - "name": "setURI", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "uri", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/mcs/contract/mcs_contract.py b/mcs/contract/mcs_contract.py deleted file mode 100644 index b249e5c..0000000 --- a/mcs/contract/mcs_contract.py +++ /dev/null @@ -1,80 +0,0 @@ -from web3 import Web3 -from mcs.common.constants import * -from mcs.common.params import Params -from mcs.common.utils import get_contract_abi, get_amount -from web3.middleware import geth_poa_middleware -from mcs.api_client import APIClient - - -class ContractClient(): - def __init__(self, rpc_endpoint, chain_name): - self.rpc_endpoint = rpc_endpoint - self.w3 = Web3(Web3.HTTPProvider(rpc_endpoint)) - self.w3.middleware_onion.inject(geth_poa_middleware, layer=0) - data = APIClient(None, None, chain_name, False).get_params()['data'] - self.SWAN_PAYMENT_ADDRESS = data['payment_contract_address'] - self.USDC_TOKEN = data['usdc_address'] - self.MINT_ADDRESS = data['mint_contract_address'] - - def approve_usdc(self, wallet_address, private_key, amount): - nonce = self.w3.eth.getTransactionCount(wallet_address) - usdc_abi = get_contract_abi(USDC_ABI) - token = self.w3.eth.contract(self.USDC_TOKEN, abi=usdc_abi) - decimals = token.functions.decimals().call() - amount = int(amount * (10 ** decimals)) - usdc_balance = token.functions.balanceOf(wallet_address).call() - if int(usdc_balance) < int(amount): - print("Insufficient balance") - return - tx = token.functions.approve(self.SWAN_PAYMENT_ADDRESS, amount).buildTransaction({ - 'from': wallet_address, - 'nonce': nonce - }) - signed_tx = self.w3.eth.account.signTransaction(tx, private_key) - tx_hash = self.w3.eth.sendRawTransaction(signed_tx.rawTransaction) - self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=CONTRACT_TIME_OUT) - return self.w3.toHex(tx_hash) - - def upload_file_pay(self, wallet_address, private_key, file_size, w_cid, rate, params): - amount = get_amount(file_size, rate) - nonce = self.w3.eth.getTransactionCount(wallet_address) - swan_payment_abi = get_contract_abi(SWAN_PAYMENT_ABI) - swan_payment = self.w3.eth.contract(self.SWAN_PAYMENT_ADDRESS, abi=swan_payment_abi) - usdc_abi = get_contract_abi(USDC_ABI) - token = self.w3.eth.contract(self.USDC_TOKEN, abi=usdc_abi) - decimals = token.functions.decimals().call() - lock_obj = { - 'id': w_cid, - 'minPayment': int(amount * (10 ** decimals)), - 'amount': int(amount * (10 ** decimals) * float(params['pay_multiply_factor'])), - 'lockTime': 86400 * params['lock_time'], - 'recipient': params['payment_recipient_address'], - 'size': file_size, - 'copyLimit': 5, - } - options_obj = { - 'from': wallet_address, - 'nonce': nonce - } - tx = swan_payment.functions.lockTokenPayment(lock_obj).buildTransaction(options_obj) - signed_tx = self.w3.eth.account.signTransaction(tx, private_key) - tx_hash = self.w3.eth.sendRawTransaction(signed_tx.rawTransaction) - self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=CONTRACT_TIME_OUT) - return self.w3.toHex(tx_hash) - - def mint_nft(self, wallet_address, private_key, nft_meta_uri): - nonce = self.w3.eth.getTransactionCount(wallet_address) - mint_abi = get_contract_abi(MINT_ABI) - mint_contract = self.w3.eth.contract(self.MINT_ADDRESS, abi=mint_abi) - option_obj = { - 'from': wallet_address, - 'nonce': nonce - } - tx = mint_contract.functions.mintUnique(wallet_address, str(nft_meta_uri)).buildTransaction(option_obj) - signed_tx = self.w3.eth.account.signTransaction(tx, private_key) - tx_hash = self.w3.eth.sendRawTransaction(signed_tx.rawTransaction) - receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=CONTRACT_TIME_OUT) - result = mint_contract.events.TransferSingle().processReceipt(receipt) - id = result[0]['args']['id'] - token_id = int(id) - return self.w3.toHex(tx_hash), token_id diff --git a/mcs/object/bucket_storage.py b/mcs/object/bucket_storage.py index 582f960..2014813 100644 --- a/mcs/object/bucket_storage.py +++ b/mcs/object/bucket_storage.py @@ -22,7 +22,7 @@ def to_json(self): class File: - def __init__(self, file_data): + def __init__(self, file_data, gateway = 'https://ipfs.io'): self.name = file_data["name"] self.address = file_data["address"] self.bucket_uid = file_data["bucket_uid"] @@ -30,7 +30,7 @@ def __init__(self, file_data): self.prefix = file_data["prefix"] self.size = file_data["size"] self.payloadCid = file_data["payload_cid"] - self.ipfs_url = file_data["ipfs_url"] + self.ipfs_url = gateway + '/ipfs/' + file_data["payload_cid"] self.pin_status = file_data["pin_status"] self.is_deleted = file_data["is_deleted"] self.is_folder = file_data["is_folder"] @@ -38,6 +38,9 @@ def __init__(self, file_data): self.updated_at = file_data["updated_at"] self.created_at = file_data["created_at"] self.deleted_at = file_data["deleted_at"] + self.gateway = gateway + self.object_name = file_data["object_name"] + self.type = file_data["type"] def to_json(self): return json.dumps(self, default=lambda o: o.__dict__, diff --git a/mcs/object/onchain_storage.py b/mcs/object/onchain_storage.py new file mode 100644 index 0000000..7340b0d --- /dev/null +++ b/mcs/object/onchain_storage.py @@ -0,0 +1,110 @@ +import json + + +class Upload: + def __init__(self, upload_data): + self.source_file_upload_id = upload_data["source_file_upload_id"] + self.payload_cid = upload_data["payload_cid"] + self.ipfs_url = upload_data["ipfs_url"] + self.file_size = upload_data["file_size"] + self.w_cid = upload_data["w_cid"] + self.status = upload_data["status"] + + def pay(self, amount=''): + pass + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) + + +class Deal: + def __init__(self, deal_data): + self.deal_id = deal_data.get("deal_id", None) + self.message_cid = deal_data.get("message_cid", None) + self.deal_cid = deal_data["deal_cid"] + self.height = deal_data["height"] + self.piece_cid = deal_data["piece_cid"] + self.verified_deal = deal_data["verified_deal"] + self.storage_price_per_epoch = deal_data["storage_price_per_epoch"] + self.signature = deal_data["signature"] + self.signature_type = deal_data["signature_type"] + self.created_at = deal_data["created_at"] + self.piece_size_format = deal_data["piece_size_format"] + self.start_height = deal_data["start_height"] + self.end_height = deal_data["end_height"] + self.client = deal_data["client"] + self.client_collateral_format = deal_data["client_collateral_format"] + self.provider = deal_data["provider"] + self.provider_tag = deal_data["provider_tag"] + self.verified_provider = deal_data["verified_provider"] + self.provider_collateral_format = deal_data["provider_collateral_format"] + self.status = deal_data["status"] + self.network_name = deal_data["network_name"] + self.storage_price = deal_data["storage_price"] + self.ipfs_url = deal_data["ipfs_url"] + self.file_name = deal_data["file_name"] + self.w_cid = deal_data["w_cid"] + self.car_file_payload_cid = deal_data["car_file_payload_cid"] + self.locked_at = deal_data["locked_at"] + self.locked_fee = deal_data["locked_fee"] + self.unlocked = deal_data["unlocked"] + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) + +class SourceFile: + def __init__(self, file_data): + self.source_file_upload_id = file_data["source_file_upload_id"] + self.file_name = file_data["file_name"] + self.file_size = file_data["file_size"] + self.upload_at = file_data["upload_at"] + self.duration = file_data["duration"] + self.ipfs_url = file_data["ipfs_url"] + self.pin_status = file_data["pin_status"] + self.pay_amount = file_data["pay_amount"] + self.status = file_data["status"] + self.note = file_data["note"] + self.is_free = file_data["is_free"] + self.is_minted = file_data["is_minted"] + self.refunded_by_self = file_data["refunded_by_self"] + self.offline_deal = file_data["offline_deal"] + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) + +class Collection: + def __init__(self, collection_data): + self.id = collection_data["id"] + self.address = collection_data["address"] + + self.name = collection_data["name"] + self.description = collection_data["description"] + self.image_url = collection_data["image_url"] + self.external_link = collection_data["external_link"] + self.seller_fee = collection_data["seller_fee"] + self.wallet_id = collection_data["wallet_id"] + self.wallet_id_recipient = collection_data["wallet_id_recipient"] + self.wallet_recipient = collection_data["wallet_recipient"] + self.is_default = collection_data["is_default"] + + self.tx_hash = collection_data["tx_hash"] + self.create_at = collection_data["create_at"] + self.update_at = collection_data["update_at"] + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) + +class Payment: + def __init__(self, payment_data): + self.w_cid = payment_data["w_cid"] + self.pay_amount = payment_data["pay_amount"] + self.pay_tx_hash = payment_data["pay_tx_hash"] + self.token_address = payment_data["token_address"] + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) \ No newline at end of file diff --git a/mcs/upload/__init__.py b/mcs/upload/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/mcs/upload/onchain_upload.py b/mcs/upload/onchain_upload.py deleted file mode 100644 index d1f62df..0000000 --- a/mcs/upload/onchain_upload.py +++ /dev/null @@ -1,68 +0,0 @@ -from mcs import APIClient -from mcs.contract import ContractClient -from mcs.common.utils import get_amount -from mcs.api import OnchainAPI -import logging -from web3 import Web3 - - -class OnchainUpload(): - def __init__(self, chain_name, private_key, rpc_endpoint, api_key, access_token, file_path): - self.chain_name = chain_name - self.private_key = private_key - self.rpc_endpoint = rpc_endpoint - self.api_key = api_key - self.access_token = access_token - self.wallet_address = Web3(Web3.HTTPProvider(self.rpc_endpoint)) \ - .eth.account.privateKeyToAccount(self.private_key).address - self.file_path = file_path - self.api = APIClient(self.api_key, self.access_token, self.chain_name) - self.onchain = OnchainAPI(self.api) - self.w3_api = ContractClient(self.rpc_endpoint, self.chain_name) - - def approve_token(self, amount): - return self.w3_api.approve_usdc(self.wallet_address, self.private_key, amount) - - def simple_upload(self, amount): - self.approve_token(amount) - self.stream_upload() - payment_hash = self.pay() - return payment_hash - - def stream_upload(self): - upload_file = self.onchain.stream_upload_file(self.wallet_address, self.file_path) - file_data = upload_file["data"] - self.upload_response = file_data - - def estimate_amount(self): - file_size = self.upload_response['file_size'] - rate = self.api.get_price_rate()["data"] - amount = get_amount(file_size, rate) - return amount - - def pay(self): - file_size, w_cid = self.upload_response['file_size'], self.upload_response['w_cid'] - params = self.api.get_params()["data"] - rate = self.api.get_price_rate()["data"] - # payment - try: - self.payment_tx_hash = self.w3_api.upload_file_pay(self.wallet_address, self.private_key, - file_size, w_cid, rate, params) - except Exception as e: - logging.error(str(e)) - return 'payment failed: ' + str(e) - - return self.payment_tx_hash - - def mint(self, file_name): - file_data = self.upload_response - source_file_upload_id, nft_uri, file_size = file_data['source_file_upload_id'], file_data['ipfs_url'], \ - file_data['file_size'] - meta_url = \ - self.onchain.upload_nft_metadata(self.wallet_address, file_name, nft_uri, self.payment_tx_hash, file_size)[ - 'data'][ - 'ipfs_url'] - tx_hash, token_id = self.w3_api.mint_nft(self.wallet_address, self.private_key, meta_url) - response = self.onchain.get_mint_info(source_file_upload_id, None, - tx_hash, token_id, self.api.get_params()["data"]['mint_contract_address']) - return tx_hash, token_id, response diff --git a/setup.py b/setup.py index 1328e9f..c47cb4a 100644 --- a/setup.py +++ b/setup.py @@ -5,11 +5,11 @@ long_description = (this_directory / "PipReleaseDoc.md").read_text() setup(name="python-mcs-sdk", - version="0.2.1", + version="0.2.15", author="daniel8088", author_email="danilew8088@gmail.com", install_requires=["web3==5.31.1", "requests==2.28.1", "requests_toolbelt==0.10.1", "tqdm==4.64.1"], - packages=["mcs", "mcs.api", "mcs.contract", "mcs.contract.abi", "mcs.common", "mcs.upload", "mcs.object"], + packages=["mcs", "mcs.api", "mcs.contract", "mcs.contract.abi", "mcs.common", "mcs.object"], license="MIT", include_package_data=True, description="A python software development kit for the Multi-Chain Storage", diff --git a/test/test_bucket_api.py b/test/test_bucket_api.py index ea09fcd..29bf44b 100644 --- a/test/test_bucket_api.py +++ b/test/test_bucket_api.py @@ -5,84 +5,83 @@ from mcs import BucketAPI, APIClient import time -chain_name = "polygon.mumbai" - - -def test_info(): +def login(): load_dotenv(".env_test") - wallet_info = { - 'api_key': os.getenv('api_key'), - 'access_token': os.getenv('access_token') - } - return wallet_info - - -def test_user_register(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - print(api.token) + api_key = os.getenv('api_key') + access_token = os.getenv('access_token') + chain_name = os.getenv("chain_name") + api = BucketAPI(APIClient(api_key, access_token, chain_name)) + + assert api + return api +def test_delete_bucket(): + api = login() + print(api.delete_bucket('test-bucket')) def test_list_buckets(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - for i in api.list_buckets(): - print(i.to_json()) - + api = login() + assert api.list_buckets() is not None def test_create_bucket(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - print(api.create_bucket('33333')) - - -def test_delete_bucket(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - print(api.delete_bucket('33333')) - + api = login() + create = api.create_bucket('test-bucket') + assert create == True def test_get_bucket(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - print(api.get_bucket('123121')) + api = login() + bucket = api.get_bucket('test-bucket') + assert bucket.bucket_name == 'test-bucket' def test_create_folder(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - print(api.create_folder('12312', '55555', '44444')) + api = login() + create = api.create_folder('test-bucket', 'folder1') + assert create == True -def test_delete_file(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - print(api.delete_file('12312', '44444/55555/log_mcs.png')) - +# def test_create_folder_with_same_name(): +# api = login() +# create = api.create_folder('test-bucket', 'folder1') +# print(create) def test_upload_file(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) + api = login() filepath = "/images/log_mcs.png" parentpath = os.path.abspath(os.path.dirname(__file__)) - print(api.upload_file('12312', "44444/55555/log_mcs.png", parentpath + filepath).to_json()) - + file = api.upload_file('test-bucket', "folder1/mcs-logo.png", parentpath + filepath) + assert file.name == "mcs-logo.png" def test_get_file(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - print(api.get_file('12312', '44444/55555/log_mcs.png').to_json()) + api = login() + file = api.get_file('test-bucket', 'folder1/mcs-logo.png') + + assert file.name == "mcs-logo.png" def test_get_file_list(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - for i in api.list_files(12312, '44444/55555', 100, '0'): - print(i.to_json()) + api = login() + list = api.list_files('test-bucket', 'folder1') + + assert len(list) == 1 + assert list[0].name == 'mcs-logo.png' def test_download_file(): - info = test_info() - api = BucketAPI(APIClient(info['api_key'], info['access_token'], chain_name)) - result = api.download_file('12312', '44444/55555/log_mcs.png', "aaaa.png") - print(result) + api = login() + result = api.download_file('test-bucket', 'folder1/mcs-logo.png', "aaaa.png") + + assert result == True + + +def test_delete_file(): + api = login() + delete = api.delete_file('test-bucket', 'folder1/mcs-logo.png') + + assert delete == True + +def test_upload_ipfs_folder(): + api = login() + res = api.upload_ipfs_folder('test-bucket', 'ipfs-folder', 'images') + + print(res) \ No newline at end of file diff --git a/test/test_onchain_api.py b/test/test_onchain_api.py index e51a6fe..c862e84 100644 --- a/test/test_onchain_api.py +++ b/test/test_onchain_api.py @@ -4,36 +4,61 @@ from mcs.common.params import Params from mcs import OnchainAPI, APIClient -chain_name = 'polygon.mumbai' -load_dotenv('.env_test') -api_key = os.getenv('api_key') -access_token = os.getenv('access_token') -@pytest.mark.asyncio -async def test_get_params(): - mcs_api = APIClient(api_key, access_token, chain_name) - print(mcs_api.get_params()) +def login(): + load_dotenv(".env_test") + api_key = os.getenv('api_key') + access_token = os.getenv('access_token') + chain_name = os.getenv("chain_name") + private_key = os.getenv("private_key") + rpc_endpoint = os.getenv("rpc_endpoint") -@pytest.mark.asyncio -async def test_user_register(): - mcs_api = APIClient(api_key, access_token, chain_name) - print(mcs_api.token) + api = OnchainAPI(APIClient(api_key, access_token, chain_name), private_key, rpc_endpoint) + # print(api.token) + assert api + return api -@pytest.mark.asyncio -async def test_get_price_rate(): - mcs_api = APIClient(api_key, access_token, chain_name) - print(mcs_api.get_price_rate()) +def test_upload_file(): + api = login() + filepath = "/images/log_mcs.png" + parentpath = os.path.abspath(os.path.dirname(__file__)) + file = api.upload(parentpath + filepath) + pytest.file = file + assert file -@pytest.mark.asyncio -async def test_upload_file(): - filepath = "/images/log_mcs.png" - parent_path = os.path.abspath(os.path.dirname(__file__)) +def test_pay_file(): + api = login() + payment = api.pay(pytest.file.source_file_upload_id, pytest.file.file_size) + print(payment) + +def test_create_collection(): + api = login() + + num_collections = len(api.get_collections()) + collection = api.create_collection({"name": 'test-collection-'+str(num_collections)}) + + assert len(api.get_collections()) == num_collections + 1 + +# def test_mint(): +# api = login() + +# mint = api.mint(pytest.file.source_file_upload_id, {"name": 'test-nft', 'image': pytest.file.ipfs_url}, pytest.collection_address) + +# assert mint["id"] == 1 + +def test_get_uploads(): + api = login() + deals = api.get_user_tasks_deals() - mcs_api = APIClient(api_key, access_token, chain_name) - onchain_api = OnchainAPI(mcs_api) - print(onchain_api.upload_file(parent_path + filepath)) + file_ids = [deal.source_file_upload_id for deal in deals] + + assert pytest.file.source_file_upload_id in file_ids +def test_get_deal_detail(): + api = login() + detail = api.get_deal_detail(pytest.file.source_file_upload_id) + assert detail diff --git a/test/test_onchain_upload.py b/test/test_onchain_upload.py deleted file mode 100644 index 96facb4..0000000 --- a/test/test_onchain_upload.py +++ /dev/null @@ -1,133 +0,0 @@ -import os - -import web3 -from web3 import Web3 -from dotenv import load_dotenv -from mcs import APIClient, OnchainAPI -from mcs.common.params import Params -from mcs.contract import ContractClient -from mcs.common.utils import get_amount - -chain_name = "polygon.mumbai" - - -def test_info(): - load_dotenv(".env_test") - wallet_info = { - 'wallet_address': os.getenv('wallet_address'), - 'private_key': os.getenv('private_key'), - 'rpc_endpoint': os.getenv('rpc_endpoint'), - 'api_key': os.getenv('api_key'), - 'access_token': os.getenv('access_token') - } - return wallet_info - - -def test_approve_usdc(): - info = test_info() - wallet_address = info['wallet_address'] - private_key = info['private_key'] - rpc_endpoint = info['rpc_endpoint'] - w3_api = ContractClient(rpc_endpoint, chain_name) - w3_api.approve_usdc(wallet_address, private_key, 1) - - -def test_upload_file_pay(): - info = test_info() - wallet_address = info['wallet_address'] - private_key = info['private_key'] - rpc_endpoint = info['rpc_endpoint'] - api_key = info['api_key'] - access_token = info['access_token'] - - w3_api = ContractClient(rpc_endpoint, chain_name) - onchain_api = OnchainAPI(APIClient(api_key, access_token, chain_name)) - # upload file to mcs - filepath = "/images/log_mcs.png" - parent_path = os.path.abspath(os.path.dirname(__file__)) - upload_file = onchain_api.stream_upload_file(parent_path + filepath) - print(upload_file) - # test upload file - assert upload_file['status'] == 'success' - file_data = upload_file["data"] - payload_cid, source_file_upload_id, nft_uri, file_size, w_cid = file_data['payload_cid'], file_data[ - 'source_file_upload_id'], file_data['ipfs_url'], file_data['file_size'], file_data['w_cid'] - # get the global variable - params = onchain_api.api_client.get_params()["data"] - # test get params api - assert onchain_api.api_client.get_params()['status'] == 'success' - # get filcoin price - rate = onchain_api.api_client.get_price_rate()["data"] - # test get price rate api - assert onchain_api.api_client.get_price_rate()['status'] == 'success' - amount = get_amount(file_size, rate) - approve_amount = int(web3.Web3.toWei(amount, 'ether') * float(params['pay_multiply_factor'])) - w3_api.approve_usdc(wallet_address, private_key, approve_amount) - # test upload_file_pay contract - w3_api.upload_file_pay(wallet_address, private_key, file_size, w_cid, rate, params) - # test get payment info api - payment_info = onchain_api.get_payment_info(source_file_upload_id) - assert payment_info['status'] == 'success' - assert payment_info['data']['w_cid'] == w_cid - # test get deal detail - deal_detail = onchain_api.get_deal_detail(source_file_upload_id) - assert deal_detail['status'] == 'success' - assert deal_detail['data'] != None - - -def test_mint_nft(): - info = test_info() - wallet_address = info['wallet_address'] - private_key = info['private_key'] - rpc_endpoint = info['rpc_endpoint'] - api_key = info['api_key'] - access_token = info['access_token'] - - w3_api = ContractClient(rpc_endpoint, chain_name) - onchain_api = OnchainAPI(APIClient(api_key, access_token, chain_name)) - w3 = Web3(Web3.HTTPProvider(rpc_endpoint)) - - # upload file to mcs - filepath = "/images/log_mcs.png" - filename = "log_mcs.png" - parent_path = os.path.abspath(os.path.dirname(__file__)) - upload_file = onchain_api.stream_upload_file(parent_path + filepath) - # test upload file - assert upload_file['status'] == 'success' - file_data = upload_file["data"] - payload_cid, source_file_upload_id, nft_uri, file_size, w_cid = file_data['payload_cid'], file_data[ - 'source_file_upload_id'], file_data['ipfs_url'], file_data['file_size'], file_data['w_cid'] - # get the global variable - params = onchain_api.api_client.get_params()["data"] - # test get params api - assert onchain_api.api_client.get_params()['status'] == 'success' - # get filcoin price - rate = onchain_api.api_client.get_price_rate()["data"] - # test get price rate api - assert onchain_api.api_client.get_price_rate()['status'] == 'success' - amount = get_amount(file_size, rate) - approve_amount = int(w3.toWei(amount, 'ether') * float(params['pay_multiply_factor'])) - w3_api.approve_usdc(wallet_address, private_key, approve_amount) - # test upload_file_pay contract - tx_hash = w3_api.upload_file_pay(wallet_address, private_key, file_size, w_cid, rate, params) - print(tx_hash) - # upload nft metadata - nft_metadata = onchain_api.upload_nft_metadata(wallet_address, filename, nft_uri, tx_hash, file_size) - # test upload nft metadata - assert nft_metadata['status'] == 'success' - meta_url = nft_metadata['data']['ipfs_url'] - print(meta_url) - # test mint nft contract - tx_hash, token_id = w3_api.mint_nft(wallet_address, - private_key, meta_url) - print(tx_hash) - - # update mint info - mint_address = params['mint_contract_address'] - mint_info = onchain_api.get_mint_info(source_file_upload_id, None, tx_hash, token_id, mint_address) - # test update mint info - assert mint_info['status'] == 'success' - assert mint_info['data']['source_file_upload_id'] == source_file_upload_id - assert mint_info['data']['nft_tx_hash'] == tx_hash - assert mint_info['data']['token_id'] == int(token_id) - assert mint_info['data']['mint_address'] == mint_address diff --git a/test/test_simple_upload.py b/test/test_simple_upload.py deleted file mode 100644 index 84e4be5..0000000 --- a/test/test_simple_upload.py +++ /dev/null @@ -1,19 +0,0 @@ -import os -from dotenv import load_dotenv -from mcs.upload.onchain_upload import OnchainUpload - -chain_name = "polygon.mumbai" - -def test_auto_upload(): - load_dotenv(".env_test") - private_key = os.getenv('private_key') - rpc_endpoint = os.getenv('rpc_endpoint') - api_key = os.getenv('api_key') - access_token = os.getenv('access_token') - - filepath = "/images/log_mcs.png" - parent_path = os.path.abspath(os.path.dirname(__file__)) - - up = OnchainUpload(chain_name, private_key, rpc_endpoint, api_key, access_token, parent_path+filepath) - hash = up.simple_upload(1) - return hash \ No newline at end of file