diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ea7f2ce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git* +**/*.pyc +.venv/ \ No newline at end of file diff --git a/chainRegistry.json b/chainRegistry.json new file mode 100644 index 0000000..f60e0c1 --- /dev/null +++ b/chainRegistry.json @@ -0,0 +1,111 @@ +[ + { + "id": "bitcoin", + "name": "Bitcoin", + "coinId": 0, + "symbol": "BTC", + "decimals": 8, + "blockchain": "Bitcoin", + "derivation": [ + { + "name": "segwit", + "path": "m/84'/0'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + }, + { + "name": "legacy", + "path": "m/44'/0'/0'/0/0", + "xpub": "xpub", + "xprv": "xprv" + }, + { + "name": "testnet", + "path": "m/84'/1'/0'/0/0", + "xpub": "zpub", + "xprv": "zprv" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1", + "p2pkhPrefix": 0, + "p2shPrefix": 5, + "hrp": "bc", + "publicKeyHasher": "sha256ripemd", + "base58Hasher": "sha256d", + "explorer": { + "url": "https://blockchair.com", + "txPath": "/bitcoin/transaction/", + "accountPath": "/bitcoin/address/", + "sampleTx": "0607f62530b68cfcc91c57a1702841dd399a899d0eecda8e31ecca3f52f01df2", + "sampleAccount": "17A16QmavnUfCW11DAApiJxp7ARnxN5pGX" + }, + "info": { + "url": "https://bitcoin.org", + "source": "https://github.com/trezor/blockbook", + "rpc": "", + "documentation": "https://github.com/trezor/blockbook/blob/master/docs/api.md" + }, + "logo": "https://raw.githubusercontent.com/Oxchanger/crypto-logo/5aed02c4c539c4161073b903590945733b0c747b/btc.svg" + }, + + { + "id": "ethereum", + "name": "Ethereum", + "coinId": 60, + "symbol": "ETH", + "decimals": 18, + "blockchain": "Ethereum", + "derivation": [ + { + "path": "m/44'/60'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "chainId": "1", + "addressHasher": "keccak256", + "explorer": { + "url": "https://etherscan.io", + "txPath": "/tx/", + "accountPath": "/address/", + "sampleTx": "0x9edaf0f7d9c6629c31bbf0471fc07d696c73b566b93783f7e25d8d5d2b62fa4f", + "sampleAccount": "0x5bb497e8d9fe26e92dd1be01e32076c8e024d167" + }, + "info": { + "url": "https://ethereum.org", + "source": "https://github.com/ethereum/go-ethereum", + "rpc": "https://mainnet.infura.io", + "documentation": "https://eth.wiki/json-rpc/API" + }, + "logo": "https://raw.githubusercontent.com/Oxchanger/crypto-logo/5aed02c4c539c4161073b903590945733b0c747b/eth.svg" + }, + + { + "id": "tron", + "name": "Tron", + "coinId": 195, + "symbol": "TRX", + "decimals": 6, + "blockchain": "Tron", + "derivation": [ + { + "path": "m/44'/195'/0'/0/0" + } + ], + "curve": "secp256k1", + "publicKeyType": "secp256k1Extended", + "explorer": { + "url": "https://tronscan.org", + "txPath": "/#/transaction/", + "accountPath": "/#/address/" + }, + "info": { + "url": "https://tron.network", + "source": "https://github.com/tronprotocol/java-tron", + "rpc": "https://api.trongrid.io", + "documentation": "https://developers.tron.network/docs/tron-wallet-rpc-api" + }, + "logo": "https://raw.githubusercontent.com/Oxchanger/crypto-logo/5aed02c4c539c4161073b903590945733b0c747b/trx.svg" + } +] diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..ce08255 --- /dev/null +++ b/dockerfile @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.11 + +WORKDIR /code + +COPY requirements.txt . + +RUN pip install --no-cache-dir --upgrade -r requirements.txt + +COPY . . + +EXPOSE 3100 + +CMD ["gunicorn", "main:app"] \ No newline at end of file diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 0000000..3b5188c --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,12 @@ +# Gunicorn configuration file +import multiprocessing + +max_requests = 1000 +max_requests_jitter = 50 + +log_file = "-" + +bind = "0.0.0.0:3100" + +worker_class = "uvicorn.workers.UvicornWorker" +workers = (multiprocessing.cpu_count() * 2) + 1 diff --git a/main.py b/main.py index 195f261..e52cd10 100644 --- a/main.py +++ b/main.py @@ -1,34 +1,243 @@ -from fastapi import FastAPI, Form, Request, status -from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse -from fastapi.staticfiles import StaticFiles -from fastapi.templating import Jinja2Templates +from typing import Union +from enum import Enum +import json +import os +import requests +from dotenv import load_dotenv + import uvicorn +from fastapi import FastAPI +from pydantic import BaseModel + + +class Parameters(BaseModel): + hexstring: str + maxfeerate: Union[str, None] = None + + +class BlockChainName(str, Enum): + BitcoinTest = "BitcoinTest" + Bitcoin = "Bitcoin" + Ethereum = "Ethereum" app = FastAPI() -app.mount("/static", StaticFiles(directory="static"), name="static") -templates = Jinja2Templates(directory="templates") - -@app.get("/", response_class=HTMLResponse) -async def index(request: Request): - print('Request for index page received') - return templates.TemplateResponse('index.html', {"request": request}) - -@app.get('/favicon.ico') -async def favicon(): - file_name = 'favicon.ico' - file_path = './static/' + file_name - return FileResponse(path=file_path, headers={'mimetype': 'image/vnd.microsoft.icon'}) - -@app.post('/hello', response_class=HTMLResponse) -async def hello(request: Request, name: str = Form(...)): - if name: - print('Request for hello page received with name=%s' % name) - return templates.TemplateResponse('hello.html', {"request": request, 'name':name}) - else: - print('Request for hello page received with no name or blank name -- redirecting') - return RedirectResponse(request.url_for("index"), status_code=status.HTTP_302_FOUND) +load_dotenv() + + +@app.post("/sendrawtransaction/") +async def send_raw_transaction(Parameters: Parameters): + url = os.getenv('BITCOIN_TEST_END_POINT_URL') + payload = json.dumps({ + "method": "sendrawtransaction", + "params": [ + Parameters.hexstring + ] + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + return json.loads(response.text) + + +@app.get("/chains") +async def get_chains(): + with open('./chainRegistry.json', 'r') as f: + + chains = json.load(f) + + return chains + + +@app.get("/get_address/{address}") +async def get_address_from_block_chain(address: str): + url = os.getenv('BITCOIN_TEST_END_POINT_URL') + + payload = json.dumps({ + "method": "bb_getaddress", + "params": [ + address, + { + "page": 1, + "size": 1000, + "fromHeight": 0, + "details": "txs" + } + ] + }) + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + return json.loads(response.text) + + +@app.get("/get_utxos/{address}") +async def get_utxos_from_block_chain(address: str): + url = os.getenv('BITCOIN_TEST_END_POINT_URL') + + payload = json.dumps({ + "method": "bb_getutxos", + "params": [ + address, + { + "confirmed": True + } + ] + }) + headers = { + 'Content-Type': 'application/json' + } + response = requests.request("POST", url, headers=headers, data=payload) + + return json.loads(response.text) + + +@app.get("/get_tx/{txid}") +async def get_tx_from_block_chain(txid: str): + url = os.getenv('BITCOIN_TEST_END_POINT_URL') + + payload = json.dumps({ + "method": "bb_gettx", + "params": [ + txid + ] + }) + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + + return json.loads(response.text) + + +@app.get("/decoderawtransaction/{hexstring}") +async def decoderawtransaction(hexstring: str): + url = os.getenv('BITCOIN_TEST_END_POINT_URL') + + payload = json.dumps({ + "method": "decoderawtransaction", + "params": [ + hexstring + ] + }) + + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + + return json.loads(response.text) + + +@app.get("/testmempoolaccept/{rawtxs}") +async def testmempoolaccept(rawtxs: str): + url = os.getenv('BITCOIN_TEST_END_POINT_URL') + + payload = json.dumps({ + "method": "testmempoolaccept", + "params": [ + rawtxs + ] + }) + + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + + return json.loads(response.text) + + +@app.get("/estimatesmartfee/{conf_target}") +async def estimate_smart_fee(conf_target: int): + url = os.getenv('BITCOIN_TEST_END_POINT_URL') + + payload = json.dumps({ + "method": "estimatesmartfee", + "params": [ + conf_target + ] + }) + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + + return json.loads(response.text) + + +@app.get("/wallet/{block_chain_name}/{xpub}") +async def get_wallet(block_chain_name: BlockChainName, xpub: str): + + wallet = await get_wallet_from_block_chain(block_chain_name, xpub) + + return wallet + + +async def get_wallet_from_block_chain(block_chain_name: str, xpub: str): + + block_chain_url = get_block_chain_url_end_point(block_chain_name) + + wallet_request_payload = get_wallet_request_payload( + block_chain_name, xpub) + + headers = {'Content-Type': 'application/json'} + + wallet_from_block_chain = requests.request( + "POST", block_chain_url, headers=headers, data=wallet_request_payload) + + wallet = add_the_required_key_value_pairs_to_the_JSON_object( + json.loads(wallet_from_block_chain.text)) + + return wallet + + +def get_block_chain_url_end_point(block_chain_name: str): + block_chain_endPoints = { + 'BitcoinTest': os.getenv('BITCOIN_TEST_END_POINT_URL'), + 'Bitcoin': os.getenv('BITCOIN_END_POINT_URL'), + 'Ethereum': os.getenv('ETHEREUM_END_POINT_URL') + } + return block_chain_endPoints[block_chain_name] + + +def get_wallet_request_payload(block_chain_name: str, xpub: str): + wallet_request_payloads = { + 'BitcoinTest': json.dumps({ + "method": "bb_getxpub", + "params": [ + f'pkh({xpub})', + { + "page": 1, + "size": 1000, + "fromHeight": 0, + "details": "txid" + } + ] + }), + 'Bitcoin': json.dumps({"method": "getblockchaininfo"}), + 'Ethereum': json.dumps({"method": "getblockchaininfo"}) + } + return wallet_request_payloads[block_chain_name] + + +def add_the_required_key_value_pairs_to_the_JSON_object(response_from_blockChain): + + response_from_blockChain['id'] = '1' + response_from_blockChain['name'] = 'BitcoinTest' + response_from_blockChain['symbol'] = 'BTC' + response_from_blockChain['logo'] = 'https://raw.githubusercontent.com/Oxchanger/crypto-logo/5aed02c4c539c4161073b903590945733b0c747b/btc.svg' + response_from_blockChain['explorerUrl'] = 'some_explorerUrl' + + return response_from_blockChain + if __name__ == '__main__': uvicorn.run('main:app', host='0.0.0.0', port=8000) - diff --git a/requirements.txt b/requirements.txt index e1766cf..cd68d13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,23 @@ uvicorn gunicorn jinja2 python-multipart +annotated-types==0.5.0 +anyio==3.7.1 +certifi==2023.7.22 +charset-normalizer==3.2.0 +click==8.1.7 +h11==0.14.0 +httptools==0.6.0 +idna==3.4 +pydantic==2.3.0 +pydantic_core==2.6.3 +python-dotenv==1.0.0 +PyYAML==6.0.1 +requests==2.31.0 +sniffio==1.3.0 +starlette==0.27.0 +typing_extensions==4.7.1 +urllib3==2.0.4 +uvloop==0.17.0 +watchfiles==0.20.0 +websockets==11.0.3