Main Code

This commit is contained in:
Bob 2023-03-10 12:46:21 +02:00
parent 969a44ff0c
commit 0be3c6b5f4
46 changed files with 1300 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# fastapi
# yes

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
__pycache__/main.cpython-38.pyc Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
__pycache__/models.cpython-38.pyc Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
__pycache__/rpcs.cpython-38.pyc Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

88
config.py Executable file
View File

@ -0,0 +1,88 @@
###
# RPC Lists
rpcs = {
'BTC': {
'host': '172.16.0.2',
'port': 8332,
'user': '55c44311a1e1708ec1afbf9949c96d08',
'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF'
},
'BTCT': {
'host': '172.16.2.2',
'port': 18332,
'user': '55c44311a1e1708ec1afbf9949c96d08',
'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF'
},
'LTC': {
'host': '172.16.1.2',
'port': 9332,
'user': '55c44311a1e1708ec1afbf9949c96d08',
'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF'
},
'BCH': {
'host': '172.16.3.2',
'port': 8552,
'user': '55c44311a1e1708ec1afbf9949c96d08',
'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF'
},
'ZEC': {
'host': '172.16.5.2',
'port': 8552,
'user': '55c44311a1e1708ec1afbf9949c96d08',
'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF'
},
'DOGE': {
'host': '172.16.6.2',
'port': 25555,
'user': '55c44311a1e1708ec1afbf9949c96d08',
'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF'
},
'XMR': {
'host': 'http://172.16.4.3:18083/json_rpc',
'user': '55c44311a1e1708ec1afbf9949c96d08',
'pass': 'lzD8NH4sexdTHnRJ1BxZfBvUxZHlvmW2bwDsF'
},
}
DB = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'pass': 'xegh3kAJyDLaRu'
}
fee = {
"REGULAR": {
'BTC': 0.02,
'BTCLN': 0.02,
'BTCT': 0.005,
'LTC': 0.02,
'BCH': 0.02,
'DCR': 0.02,
'DASH': 0.02,
'XRP': 0.02,
'DOGE': 0.02,
'XMR': 0.06,
'ZEC': 0.06,
'USDT': 0.02,
'ETH': 0.06,
'XHV': 0.06
},
"MERCHANT": {
'BTC': 0.01,
'BTCLN': 0.01,
'BTCT': 0.005,
'LTC': 0.01,
'BCH': 0.01,
'DCR': 0.01,
'DASH': 0.01,
'XRP': 0.01,
'DOGE': 0.01,
'XMR': 0.01,
'ZEC': 0.01,
'USDT': 0.01,
'ETH': 0.01,
'XHV': 0.01
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dependencies/__pycache__/crypto.cpython-38.pyc vendored Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

129
dependencies/bchconvert.py vendored Executable file
View File

@ -0,0 +1,129 @@
from dependencies.crypto import *
from base58 import b58decode_check, b58encode_check
import sys
class InvalidAddress(Exception):
pass
class Address:
VERSION_MAP = {
'legacy': [
('P2SH', 5, False),
('P2PKH', 0, False),
('P2SH-TESTNET', 196, True),
('P2PKH-TESTNET', 111, True)
],
'cash': [
('P2SH', 8, False),
('P2PKH', 0, False),
('P2SH-TESTNET', 8, True),
('P2PKH-TESTNET', 0, True)
]
}
MAINNET_PREFIX = 'bitcoincash'
TESTNET_PREFIX = 'bchtest'
def __init__(self, version, payload, prefix=None):
self.version = version
self.payload = payload
if prefix:
self.prefix = prefix
else:
if Address._address_type('cash', self.version)[2]:
self.prefix = self.TESTNET_PREFIX
else:
self.prefix = self.MAINNET_PREFIX
def __str__(self):
return 'version: {}\npayload: {}\nprefix: {}'.format(self.version, self.payload, self.prefix)
def legacy_address(self):
version_int = Address._address_type('legacy', self.version)[1]
return b58encode_check(Address.code_list_to_string([version_int] + self.payload))
def cash_address(self):
version_int = Address._address_type('cash', self.version)[1]
payload = [version_int] + self.payload
payload = convertbits(payload, 8, 5)
checksum = calculate_checksum(self.prefix, payload)
return self.prefix + ':' + b32encode(payload + checksum)
@staticmethod
def code_list_to_string(code_list):
if sys.version_info > (3, 0):
output = bytes()
for code in code_list:
output += bytes([code])
else:
output = ''
for code in code_list:
output += chr(code)
return output
@staticmethod
def _address_type(address_type, version):
for mapping in Address.VERSION_MAP[address_type]:
if mapping[0] == version or mapping[1] == version:
return mapping
raise InvalidAddress('Could not determine address version')
@staticmethod
def from_string(address_string):
try:
address_string = str(address_string)
except Exception:
raise InvalidAddress('Expected string as input')
if ':' not in address_string:
return Address._legacy_string(address_string)
else:
return Address._cash_string(address_string)
@staticmethod
def _legacy_string(address_string):
try:
decoded = bytearray(b58decode_check(address_string))
except ValueError:
raise InvalidAddress('Could not decode legacy address')
version = Address._address_type('legacy', decoded[0])[0]
payload = list()
for letter in decoded[1:]:
payload.append(letter)
return Address(version, payload)
@staticmethod
def _cash_string(address_string):
if address_string.upper() != address_string and address_string.lower() != address_string:
raise InvalidAddress('Cash address contains uppercase and lowercase characters')
address_string = address_string.lower()
colon_count = address_string.count(':')
if colon_count == 0:
address_string = Address.MAINNET_PREFIX + ':' + address_string
elif colon_count > 1:
raise InvalidAddress('Cash address contains more than one colon character')
prefix, base32string = address_string.split(':')
decoded = b32decode(base32string)
if not verify_checksum(prefix, decoded):
raise InvalidAddress('Bad cash address checksum')
converted = convertbits(decoded, 5, 8)
version = Address._address_type('cash', converted[0])[0]
if prefix == Address.TESTNET_PREFIX:
version += '-TESTNET'
payload = converted[1:-6]
return Address(version, payload, prefix)
def to_cash_address(address):
return Address.from_string(address).cash_address()
def to_legacy_address(address):
return Address.from_string(address).legacy_address()
def is_valid(address):
try:
Address.from_string(address)
return True
except InvalidAddress:
return False

70
dependencies/crypto.py vendored Executable file
View File

@ -0,0 +1,70 @@
CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
def polymod(values):
chk = 1
generator = [
(0x01, 0x98f2bc8e61),
(0x02, 0x79b76d99e2),
(0x04, 0xf33e5fb3c4),
(0x08, 0xae2eabe2a8),
(0x10, 0x1e4f43e470)]
for value in values:
top = chk >> 35
chk = ((chk & 0x07ffffffff) << 5) ^ value
for i in generator:
if top & i[0] != 0:
chk ^= i[1]
return chk ^ 1
def prefix_expand(prefix):
return [ord(x) & 0x1f for x in prefix] + [0]
def calculate_checksum(prefix, payload):
poly = polymod(prefix_expand(prefix) + payload + [0, 0, 0, 0, 0, 0, 0, 0])
out = list()
for i in range(8):
out.append((poly >> 5 * (7 - i)) & 0x1f)
return out
def verify_checksum(prefix, payload):
return polymod(prefix_expand(prefix) + payload) == 0
def b32decode(inputs):
out = list()
for letter in inputs:
out.append(CHARSET.find(letter))
return out
def b32encode(inputs):
out = ''
for char_code in inputs:
out += CHARSET[char_code]
return out
def convertbits(data, frombits, tobits, pad=True):
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret

185
dependencies/moneropy.py vendored Executable file
View File

@ -0,0 +1,185 @@
# MoneroPy - A python toolbox for Monero
# Copyright (C) 2016 The MoneroPy Developers.
#
# MoneroPy is released under the BSD 3-Clause license. Use and redistribution of
# this software is subject to the license terms in the LICENSE file found in the
# top-level directory of this distribution.
#
# Modified by emesik and rooterkyberian:
# + optimized
# + proper exceptions instead of returning errors as results
__alphabet = [
ord(s) for s in "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
]
__b58base = 58
__UINT64MAX = 2 ** 64
__encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]
__fullBlockSize = 8
__fullEncodedBlockSize = 11
def _hexToBin(hex_):
if len(hex_) % 2 != 0:
raise ValueError("Hex string has invalid length: %d" % len(hex_))
return [int(hex_[i : i + 2], 16) for i in range(0, len(hex_), 2)]
def _binToHex(bin_):
return "".join("%02x" % int(b) for b in bin_)
def _uint8be_to_64(data):
if not (1 <= len(data) <= 8):
raise ValueError("Invalid input length: %d" % len(data))
res = 0
for b in data:
res = res << 8 | b
return res
def _uint64_to_8be(num, size):
if size < 1 or size > 8:
raise ValueError("Invalid input length: %d" % size)
res = [0] * size
twopow8 = 2 ** 8
for i in range(size - 1, -1, -1):
res[i] = num % twopow8
num = num // twopow8
return res
def encode_block(data, buf, index):
l_data = len(data)
if l_data < 1 or l_data > __fullEncodedBlockSize:
raise ValueError("Invalid block length: %d" % l_data)
num = _uint8be_to_64(data)
i = __encodedBlockSizes[l_data] - 1
while num > 0:
remainder = num % __b58base
num = num // __b58base
buf[index + i] = __alphabet[remainder]
i -= 1
return buf
def encode(hex):
"""Encode hexadecimal string as base58 (ex: encoding a Monero address)."""
data = _hexToBin(hex)
l_data = len(data)
if l_data == 0:
return ""
full_block_count = l_data // __fullBlockSize
last_block_size = l_data % __fullBlockSize
res_size = (
full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size]
)
res = bytearray([__alphabet[0]] * res_size)
for i in range(full_block_count):
res = encode_block(
data[(i * __fullBlockSize) : (i * __fullBlockSize + __fullBlockSize)],
res,
i * __fullEncodedBlockSize,
)
if last_block_size > 0:
res = encode_block(
data[
(full_block_count * __fullBlockSize) : (
full_block_count * __fullBlockSize + last_block_size
)
],
res,
full_block_count * __fullEncodedBlockSize,
)
return bytes(res).decode("ascii")
def decode_block(data, buf, index):
l_data = len(data)
if l_data < 1 or l_data > __fullEncodedBlockSize:
raise ValueError("Invalid block length: %d" % l_data)
res_size = __encodedBlockSizes.index(l_data)
if res_size <= 0:
raise ValueError("Invalid block size: %d" % res_size)
res_num = 0
order = 1
for i in range(l_data - 1, -1, -1):
digit = __alphabet.index(data[i])
if digit < 0:
raise ValueError("Invalid symbol: %s" % data[i])
product = order * digit + res_num
if product > __UINT64MAX:
raise ValueError(
"Overflow: %d * %d + %d = %d" % (order, digit, res_num, product)
)
res_num = product
order = order * __b58base
if res_size < __fullBlockSize and 2 ** (8 * res_size) <= res_num:
raise ValueError("Overflow: %d doesn't fit in %d bit(s)" % (res_num, res_size))
tmp_buf = _uint64_to_8be(res_num, res_size)
buf[index : index + len(tmp_buf)] = tmp_buf
return buf
def decode(enc):
"""Decode a base58 string (ex: a Monero address) into hexidecimal form."""
enc = bytearray(enc, encoding="ascii")
l_enc = len(enc)
if l_enc == 0:
return ""
full_block_count = l_enc // __fullEncodedBlockSize
last_block_size = l_enc % __fullEncodedBlockSize
try:
last_block_decoded_size = __encodedBlockSizes.index(last_block_size)
except ValueError:
raise ValueError("Invalid encoded length: %d" % l_enc)
data_size = full_block_count * __fullBlockSize + last_block_decoded_size
data = bytearray(data_size)
for i in range(full_block_count):
data = decode_block(
enc[
(i * __fullEncodedBlockSize) : (
i * __fullEncodedBlockSize + __fullEncodedBlockSize
)
],
data,
i * __fullBlockSize,
)
if last_block_size > 0:
data = decode_block(
enc[
(full_block_count * __fullEncodedBlockSize) : (
full_block_count * __fullEncodedBlockSize + last_block_size
)
],
data,
full_block_count * __fullBlockSize,
)
return _binToHex(data)

