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") 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")))

Loading…
Cancel
Save