|
|
|
@ -50,8 +50,13 @@ SQUARE_API_VERSION = "2026-01-22" |
|
|
|
SQUARE_WEBHOOK_LOG = str(BASE_DIR / "logs" / "square_webhook_events.log") |
|
|
|
SQUARE_WEBHOOK_LOG = str(BASE_DIR / "logs" / "square_webhook_events.log") |
|
|
|
ORACLE_BASE_URL = os.getenv("ORACLE_BASE_URL", "https://monitor.outsidethebox.top") |
|
|
|
ORACLE_BASE_URL = os.getenv("ORACLE_BASE_URL", "https://monitor.outsidethebox.top") |
|
|
|
CRYPTO_EVM_PAYMENT_ADDRESS = os.getenv("OTB_BILLING_CRYPTO_EVM_ADDRESS", "0x44f6c44C42e6ae0392E7289F032384C0d37F56D5") |
|
|
|
CRYPTO_EVM_PAYMENT_ADDRESS = os.getenv("OTB_BILLING_CRYPTO_EVM_ADDRESS", "0x44f6c44C42e6ae0392E7289F032384C0d37F56D5") |
|
|
|
RPC_ETHEREUM_URL = os.getenv("OTB_BILLING_RPC_ETHEREUM", "https://cloudflare-eth.com") |
|
|
|
RPC_ETHEREUM_URL = os.getenv("OTB_BILLING_RPC_ETHEREUM", "https://ethereum-rpc.publicnode.com") |
|
|
|
RPC_ARBITRUM_URL = os.getenv("OTB_BILLING_RPC_ARBITRUM", "https://arb1.arbitrum.io/rpc") |
|
|
|
RPC_ETHEREUM_URL_2 = os.getenv("OTB_BILLING_RPC_ETHEREUM_2", "https://rpc.ankr.com/eth") |
|
|
|
|
|
|
|
RPC_ETHEREUM_URL_3 = os.getenv("OTB_BILLING_RPC_ETHEREUM_3", "https://eth.drpc.org") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RPC_ARBITRUM_URL = os.getenv("OTB_BILLING_RPC_ARBITRUM", "https://arbitrum-one-rpc.publicnode.com") |
|
|
|
|
|
|
|
RPC_ARBITRUM_URL_2 = os.getenv("OTB_BILLING_RPC_ARBITRUM_2", "https://rpc.ankr.com/arbitrum") |
|
|
|
|
|
|
|
RPC_ARBITRUM_URL_3 = os.getenv("OTB_BILLING_RPC_ARBITRUM_3", "https://arb1.arbitrum.io/rpc") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -258,15 +263,19 @@ def get_invoice_crypto_options(invoice): |
|
|
|
options.sort(key=lambda x: (0 if x.get("recommended") else 1, x.get("symbol"))) |
|
|
|
options.sort(key=lambda x: (0 if x.get("recommended") else 1, x.get("symbol"))) |
|
|
|
return options |
|
|
|
return options |
|
|
|
|
|
|
|
|
|
|
|
def get_rpc_url_for_chain(chain_name): |
|
|
|
def get_rpc_urls_for_chain(chain_name): |
|
|
|
chain = str(chain_name or "").lower() |
|
|
|
chain = str(chain_name or "").lower() |
|
|
|
if chain == "ethereum": |
|
|
|
if chain == "ethereum": |
|
|
|
return RPC_ETHEREUM_URL |
|
|
|
return [u for u in [RPC_ETHEREUM_URL, RPC_ETHEREUM_URL_2, RPC_ETHEREUM_URL_3] if u] |
|
|
|
if chain == "arbitrum": |
|
|
|
if chain == "arbitrum": |
|
|
|
return RPC_ARBITRUM_URL |
|
|
|
return [u for u in [RPC_ARBITRUM_URL, RPC_ARBITRUM_URL_2, RPC_ARBITRUM_URL_3] if u] |
|
|
|
return None |
|
|
|
return [] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rpc_call_any(rpc_urls, method, params): |
|
|
|
|
|
|
|
last_error = None |
|
|
|
|
|
|
|
|
|
|
|
def rpc_call(rpc_url, method, params): |
|
|
|
for rpc_url in rpc_urls: |
|
|
|
|
|
|
|
try: |
|
|
|
payload = json.dumps({ |
|
|
|
payload = json.dumps({ |
|
|
|
"jsonrpc": "2.0", |
|
|
|
"jsonrpc": "2.0", |
|
|
|
"id": 1, |
|
|
|
"id": 1, |
|
|
|
@ -291,7 +300,16 @@ def rpc_call(rpc_url, method, params): |
|
|
|
if isinstance(data, dict) and data.get("error"): |
|
|
|
if isinstance(data, dict) and data.get("error"): |
|
|
|
raise RuntimeError(str(data["error"])) |
|
|
|
raise RuntimeError(str(data["error"])) |
|
|
|
|
|
|
|
|
|
|
|
return (data or {}).get("result") |
|
|
|
return { |
|
|
|
|
|
|
|
"rpc_url": rpc_url, |
|
|
|
|
|
|
|
"result": (data or {}).get("result"), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
except Exception as err: |
|
|
|
|
|
|
|
last_error = err |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if last_error: |
|
|
|
|
|
|
|
raise last_error |
|
|
|
|
|
|
|
raise RuntimeError("No RPC URLs configured") |
|
|
|
|
|
|
|
|
|
|
|
def _to_base_units(amount_text, decimals): |
|
|
|
def _to_base_units(amount_text, decimals): |
|
|
|
amount_dec = Decimal(str(amount_text)) |
|
|
|
amount_dec = Decimal(str(amount_text)) |
|
|
|
@ -317,13 +335,20 @@ def parse_erc20_transfer_input(input_data): |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def verify_wallet_transaction(option, tx_hash): |
|
|
|
def verify_wallet_transaction(option, tx_hash): |
|
|
|
rpc_url = get_rpc_url_for_chain(option.get("chain")) |
|
|
|
rpc_urls = get_rpc_urls_for_chain(option.get("chain")) |
|
|
|
if not rpc_url: |
|
|
|
if not rpc_urls: |
|
|
|
raise RuntimeError("No RPC configured for chain") |
|
|
|
raise RuntimeError("No RPC configured for chain") |
|
|
|
|
|
|
|
|
|
|
|
tx = rpc_call(rpc_url, "eth_getTransactionByHash", [tx_hash]) |
|
|
|
seen_result = None |
|
|
|
|
|
|
|
last_not_found = False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for rpc_url in rpc_urls: |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
rpc_resp = rpc_call_any([rpc_url], "eth_getTransactionByHash", [tx_hash]) |
|
|
|
|
|
|
|
tx = rpc_resp.get("result") |
|
|
|
if not tx: |
|
|
|
if not tx: |
|
|
|
raise RuntimeError("Transaction hash not found on RPC") |
|
|
|
last_not_found = True |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
wallet_to = str(option.get("wallet_address") or "").lower() |
|
|
|
wallet_to = str(option.get("wallet_address") or "").lower() |
|
|
|
expected_units = _to_base_units(option.get("display_amount"), option.get("decimals") or 18) |
|
|
|
expected_units = _to_base_units(option.get("display_amount"), option.get("decimals") or 18) |
|
|
|
@ -348,10 +373,20 @@ def verify_wallet_transaction(option, tx_hash): |
|
|
|
if int(parsed["amount"]) != expected_units: |
|
|
|
if int(parsed["amount"]) != expected_units: |
|
|
|
raise RuntimeError("Token transfer amount does not match frozen quote amount") |
|
|
|
raise RuntimeError("Token transfer amount does not match frozen quote amount") |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
seen_result = { |
|
|
|
"rpc_url": rpc_url, |
|
|
|
"rpc_url": rpc_url, |
|
|
|
"tx": tx, |
|
|
|
"tx": tx, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
except Exception: |
|
|
|
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not seen_result: |
|
|
|
|
|
|
|
if last_not_found: |
|
|
|
|
|
|
|
raise RuntimeError("Transaction hash not found on any configured RPC") |
|
|
|
|
|
|
|
raise RuntimeError("Unable to verify transaction on configured RPC pool") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return seen_result |
|
|
|
|
|
|
|
|
|
|
|
def square_amount_to_cents(value): |
|
|
|
def square_amount_to_cents(value): |
|
|
|
return int((to_decimal(value) * 100).quantize(Decimal("1"))) |
|
|
|
return int((to_decimal(value) * 100).quantize(Decimal("1"))) |
|
|
|
|