def block_height(): rpc = RpcConfig.get_rpc() if rpc is None: return ResponseObject.error(**out_data_missing) try: node_height = rpc.get_block_height().to_dict() except Exception as e: return ResponseObject.error(**rpc_service_error) return ResponseObject.success(data=node_height)
def get_secret(project_id, coin_name) -> (bool, object): project_secret = runtime.rsa_secret.get(project_id) if project_secret is None: return False, ResponseObject.error(**project_passphrase_miss) coin_secret = project_secret.get(coin_name) if coin_secret is None: return False, ResponseObject.error(**coin_passphrase_miss) secret = coin_secret.get('secret') crypto = coin_secret.get('crypto') if not all([secret, crypto]): return False, ResponseObject.error(**coin_passphrase_miss) return True, secret
def set_passphrase(project_id, coin_name, secret): project_coin = ProjectCoin.get_pro_coin_by_pid_cname(project_id, coin_name) if not project_coin: return False, ResponseObject.error(**sign_rsa_not_found) is_valid, result, passphrase, rpc = check_passphrase(project_coin, secret) if not is_valid: return result crypto = result coin_secret_map = {coin_name: {'secret': secret, 'crypto': crypto}} if runtime.rsa_secret.get(project_id): runtime.rsa_secret[project_id].update(coin_secret_map) else: runtime.rsa_secret[project_id] = coin_secret_map return ResponseObject.success(data=True)
def check_address(): """查询某地址是否归属我方钱包中 @@@ #### 签名 [v] 必须 #### args | args | nullable | type | default | remark | |--------|--------|--------|--------|--------| | address | false | string | | 查询地址 | #### return | args | nullable | type | remark | |--------|--------|--------|--------| | isSet | false | bool | 是否为我方地址 | - ##### json > "100.00" @@@ :return: """ _json = get_json(request) project_id = request.project_id if _json is not None: address = _json.get("address") if address is not None: return make_response(jsonify(is_mine_address(project_id, address))) result = ResponseObject.raise_args_error() return make_response(jsonify(result))
def get_balance(): """获取地址余额 查询某地址余额 @@@ #### 签名 [v] 必须 #### args | args | nullable | type | default | remark | |--------|--------|--------|--------|--------| | address | false | string | | 查询地址 | #### return | args | nullable | type | remark | |--------|--------|--------|--------| | balance | false | string | 余额 | - ##### json > "100.00" @@@ :return: """ _json = get_json(request) if _json is not None: address = _json.get("address") if address is not None: return make_response(jsonify(get_balance_by_address(address))) result = ResponseObject.raise_args_error() return make_response(jsonify(result))
def send_transaction(): """发送交易 向指定账户发送交易 发送交易之后不一定就会成功. 业务解决: 1. 等待用户确认 技术解决: 1. 需要使用 getTransactoon 确认 2. 等待推送 ******* 这里是重要, 一定要看 ******* 该接口包含的 actionId 字段为校验幂等字段. 该字段必须唯一, 且如果交易存在的情况下, 不会重复发送交易. ******* 这里是重要, 一定要看 ******* `该接口必须要在先设置密码后才能使用` @@@ #### 签名 [v] 必须 #### args 暂不需要入参 | args | nullable | type | default | remark | |--------|--------|--------|--------|--------| | actionId | false | string | | 执行码 | | sender | false | string | | 发送地址 | | receiver | false | string | | 接口地址 | | amount | false | string | | 发送金额 | | coin | true | string | Ethereum | 币种, 默认使用ETH ERC20 的 USDT, 当前该选项更改暂时无效 | | contract | true | string | null | 合约地址, 默认使用USDT, 若指定时指使用指定 | #### return | args | nullable | type | remark | |--------|--------|--------|--------| | txHash | false | string | 交易流水号 | - ##### json > "100.00" @@@ """ _json = get_json(request) project_id = request.project_id if _json is not None: action_id = _json.get("actionId") sender = _json.get("address") receiver = _json.get("receiver") amount = _json.get("amount") coin = _json.get("coin", 'Ethereum') contract = _json.get("contract") if None not in [action_id, sender, receiver, amount]: return make_response( jsonify( send_transaction_handle(project_id, action_id, sender, receiver, amount, coin, contract))) result = ResponseObject.raise_args_error() return make_response(jsonify(result))
def get_wallet_total_balance(): """目前该接口逻辑仅针对于 ETH USDT""" rpc = RpcConfig.get_rpc() if rpc is None: return ResponseObject.error(**out_data_missing) coin = Coin.get_erc20_usdt_coin() if coin is None: return ResponseObject.error(**coin_missing) try: balance = rpc.get_wallet_balance(contract=coin.contract) except Exception as e: return ResponseObject.error(**rpc_service_error) if balance or balance == 0: balance = safe_math.divided(balance, safe_math.e_calc(coin.decimal)) else: return ResponseObject.error(**balance_rpc_error) return ResponseObject.success(data=balance.to_eng_string())
def get_balance_by_address(address: str): """这里只能查单个地址的余额""" rpc = RpcConfig.get_rpc() if rpc is None: return ResponseObject.error(**out_data_missing) coin = Coin.get_erc20_usdt_coin() if coin is None: return ResponseObject.error(**coin_missing) try: balance_hex = rpc.get_balance(address, coin.contract) except Exception as e: return ResponseObject.error(**rpc_service_error) if balance_hex: balance = safe_math.divided(hex_to_int(balance_hex), safe_math.e_calc(coin.decimal)) else: return ResponseObject.error(**balance_rpc_error) return ResponseObject.success(data=balance.to_eng_string())
def check_passphrase(project_coin, secret) -> (bool, object, str, object): private_key = project_coin.ProjectCoin.hot_pk if private_key is None: return False, ResponseObject.error(**sign_rsa_not_found), None, None crypto = RsaCrypto() crypto.import_key(private_key) try: passphrase = crypto.decrypt(secret) except Exception as e: return False, ResponseObject.error(**sign_rsa_invalid), None, None rpc = RpcConfig.get_rpc() if rpc is None: return False, ResponseObject.error(**out_data_missing), None, None is_correct_passphrase = rpc.open_wallet( passphrase=passphrase, address=project_coin.ProjectCoin.hot_address) if not is_correct_passphrase: return False, ResponseObject.error(**passphrase_invalid), None, None return True, crypto, passphrase, rpc
def auth(): if app.config.get('OPEN_SIGN_AUTH') and any( [request.path.startswith(path) for path in app.config.get('SIGN_API')]): try: _auth = Auth(request) allow = _auth.check() except Exception as e: return make_response( jsonify(ResponseObject.raise_sign_exception()), 200) if isinstance(allow, dict): return make_response(jsonify(ResponseObject.error(**allow)), 200) else: if _auth.api_auth.project_id is None: return make_response( jsonify(ResponseObject.error(**sign_key_not_bind_project)), 200) request.project_id = _auth.api_auth.project_id if not app.config.get('OPEN_SIGN_AUTH'): # 为调试使用 request.project_id = 1
def create_address(project_id, coin_name, count): is_valid_secret, secret_result = get_secret(project_id, coin_name) if not is_valid_secret: return secret_result secret = secret_result project_coin = ProjectCoin.get_pro_coin_by_pid_cname(project_id, coin_name) if not project_coin: return ResponseObject.error(**sign_rsa_not_found) is_valid_pass, result, passphrase, rpc = check_passphrase( project_coin, secret) if not is_valid_pass: return result addresses = rpc.new_address(passphrase=passphrase, count=count) if not isinstance(addresses, (list, tuple)): addresses = [addresses] success_count = 0 addresses_add_list = [] for address in addresses: if address is not None: addresses_add_list.append({ "project_id": project_id, 'address': address, "coin_id": project_coin.ProjectCoin.coin_id, "address_type": AddressTypeEnum.DEPOSIT.value }) success_count += 1 runtime.project_address[address] = { "project_id": project_id, "coin_id": project_coin.ProjectCoin.coin_id, "coin_name": coin_name } Address.add_addresses(addresses_add_list) return ResponseObject.success(data=addresses)
def get_transaction(): """查询交易详情 @@@ #### 签名 [v] 必须 #### args | args | nullable | type | default | remark | |--------|--------|--------|--------|--------| | txHash | false | string | | 交易流水号 | #### return | args | nullable | type | remark | |--------|--------|--------|--------| | sender | false | string | 发送地址 | | receiver | false | string | 接口地址 | | txHash | false | string | 交易流水号 | | value | false | string | 交易金额 | | blockHeight | false | int | 所在高度 | | blockTime | false | int | 时间戳, 秒级 | | contract | true | string | 合约地址 | | isValid | false | bool | 是否有效 | | confirmNumber | false | int | 最新确认数 | - ##### json > {“sender”: "1392019302193029", "receiver": "1392019302193029", "txHash": "1392019302193029", "value": "100.00", "blockHeight": 10000, "blockTime": 10000, "contract": "1392019302193029", "isValid": true, "confirmNumber": 100 } @@@ :return: """ _json = get_json(request) if _json is not None: tx_hash = _json.get("txHash") if tx_hash is not None: return make_response(jsonify(get_tx_by_tx_hash(tx_hash))) result = ResponseObject.raise_args_error() return make_response(jsonify(result))
def new_address(): """生成新地址 生成新地址, 当前接口是同步响应返回, 所以该方法不建议一次调用超过10 V2 版本将升级为异步, 需要提供 callback 地址, 届时可生成较大数量地址 `该接口必须要在先设置密码后才能使用` @@@ #### 签名 [v] 必须 #### args 暂不需要入参 | args | nullable | type | default | remark | |--------|--------|--------|--------|--------| | count | false | int | 10 | 生成地址数量 | #### return | args | nullable | type | remark | |--------|--------|--------|--------| | addresses | false | array[string] | 地址, ['新地址'...] | - ##### json > ["abc", "bbc"] @@@ """ _json = get_json(request) project_id = request.project_id if _json is not None: coin = _json.get("coin", 'Ethereum') count = _json.get("count", 10) if count is not None and isinstance(count, int): return make_response( jsonify(create_address(project_id, coin, count))) result = ResponseObject.raise_args_error() return make_response(jsonify(result))
def set_wallet_passphrase(): """设置钱包密码 该字段是加密传输, 加密算法请见... @@@ #### 签名 [v] 必须 #### args | args | nullable | type | default | remark | |--------|--------|--------|--------|--------| | secret | false | string | | 密钥 | | coin | true | string | Ethereum | 币种, 默认使用ETH ERC20 的 USDT, 当前该选项更改暂时无效 | #### return | args | nullable | type | remark | |--------|--------|--------|--------| | isSet | false | bool | 是否设置成功 | - ##### json > true @@@ :return: """ _json = get_json(request) project_id = request.project_id if _json is not None: secret = _json.get("secret") coin = _json.get("coin", 'Ethereum') if secret is not None or coin is None: return make_response( jsonify(set_passphrase(project_id, coin, secret))) result = ResponseObject.raise_args_error() return make_response(jsonify(result))
def add_order_id(): """添加订单号 ********* 这个地方逻辑有问题, 当前无法处理 ******** 外部给预某个订单号, 给外界推送匹配订单号 @@@ #### 签名 [v] 必须 #### args | args | nullable | type | default | remark | |--------|--------|--------|--------|--------| | orderId | false | string | | 订单号 | | address | false | string | | 订单绑定的地址 | #### return | args | nullable | type | remark | |--------|--------|--------|--------| | isSet | false | bool | 是否设置成功 | - ##### json > true @@@ :return: """ _json = get_json(request) project_id = request.project_id if _json is not None: order_id = _json.get("orderId") address = _json.get("address") if order_id is not None or address is None: return make_response( jsonify(set_deposit_order_id(project_id, order_id, address))) result = ResponseObject.raise_args_error() return make_response(jsonify(result))
def send_transaction_handle(project_id, action_id, sender, receiver, amount, coin_name, contract): """ 该交易暂时只支持约定的 USDT 币种, 其他不支持, coin name 固定为 Ethereum, 其他不受理 合约暂时也同样不支持自定义, 若合约不是 USDT 合约时, 暂不受理. """ # 获取币种信息 if contract: coin = Coin.get_coin(name=coin_name, contract=contract) else: # 默认使用 USDT, 但币种依然需要校验 # coin = Coin.get_coin(name=coin_name, symbol='USDT', is_master=0) coin = Coin.get_erc20_usdt_coin() if coin is None: return ResponseObject.error(**coin_missing) if contract is None: contract = coin.contract # 检查幂等 tx = ProjectOrder.get_tx_by_action_or_hash(action_id=action_id) if tx: return ResponseObject.success(data=tx.tx_hash) # 获取项目方设置的数据, 当前只有一个项目, 所以暂时不涉及此项, 如果以后我要处理多项目时再根据需求改造. project_coin = ProjectCoin.get_pro_coin_by_pid_cname(project_id, coin_name) if not project_coin: return False, ResponseObject.error(**sign_rsa_not_found) # 校验密码 is_valid_secret, secret_result = get_secret(project_id, coin_name) if not is_valid_secret: return secret_result secret = secret_result is_valid, result, passphrase, rpc = check_passphrase(project_coin, secret) if not is_valid: return result # TODO 这里是硬性限制 USDT 的逻辑, 而且是强制 ERC20 if coin_name != 'Ethereum': return ResponseObject.error(**not_support_coin) if coin.symbol != 'USDT': return ResponseObject.error(**not_support_coin) amount = int(safe_math.multi(amount, safe_math.e_calc(coin.decimal))) # TODO 因为上面的限制, 所以下面逻辑优化, 可以不判断一些复杂的东西 if project_coin.ProjectCoin.gas == '0': gas = rpc.get_smart_fee(contract=contract) else: gas = project_coin.ProjectCoin.gas if project_coin.ProjectCoin.gas_price == '0': gas_price = rpc.gas_price() else: gas_price = project_coin.ProjectCoin.gas_price if gas is None: return ResponseObject.error(**fee_args_error) if gas_price is None: return ResponseObject.error(**fee_args_error) try: tx_hash = rpc.send_transaction(sender=sender, receiver=receiver, value=amount, passphrase=passphrase, gas=gas, gas_price=gas_price, contract=contract) except JsonRpcError as e: error = tx_send_error error['msg'] = e.message return ResponseObject.error(**error) if tx_hash: ProjectOrder.add(project_id, action_id, coin.id, tx_hash, amount, sender, receiver, gas, gas_price, contract=contract) return ResponseObject.success(data=tx_hash) return ResponseObject.error(**tx_send_error)
def is_mine_address(project_id, address): address = Address.query.filter_by(project_id=project_id, address=address).first() if not address: return ResponseObject.success(data=False) return ResponseObject.success(data=True)
def err40x(e): return make_response(jsonify(ResponseObject.raise_404_error()), 404)
def err50x(e): return make_response(jsonify(ResponseObject.raise_exception()), e.code)
def get_tx_by_tx_hash(tx_hash: str): rpc = RpcConfig.get_rpc() if rpc is None: return ResponseObject.error(**out_data_missing) chain_info = rpc.get_block_height() tx = Transaction.get_tx_coin_by_tx_hash(tx_hash=tx_hash) if tx: return ResponseObject.success( data={ "sender": tx.Transaction.sender, "receiver": tx.Transaction.receiver, "txHash": tx.Transaction.tx_hash, "value": safe_math.divided(tx.Transaction.amount, safe_math.e_calc( tx.Coin.decimal)).to_eng_string(), "blockHeight": tx.Transaction.height, "blockTime": tx.Transaction.block_time, "contract": tx.Transaction.contract, "isValid": True if tx.Transaction.status == 1 else False, "confirmNumber": chain_info.highest_height - tx.Transaction.height if tx.Transaction.height > 0 else 0 }) else: tx_origin, receipt_origin = rpc.get_transaction_by_hash(tx_hash) if tx_origin and receipt_origin: tx, receipt = EthereumResolver.resolver_transaction( tx_origin), EthereumResolver.resolver_receipt(receipt_origin) block_info = rpc.get_block_by_number(int_to_hex(tx.block_height), False) if not block_info: return ResponseObject.error(**rpc_block_not_found) block = EthereumResolver.resolver_block(block_info, False) if tx.contract is not None: coin = Coin.get_erc20_usdt_coin() else: coin = Coin.get_coin(name='Ethereum') if coin is None: return ResponseObject.error(**coin_missing) return ResponseObject.success( data={ "sender": tx.sender, "receiver": tx.receiver, "txHash": tx.tx_hash, "value": safe_math.divided(tx.value, safe_math.e_calc( coin.decimal)).to_eng_string(), "blockHeight": tx.block_height, "blockTime": block.timestamp, "contract": tx.contract, "isValid": True if receipt.status == 1 else False, "confirmNumber": chain_info.highest_height - tx.block_height }) return ResponseObject.error(**tx_miss)