Browse Source

Add 3-RPC polling pools for Ethereum and Arbitrum

main
def 6 days ago
parent
commit
309209c5c0
  1. 61
      backend/app.py

61
backend/app.py

@ -50,8 +50,13 @@ SQUARE_API_VERSION = "2026-01-22"
SQUARE_WEBHOOK_LOG = str(BASE_DIR / "logs" / "square_webhook_events.log")
ORACLE_BASE_URL = os.getenv("ORACLE_BASE_URL", "https://monitor.outsidethebox.top")
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_ARBITRUM_URL = os.getenv("OTB_BILLING_RPC_ARBITRUM", "https://arb1.arbitrum.io/rpc")
RPC_ETHEREUM_URL = os.getenv("OTB_BILLING_RPC_ETHEREUM", "https://ethereum-rpc.publicnode.com")
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")))
return options
def get_rpc_url_for_chain(chain_name):
def get_rpc_urls_for_chain(chain_name):
chain = str(chain_name or "").lower()
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":
return RPC_ARBITRUM_URL
return None
return [u for u in [RPC_ARBITRUM_URL, RPC_ARBITRUM_URL_2, RPC_ARBITRUM_URL_3] if u]
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({
"jsonrpc": "2.0",
"id": 1,
@ -291,7 +300,16 @@ def rpc_call(rpc_url, method, params):
if isinstance(data, dict) and data.get("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):
amount_dec = Decimal(str(amount_text))
@ -317,13 +335,20 @@ def parse_erc20_transfer_input(input_data):
}
def verify_wallet_transaction(option, tx_hash):
rpc_url = get_rpc_url_for_chain(option.get("chain"))
if not rpc_url:
rpc_urls = get_rpc_urls_for_chain(option.get("chain"))
if not rpc_urls:
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:
raise RuntimeError("Transaction hash not found on RPC")
last_not_found = True
continue
wallet_to = str(option.get("wallet_address") or "").lower()
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:
raise RuntimeError("Token transfer amount does not match frozen quote amount")
return {
seen_result = {
"rpc_url": rpc_url,
"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):
return int((to_decimal(value) * 100).quantize(Decimal("1")))

Loading…
Cancel
Save