139
dependencies/segwit_addr.py vendored Executable file
View File

@ -0,0 +1,139 @@
# Copyright (c) 2017, 2020 Pieter Wuille
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""Reference implementation for Bech32/Bech32m and segwit addresses."""
from enum import Enum
class Encoding(Enum):
"""Enumeration type to list the various supported encodings."""
BECH32 = 1
BECH32M = 2
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
BECH32M_CONST = 0x2bc830a3
def bech32_polymod(values):
"""Internal function that computes the Bech32 checksum."""
generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for value in values:
top = chk >> 25
chk = (chk & 0x1ffffff) << 5 ^ value
for i in range(5):
chk ^= generator[i] if ((top >> i) & 1) else 0
return chk
def bech32_hrp_expand(hrp):
"""Expand the HRP into values for checksum computation."""
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
def bech32_verify_checksum(hrp, data):
"""Verify a checksum given HRP and converted data characters."""
const = bech32_polymod(bech32_hrp_expand(hrp) + data)
if const == 1:
return Encoding.BECH32
if const == BECH32M_CONST:
return Encoding.BECH32M
return None
def bech32_create_checksum(hrp, data, spec):
"""Compute the checksum values given HRP and data."""
values = bech32_hrp_expand(hrp) + data
const = BECH32M_CONST if spec == Encoding.BECH32M else 1
polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
def bech32_encode(hrp, data, spec):
"""Compute a Bech32 string given HRP and data values."""
combined = data + bech32_create_checksum(hrp, data, spec)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
def bech32_decode(bech):
"""Validate a Bech32/Bech32m string, and determine HRP and data."""
if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
(bech.lower() != bech and bech.upper() != bech)):
return (None, None, None)
bech = bech.lower()
pos = bech.rfind('1')
if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
return (None, None, None)
if not all(x in CHARSET for x in bech[pos+1:]):
return (None, None, None)
hrp = bech[:pos]
data = [CHARSET.find(x) for x in bech[pos+1:]]
spec = bech32_verify_checksum(hrp, data)
if spec is None:
return (None, None, None)
return (hrp, data[:-6], spec)
def convertbits(data, frombits, tobits, pad=True):
"""General power-of-2 base conversion."""
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret
def decode(hrp, addr):
"""Decode a segwit address."""
hrpgot, data, spec = bech32_decode(addr)
if hrpgot != hrp:
return (None, None)
decoded = convertbits(data[1:], 5, 8, False)
if decoded is None or len(decoded) < 2 or len(decoded) > 40:
return (None, None)
if data[0] > 16:
return (None, None)
if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
return (None, None)
if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M:
return (None, None)
return (data[0], decoded)
def encode(hrp, witver, witprog):
"""Encode a segwit address."""
spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M
ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
if decode(hrp, ret) == (None, None):
return None
return ret

58
functions.py Executable file
View File

@ -0,0 +1,58 @@
import dependencies.segwit_addr as segwit_addr
import dependencies.bchconvert as bchconvert
import dependencies.moneropy as moneropy
import base58, hashlib, binascii
import socket
def decodeBase58(address):
decoded = base58.b58decode(address).hex()
prefixAndHash = decoded[:len(decoded)-8]
checksum = decoded[len(decoded)-8:]
hash = prefixAndHash
for _ in range(1,3):
hash = hashlib.sha256(binascii.unhexlify(hash)).hexdigest()
if(checksum == hash[:8]):
return True
return False
def decodeMonero(address):
# decode monero address
if len(address) == 95 or len(address) == 106:
# Decode the address from Base58
decoded = moneropy.decode(address)
# Verify address type byte
if decoded[0:2] not in ["12", "2a", "13"]:
return False
# Verify checksum
# later
return True
return False
def checksumCheck(method, address):
match method.lower():
case 'btc':
return decodeBase58(address) if address[0] == '1' or address[0] == '3' else True if address[0:3] == 'bc1' and segwit_addr.decode("bc", address)[0] != None else False
case 'ltc':
return decodeBase58(address) if address[0] == '3' or address[0] == 'M' or address[0] == 'L' else True if address[0:4] == 'ltc1' and segwit_addr.decode("ltc", address)[0] != None else False
case 'bch':
return bchconvert.is_valid(address) if address[0] == '1' else True if bchconvert.is_valid('bitcoincash:'+address) == True else False
case 'zec':
return decodeBase58(address) if address[0] == 't' or address[0] == 'z' else False
case 'xmr':
#needs new function to check if address is valid, a decoder maybe
return decodeMonero(address)
case _:
return False
def validDns(d):
try:
a = socket.gethostbyname(d)
except:
return False
# ip validation
if a.split('.')[0] in ['127', '0'] or '.'.join([a.split('.')[0], a.split('.')[1]]) == '192.168' or a in ['1.1.1.1','2.2.2.2', '3.3.3.3']:
return False
else:
return True

139
main.py Executable file
View File

@ -0,0 +1,139 @@
from typing import Optional, Union
from urllib.parse import urlparse
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import time, socket
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import QueuePool
import uvicorn
#local
from rpcs import RPCHost, RPCXMR
from models import Return
from meta import Queue
from functions import checksumCheck, validDns
import config
class ErrorException(Exception):
def __init__(self, code: str, status: str, status_message: str):
self.status = status
self.code = code
self.status_message = status_message
app = FastAPI()
@app.get("/")
def root():
return
@app.exception_handler(ErrorException)
def unicorn_exception_handler(request: Request, exc: ErrorException):
return JSONResponse(
status_code=exc.code,
content={
"status": f"{exc.status}",
"message": f"{exc.status_message}"
},
)
@app.get("/api/receive")
def receive(method: str, address: str, callback: Union[str, None] = None):
if not method:
raise ErrorException(code=422,status="error",status_message='Empty method used')
if not address:
raise ErrorException(code=422,status="error",status_message='Empty destination address')
if method.lower() not in ['btc', 'btct', 'ltc', 'doge', 'zec', 'bch', 'xmr']:
raise ErrorException(code=422,status="error",status_message='Invalid method used')
if checksumCheck(method.lower(), address) == False:
raise ErrorException(code=422,status="error",status_message='Invalid Destination Address')
if callback:
try:
data = urlparse(callback)
#scheme validation
if data.scheme == 'http' or data.scheme == 'https':
#domain validation
if validDns(data.netloc) != True:
raise ErrorException(code=422,status="error",status_message='Invalid callback: domain name does not resolve')
else:
raise ErrorException(code=422,status="error",status_message='Invalid callback: wrong url scheme, we accept http or https only')
except:
callback = 'None'
callback_req_n = 0
else:
callback = 'None'
callback_req_n = 1
## RPC connection to Demons
match method.upper():
case 'BTC' | 'LTC' | 'ZEC' | 'BCH' | 'BTCT' | 'DOGE':
rpc = RPCHost("http://%s:%s@%s:%s" % (config.rpcs[method.upper()]['user'], config.rpcs[method.upper()]['pass'], config.rpcs[method.upper()]['host'], config.rpcs[method.upper()]['port']))
case 'XMR':
rpc = RPCXMR(config.rpcs[method.upper()]['host'], config.rpcs[method.upper()]['user'], config.rpcs[method.upper()]['pass'])
case _:
raise ErrorException(code=422,status="error",status_message='RPC undergoing maintenance')
## checking if wallet is loaded
# wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,True,True,False)
try:
match method.upper():
case 'XMR':
wallet = rpc.call('create_address',{"account_index": 0})
wallet = wallet['address']
case 'LTC' | 'ZEC' | 'DOGE':
#wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,False,True)
wallet = rpc.call('getnewaddress')
case 'BTC' | 'BTCT':
#wallet = rpc.call('createwallet','test',False,False,'exdTHnRJ1BxZfBvU',False,True,True,False)
wallet = rpc.call('getnewaddress','', 'p2sh-segwit')
case 'BCH':
wallet = rpc.call('getnewaddress')
wallet = wallet.replace('bitcoincash:', '')
case _:
## notify admin about the error
raise ErrorException(code=422, status="error", status_message='RPC unavailable')
except Exception as error:
## notify admin about the error
# Failed to connect for remote procedure call.
raise ErrorException(code=422, status="error", status_message=error)
## SQL Connection with SQLAlchemy
engine = create_engine(
"mysql+pymysql://%s:%s@%s:%s/dev" % (config.DB['user'], config.DB['pass'], config.DB['host'], config.DB['port']),
poolclass=QueuePool,
#echo=True, #debug sqlalchemy
pool_size=4,
max_overflow=0,
pool_timeout=15, # Wait 15s for a connection
)
Session = sessionmaker(bind = engine)
session = Session()
if wallet:
try:
q = Queue(txhash = 'None', time = int(time.time()), account = method.upper(), fee = config.fee['REGULAR'][method.upper()], ready = 0, confirmations =0, callbackurl = callback, generated_address = wallet, destination = address, balance_received = '0.00000000', callback_req = callback_req_n, ip = socket.gethostbyname(socket.gethostname()) , hostname = socket.gethostname(), merchantId = 'None', dateTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
session.add(q)
session.commit()
session.close()
except Exception as error:
## notify admin about the error
raise ErrorException(code=422,status="error",status_message='Invalid response from dbServer')
else:
## notify admin about the error
raise ErrorException(code=422,status="error",status_message='Invalid response from rpcServer')
return Return(
address = wallet,
destination = address,
callback_url = callback,
fee = config.fee['REGULAR'][method.upper()],
status = 'success'
)
# Run
if __name__ == '__main__':
uvicorn.run('main:app', host='0.0.0.0')
#workers=4 (doesn't work with reload)

24
meta.py Normal file
View File

@ -0,0 +1,24 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
Base = declarative_base()
class Queue(Base):
__tablename__ = 'queue'
id = Column(Integer, primary_key = True)
txhash = Column(String)
time = Column(String)
account = Column(String)
fee = Column(String)
ready = Column(String)
confirmations = Column(String)
callbackurl = Column(String)
generated_address = Column(String)
destination = Column(String)
balance_received = Column(String)
callback_req = Column(String)
ip = Column(String)
hostname = Column(String)
merchantId = Column(String)
dateTime = Column(String)

15
models.py Executable file
View File

@ -0,0 +1,15 @@
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel
class Receive(BaseModel):
method: str
address: str
callback: Optional[str]
class Return(BaseModel):
address: str
destination: str
callback_url: Optional[str]
fee: Decimal
status: str

18
requirements.txt Normal file
View File

@ -0,0 +1,18 @@
anyio==3.6.2
base58==2.1.1
certifi==2022.12.7
charset-normalizer==3.0.1
click==8.1.3
fastapi==0.92.0
greenlet==2.0.1
h11==0.14.0
idna==3.4
pydantic==1.10.5
PyMySQL==1.0.2
requests==2.28.2
sniffio==1.3.0
SQLAlchemy==2.0.4
starlette==0.25.0
typing_extensions==4.5.0
urllib3==1.26.14
uvicorn==0.20.0

66
rpcs.py Executable file
View File

@ -0,0 +1,66 @@
import time, json, requests
from requests.auth import HTTPDigestAuth
class RPCHost(object):
def __init__(self, url):
self._session = requests.Session()
self._url = url
self._headers = {'content-type': 'application/json'}
def call(self, rpcMethod, *params):
payload = json.dumps({"method": rpcMethod, "params": list(params), "jsonrpc": "2.0"})
tries = 3
hadConnectionFailures = False
while True:
try:
response = self._session.post(self._url, headers=self._headers, data=payload, timeout=15)
except requests.exceptions.ConnectionError:
tries -= 1
if tries == 0:
raise Exception('Failed to connect for remote procedure call.')
hadFailedConnections = True
print("Couldn't connect for remote procedure call, will sleep for two seconds and then try again ({} more tries)".format(tries))
#time.sleep(2)
else:
if hadConnectionFailures:
print('Connected for remote procedure call after retry.')
break
if not response.status_code in (200, 500):
raise Exception('RPC connection failure: ' + str(response.status_code) + ' ' + response.reason)
responseJSON = response.json()
if 'error' in responseJSON and responseJSON['error'] != None:
raise Exception('Error in RPC call: ' + str(responseJSON['error']))
return responseJSON['result']
# class
class RPCXMR(object):
def __init__(self, url, user, password):
self._session = requests.Session()
self._url = url
self._user = user
self._pass = password
self._headers = {}
def call(self, rpcMethod, params):
payload = json.dumps({"method": rpcMethod, "params": params, "jsonrpc": "2.0"})
tries = 3
hadConnectionFailures = False
while True:
try:
response = self._session.post(self._url, headers=self._headers, data=payload, auth=HTTPDigestAuth(self._user, self._pass), timeout=15)
except requests.exceptions.ConnectionError:
tries -= 1
if tries == 0:
raise Exception('Failed to connect for remote procedure call.')
hadFailedConnections = True
print("Couldn't connect for remote procedure call, will sleep for two seconds and then try again ({} more tries)".format(tries))
#time.sleep(2)
else:
if hadConnectionFailures:
print('Connected for remote procedure call after retry.')
break
if not response.status_code in (200, 500):
raise Exception('RPC connection failure: ' + str(response.status_code) + ' ' + response.reason)
responseJSON = response.json()
if 'error' in responseJSON and responseJSON['error'] != None:
raise Exception('Error in RPC call: ' + str(responseJSON['error']))
return responseJSON['result']

183
scratch_file Normal file
View File

@ -0,0 +1,183 @@
'''
def checksumCheck(method, address):
if method == 'btc':
if address[0] == '1' or address[0] == '3':
if decodeBase58(address):
return True
return False
elif address[0:3] == 'bc1':
witver = segwit_addr.decode("bc", address)
if witver[0] == None:
return False
return True
else:
return False
elif method == 'ltc':
if address[0] == '3' or address[0] == 'M' or address[0] == 'L':
if decodeBase58(address):
return True
return False
elif address[0:4] == 'ltc1':
witver = segwit_addr.decode("ltc", address)
if witver[0] != None:
return True
return False
else:
return False
elif method == 'bch':
if address[0] == '1':
if bchconvert.is_valid(address) == True:
return True
return False
elif bchconvert.is_valid('bitcoincash:'+address) == True:
return True
return False
elif method == 'zec':
if address[0] == 't' or address[0] == 'z':
if decodeBase58(address):
return True
return False
else:
return True
elif method == 'xmr':
length = len(address)
if length == 95:
checksum = moneropy.decode(address)
start = checksum[0:2].lower()
if start == '12' or start == '2a':
return True
return False
elif length == 106:
checksum = moneropy.decode(address)
start = checksum[0:2].lower()
if start == '13':
return True
return False
else:
return False
else:
return False
'''
'''
k = sha3.keccak_256()
print("First 65: " + str(address_type[:-65]))
print(hash64)
k.update(hash64)
a = k.hexdigest()
print("sha3 digest: " + a)
print("base58 decoded: " + address_type)
first4 = a[:-4]
last = address_type[-4:]
'''
return start
'''
valid = re.compile(r"^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$")
if valid.match(address) is None:
return False
else:
return True
else:
return "END"
'''
def validDns(d):
b = False
try:
a = socket.gethostbyname(d)
b = True
except:
return False
# ip validation
if b == True:
if a.split('.')[0] in ['127', '0']:
return False
if '.'.join([a.split('.')[0], a.split('.')[1]]) == '192.168':
return False
if a in ['1.1.1.1','2.2.2.2', '3.3.3.3']:
return False
return True
import base58
import hashlib
# Modified Base58 alphabet used in Monero
MONERO_BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def encode_monero_address(address, version):
# Compute checksum
hash1 = hashlib.sha256(bytes.fromhex(version + address)).digest()
hash2 = hashlib.sha256(hash1).digest()
checksum = hash2[:4]
# Concatenate version, address, and checksum
data = bytes.fromhex(version + address) + checksum
# Encode using modified Base58 alphabet
encoded = base58.b58encode(data, alphabet=MONERO_BASE58_ALPHABET)
return encoded.decode()
def decode_monero_address(encoded):
# Decode using modified Base58 alphabet
data = base58.b58decode(encoded, alphabet=MONERO_BASE58_ALPHABET)
# Extract version, address, and checksum
version = data[:2].hex()
address = data[2:-4].hex()
checksum = data[-4:]
# Verify checksum
hash1 = hashlib.sha256(bytes.fromhex(version + address)).digest()
hash2 = hashlib.sha256(hash1).digest()
if checksum != hash2[:4]:
raise ValueError('Invalid checksum')
return version, address
def decode_monero_address(encoded):
from Crypto.Hash import keccak
import binascii
# Decode using modified Base58 alphabet
print(encoded)
data = moneropy.decode(encoded)
print(data)
# Extract version, address, and checksum
version = data[:2].encode()
address1 = data[2:66].encode()
address2 = data[66:-8].encode()
checksum = data[-8:]
print(str(version + address1 + address2))
print(hex(version))
# Verify checksum
#keccak256 = keccak.new(digest_bits=256)
#keccak256.update(version + address1 + address2)
#print(keccak256.hexdigest())
#print("Keccak256:", binascii.hexlify(keccak256))
#print(binascii.hexlify(keccak256), checksum)
return False

15
sql_app/database.py Normal file
View File

@ -0,0 +1,15 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import config
# SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
# create_engine("mysql+pymysql://%:%@%:%/dev", connect_args={"check_same_thread": False}) % (config.DB('user'), config.DB('pass'), config.DB('host'), config.DB('port'))
engine = create_engine("mysql+pymysql://%:%@%:%/dev") % (config.DB('user'), config.DB('pass'), config.DB('host'), config.DB('port'))
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

25
sql_app/models.py Normal file
View File

@ -0,0 +1,25 @@
from decimal import Decimal
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class Queue(Base):
__tablename__ = "queue"
id = Column(Integer, primary_key=True, index=True)
txhash = Column(String, unique=True, index=True)
accountId = Column(Integer)
method = Column(String)
fee = Column(Decimal)
status = Column(Integer)
confirmations = Column(Integer)
callback_url = Column(String)
local_address = Column(String)
remote_address = Column(String)
balance = Column(Decimal)
callback_requirement = Column(Integer)
unixtime = Column(Integer)
date = Column(String)
ip = Column(String)

0
sql_app/schemas.py Normal file
View File

136
sub.py Normal file
View File

@ -0,0 +1,136 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2018 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
ZMQ example using python3's asyncio
Litecoind should be started with the command line arguments:
litecoind -testnet -daemon \
-zmqpubrawtx=tcp://127.0.0.1:28332 \
-zmqpubrawblock=tcp://127.0.0.1:28332 \
-zmqpubhashtx=tcp://127.0.0.1:28332 \
-zmqpubhashblock=tcp://127.0.0.1:28332 \
-zmqpubsequence=tcp://127.0.0.1:28332
We use the asyncio library here. `self.handle()` installs itself as a
future at the end of the function. Since it never returns with the event
loop having an empty stack of futures, this creates an infinite loop. An
alternative is to wrap the contents of `handle` inside `while True`.
A blocking example using python 2.7 can be obtained from the git history:
https://github.com/bitcoin/bitcoin/blob/37a7fe9e440b83e2364d5498931253937abe9294/contrib/zmq/zmq_sub.py
"""
import binascii
import asyncio
import zmq
import zmq.asyncio
import signal
import struct
import sys
import time, requests, json
if (sys.version_info.major, sys.version_info.minor) < (3, 5):
print("This example only works with Python 3.5 and greater")
sys.exit(1)
class RPCHost(object):
def __init__(self, url):
self._session = requests.Session()
self._url = url
self._headers = {'content-type': 'application/json'}
def call(self, rpcMethod, *params):
payload = json.dumps({"method": rpcMethod, "params": list(params), "jsonrpc": "2.0"})
tries = 5
hadConnectionFailures = False
while True:
try:
response = self._session.post(self._url, headers=self._headers, data=payload)
except requests.exceptions.ConnectionError:
tries -= 1
if tries == 0:
raise Exception('Failed to connect for remote procedure call.')
hadFailedConnections = True
print("Couldn't connect for remote procedure call, will sleep for five seconds and then try again ({} more tries)".format(tries))
time.sleep(10)
else:
if hadConnectionFailures:
print('Connected for remote procedure call after retry.')
break
if not response.status_code in (200, 500):
raise Exception('RPC connection failure: ' + str(response.status_code) + ' ' + response.reason)
responseJSON = response.json()
if 'error' in responseJSON and responseJSON['error'] != None:
raise Exception('Error in RPC call: ' + str(responseJSON['error']))
return responseJSON['result']
ZMQ_PORT = 28442
SERVER_IP = '172.16.0.10'
SERVER_PORT = 8442
SERVER_USER = 'litecoinrpc'
SERVER_PASS = 'uhalalala'
serverURL = "http://%s:%s@%s:%s" % (SERVER_USER, SERVER_PASS, SERVER_IP, SERVER_PORT)
rpc = RPCHost(serverURL)
class ZMQHandler():
def __init__(self):
self.loop = asyncio.get_event_loop()
self.zmqContext = zmq.asyncio.Context()
self.zmqSubSocket = self.zmqContext.socket(zmq.SUB)
self.zmqSubSocket.setsockopt(zmq.RCVHWM, 0)
#self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "")
#self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashtx")
#self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "rawblock")
#self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "rawtx")
#self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "sequence")
self.zmqSubSocket.connect("tcp://%s:%i" % (SERVER_IP, ZMQ_PORT))
async def handle(self) :
topic, body, seq = await self.zmqSubSocket.recv_multipart()
sequence = "Unknown"
if len(seq) == 4:
sequence = str(struct.unpack('<I', seq)[-1])
if topic == b"hashblock":
print('- HASH BLOCK ('+sequence+') -')
print(binascii.hexlify(body))
elif topic == b"hashtx":
print('- HASH TX ('+sequence+') -')
print(binascii.hexlify(body))
elif topic == b"rawblock":
print('- RAW BLOCK HEADER ('+sequence+') -')
print(binascii.hexlify(body[:80]))
elif topic == b"rawtx":
print('- RAW TX ('+sequence+') -')
#decode = rpc.call('decoderawtransaction', binascii.hexlify(body).decode('utf-8'))
#data = json.dumps(decode)
#print(data)
#print(binascii.hexlify(body))
elif topic == b"sequence":
hash = binascii.hexlify(body[:32])
label = chr(body[32])
mempool_sequence = None if len(body) != 32+1+8 else struct.unpack("<Q", body[32+1:])[0]
print('- SEQUENCE ('+sequence+') -')
print(hash, label, mempool_sequence)
else:
print(binascii.hexlify(body))
# schedule ourselves to receive the next message
asyncio.ensure_future(self.handle())
def start(self):
self.loop.add_signal_handler(signal.SIGINT, self.stop)
self.loop.create_task(self.handle())
self.loop.run_forever()
def stop(self):
self.loop.stop()
self.zmqContext.destroy()
daemon = ZMQHandler()
daemon.start()

7
test.py Normal file
View File

@ -0,0 +1,7 @@
from pynng import Pair0
s1 = Pair0()
while True:
s1.listen('tcp://172.16.0.1:28442')
print(s1.recv())