"""
nukez_pay.py — Execute on-chain payments for pynukez 4.0+ test flows.

pynukez 4.0.0 removed solana_transfer() and evm_transfer() from the SDK.
The SDK no longer moves funds; it returns payment instructions and
confirms you paid. This helper fills the "execute the transfer" gap
for LOCAL TESTING so a notebook or script can drive the full pynukez
flow end-to-end without inlining solders/web3 transfer code in every
cell.

This file is INTENTIONALLY NOT part of pynukez. It lives on the test
harness side of the boundary. If you put this inside pynukez you have
just reintroduced the private-key-handling footprint 4.0.0 deleted.

DEPENDENCIES (install in your notebook env):

    pip install pynukez solana solders web3 eth-account base58

KEY FILE FORMATS:

    Solana (Ed25519):   JSON array of 64 integers (produced by
                        `solana-keygen new`). First 32 bytes = seed,
                        last 32 bytes = pubkey.
    EVM (secp256k1):    JSON object {"address": "0x...",
                        "private_key": "0x..."}.

TYPICAL NOTEBOOK USAGE:

    import sys
    sys.path.insert(0, "/Users/zhanson/Desktop")

    from pynukez import Nukez
    from nukez_pay import pay_from_request

    SVM = "/Users/zhanson/.keys/delegators/svm_key.json"
    EVM = "/Users/zhanson/.keys/delegators/evm_key.json"

    # Dual-key client: Ed25519 signs envelopes for Solana-paid lockers,
    # secp256k1 signs envelopes for EVM-paid lockers. Neither key moves
    # funds — that's what this helper is for.
    client = Nukez(
        keypair_path=SVM,
        evm_private_key_path=EVM,
        base_url="https://api.nukez.xyz",
        network="mainnet-beta",
    )

    req = client.request_storage(units=1, provider="gcs")
    print(req.next_step)

    # Execute the transfer (the thing pynukez 4.0 no longer does)
    payment = pay_from_request(
        req,
        svm_keypair=SVM,
        evm_keypair=EVM,
        # Optional RPC overrides. Mainnet public RPCs rate-limit hard;
        # pass Helius/QuickNode/Alchemy endpoints for real testing.
        svm_rpc="https://api.mainnet-beta.solana.com",
        evm_rpc=None,  # uses default or env var
    )
    print(f"tx_sig: {payment.tx_sig}")

    # Close the loop with the gateway
    receipt = client.confirm_storage(req.pay_req_id, **payment.confirm_kwargs())
    print(f"receipt.id: {receipt.id}")

SELECTING A PAYMENT PATH:

    pay_from_request inspects the selected payment leg. For Solana SOL it
    sends a native SOL transfer. For Solana USDC/USDT/WETH it sends an SPL
    token transfer from the payer's associated token account to the treasury
    token account returned by the gateway. For EVM it sends native MON or
    the ERC-20 given by req.token_address.

    No auto-swap, no route hunting — if you want a non-default leg, call
    request_storage(pay_network=..., pay_asset=...) first to get a quote in
    that exact shape.

DEFAULT RPC URLS (override with svm_rpc= / evm_rpc= at call time):

    Solana:
      solana-devnet             -> https://api.devnet.solana.com
      solana-mainnet            -> https://api.mainnet-beta.solana.com
    Monad:
      monad-testnet  / 10143    -> $MONAD_TESTNET_RPC_PRIMARY or
                                   https://testnet-rpc.monad.xyz
      monad-mainnet  / 143      -> $MONAD_MAINNET_RPC_PRIMARY or
                                   https://rpc.monad.xyz

LOW-LEVEL FUNCTIONS (use these directly if you don't have a pynukez
StorageRequest in hand):

    send_sol(keypair_path, to_address, amount_lamports, network="solana-mainnet", rpc_url=None)
    send_spl(keypair_path, to_token_account, mint_address, amount_raw, decimals,
             network="solana-mainnet", rpc_url=None)
    send_evm(private_key_path, to_address, amount_raw, network="monad-mainnet",
             token_address=None, rpc_url=None)
"""
from __future__ import annotations

import json
import os
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Optional


# ══════════════════════════════════════════════════════════════════════
# Result type
# ══════════════════════════════════════════════════════════════════════

@dataclass
class PaymentResult:
    """
    Return value of pay_from_request / send_sol / send_evm.

    Maps 1:1 to the kwargs pynukez's confirm_storage() expects:

        receipt = client.confirm_storage(req.pay_req_id, **payment.confirm_kwargs())
    """
    tx_sig: str
    chain: str              # "solana" or "evm"
    network: str            # original network identifier from request
    payment_chain: Optional[str] = None   # passed to confirm_storage for EVM
    payment_asset: Optional[str] = None   # passed to confirm_storage for EVM

    def confirm_kwargs(self) -> dict:
        """kwargs ready to splat into Nukez.confirm_storage(pay_req_id, ...)."""
        kw: dict = {"tx_sig": self.tx_sig}
        if self.payment_chain:
            kw["payment_chain"] = self.payment_chain
        if self.payment_asset:
            kw["payment_asset"] = self.payment_asset
        return kw


# ══════════════════════════════════════════════════════════════════════
# RPC defaults
# ══════════════════════════════════════════════════════════════════════

def _default_solana_rpc(network: str) -> str:
    n = (network or "").lower()
    if "devnet" in n:
        return "https://api.devnet.solana.com"
    if "testnet" in n:
        return "https://api.testnet.solana.com"
    return "https://api.mainnet-beta.solana.com"


def _default_evm_rpc(network: str) -> str:
    n = (network or "").lower()
    if ("monad" in n and "testnet" in n) or "10143" in n:
        return os.environ.get(
            "MONAD_TESTNET_RPC_PRIMARY",
            "https://testnet-rpc.monad.xyz",
        )
    if "monad" in n or n.endswith(":143") or n == "143":
        return os.environ.get(
            "MONAD_MAINNET_RPC_PRIMARY",
            "https://rpc.monad.xyz",
        )
    raise ValueError(
        f"No default EVM RPC known for network {network!r}. "
        f"Pass evm_rpc= explicitly or set MONAD_TESTNET_RPC_PRIMARY / "
        f"MONAD_MAINNET_RPC_PRIMARY."
    )


def _load_solana_keypair(keypair_path: str):
    """Load a Solana CLI 64-byte keypair JSON as a solders Keypair."""
    from solders.keypair import Keypair as SoldersKeypair

    kp_path = Path(keypair_path).expanduser()
    with open(kp_path, "r") as f:
        raw = json.load(f)
    if not isinstance(raw, list) or len(raw) != 64:
        raise ValueError(
            f"{keypair_path}: expected a 64-int Solana keypair array, "
            f"got {type(raw).__name__} len="
            f"{len(raw) if isinstance(raw, list) else '?'}"
        )
    return SoldersKeypair.from_bytes(bytes(b & 0xFF for b in raw))


# ══════════════════════════════════════════════════════════════════════
# Solana SOL transfer
# ══════════════════════════════════════════════════════════════════════

def send_sol(
    keypair_path: str,
    to_address: str,
    amount_lamports: int,
    network: str = "solana-mainnet",
    rpc_url: Optional[str] = None,
    wait_for_confirmation: bool = True,
    timeout_sec: int = 60,
    verbose: bool = True,
) -> PaymentResult:
    """
    Submit a native SOL transfer and return a PaymentResult.

    Args:
        keypair_path: Path to a Solana keypair JSON file (64-int array).
        to_address: Destination base58 pubkey.
        amount_lamports: Integer lamports. Use request.amount_lamports.
        network: "solana-devnet" / "solana-mainnet" (used to pick a
            default RPC if rpc_url isn't provided).
        rpc_url: Explicit RPC endpoint. Mainnet public RPC rate-limits
            aggressively — pass a Helius/QuickNode/Triton URL for
            non-trivial testing.
        wait_for_confirmation: Block until the RPC reports the tx as
            confirmed or finalized.
        timeout_sec: Max seconds to wait for confirmation.
        verbose: Print progress.

    Returns:
        PaymentResult with tx_sig = base58 Solana signature.
    """
    # Lazy imports so importing this module doesn't require solders/solana
    # unless a Solana payment is actually executed.
    from solana.rpc.api import Client
    from solders.message import MessageV0
    from solders.pubkey import Pubkey
    from solders.system_program import TransferParams, transfer
    from solders.transaction import VersionedTransaction

    rpc = rpc_url or _default_solana_rpc(network)
    client = Client(rpc)
    kp = _load_solana_keypair(keypair_path)

    to_pubkey = Pubkey.from_string(to_address)
    ix = transfer(TransferParams(
        from_pubkey=kp.pubkey(),
        to_pubkey=to_pubkey,
        lamports=int(amount_lamports),
    ))

    bh_resp = client.get_latest_blockhash()
    blockhash = bh_resp.value.blockhash

    msg = MessageV0.try_compile(
        payer=kp.pubkey(),
        instructions=[ix],
        address_lookup_table_accounts=[],
        recent_blockhash=blockhash,
    )
    tx = VersionedTransaction(msg, [kp])

    if verbose:
        print(f"  [sol] sending {int(amount_lamports)/1e9:.9f} SOL "
              f"({amount_lamports} lamports)")
        print(f"  [sol] from   {kp.pubkey()}")
        print(f"  [sol] to     {to_address}")
        print(f"  [sol] rpc    {rpc}")

    resp = client.send_transaction(tx)
    sig = str(resp.value)
    if verbose:
        print(f"  [sol] submitted: {sig}")

    if wait_for_confirmation:
        deadline = time.time() + timeout_sec
        status = None
        while time.time() < deadline:
            try:
                status_resp = client.get_signature_statuses([resp.value])
                status = status_resp.value[0] if status_resp.value else None
                if status and status.confirmation_status in ("confirmed", "finalized"):
                    if verbose:
                        print(f"  [sol] status: {status.confirmation_status}")
                    break
            except Exception as e:
                if verbose:
                    print(f"  [sol] status check error (retrying): {e}")
            time.sleep(2)
        else:
            if verbose:
                print(f"  [sol] still pending after {timeout_sec}s — "
                      f"returning signature anyway")

    return PaymentResult(
        tx_sig=sig,
        chain="solana",
        network=network,
    )


# ══════════════════════════════════════════════════════════════════════
# Solana SPL token transfer
# ══════════════════════════════════════════════════════════════════════

def send_spl(
    keypair_path: str,
    to_token_account: str,
    mint_address: str,
    amount_raw: int,
    decimals: int,
    network: str = "solana-mainnet",
    rpc_url: Optional[str] = None,
    wait_for_confirmation: bool = True,
    timeout_sec: int = 60,
    verbose: bool = True,
) -> PaymentResult:
    """
    Submit a Solana SPL token transfer and return a PaymentResult.

    Args:
        keypair_path: Path to a Solana keypair JSON file (64-int array).
        to_token_account: Destination SPL token account. For Nukez gateway
            quotes this is the treasury ATA returned as request.pay_to_address.
        mint_address: SPL token mint address, e.g. USDC/USDT/WETH mint.
        amount_raw: Atomic token units from the quote.
        decimals: Token decimals from the quote.
        network: Solana network label or CAIP-2 string.
        rpc_url: Explicit RPC endpoint.
        wait_for_confirmation: Block until confirmed/finalized.
        timeout_sec: Max seconds to wait.
        verbose: Print progress.

    Returns:
        PaymentResult with tx_sig = base58 Solana signature.
    """
    from solana.rpc.api import Client
    from solders.message import MessageV0
    from solders.pubkey import Pubkey
    from solders.transaction import VersionedTransaction
    from spl.token.constants import TOKEN_PROGRAM_ID
    from spl.token.instructions import (
        TransferCheckedParams,
        get_associated_token_address,
        transfer_checked,
    )

    rpc = rpc_url or _default_solana_rpc(network)
    client = Client(rpc)
    kp = _load_solana_keypair(keypair_path)

    mint = Pubkey.from_string(mint_address)
    source_ata = get_associated_token_address(kp.pubkey(), mint)
    dest_account = Pubkey.from_string(to_token_account)

    ix = transfer_checked(
        TransferCheckedParams(
            program_id=TOKEN_PROGRAM_ID,
            source=source_ata,
            mint=mint,
            dest=dest_account,
            owner=kp.pubkey(),
            amount=int(amount_raw),
            decimals=int(decimals),
            signers=[],
        )
    )

    bh_resp = client.get_latest_blockhash()
    blockhash = bh_resp.value.blockhash
    msg = MessageV0.try_compile(
        payer=kp.pubkey(),
        instructions=[ix],
        address_lookup_table_accounts=[],
        recent_blockhash=blockhash,
    )
    tx = VersionedTransaction(msg, [kp])

    if verbose:
        human = int(amount_raw) / (10 ** int(decimals))
        print(f"  [spl] sending {human:.12g} tokens ({amount_raw} raw)")
        print(f"  [spl] from owner  {kp.pubkey()}")
        print(f"  [spl] source ATA  {source_ata}")
        print(f"  [spl] mint        {mint_address}")
        print(f"  [spl] to token acct {to_token_account}")
        print(f"  [spl] rpc         {rpc}")

    resp = client.send_transaction(tx)
    sig = str(resp.value)
    if verbose:
        print(f"  [spl] submitted: {sig}")

    if wait_for_confirmation:
        deadline = time.time() + timeout_sec
        status = None
        while time.time() < deadline:
            try:
                status_resp = client.get_signature_statuses([resp.value])
                status = status_resp.value[0] if status_resp.value else None
                if status and status.confirmation_status in ("confirmed", "finalized"):
                    if verbose:
                        print(f"  [spl] status: {status.confirmation_status}")
                    break
            except Exception as e:
                if verbose:
                    print(f"  [spl] status check error (retrying): {e}")
            time.sleep(2)
        else:
            if verbose:
                print(f"  [spl] still pending after {timeout_sec}s — "
                      f"returning signature anyway")

    return PaymentResult(
        tx_sig=sig,
        chain="solana",
        network=network,
    )


# ══════════════════════════════════════════════════════════════════════
# EVM native / ERC-20 transfer
# ══════════════════════════════════════════════════════════════════════

_ERC20_TRANSFER_ABI = [{
    "constant": False,
    "inputs": [
        {"name": "_to", "type": "address"},
        {"name": "_value", "type": "uint256"},
    ],
    "name": "transfer",
    "outputs": [{"name": "", "type": "bool"}],
    "payable": False,
    "stateMutability": "nonpayable",
    "type": "function",
}]


def send_evm(
    private_key_path: str,
    to_address: str,
    amount_raw: int,
    network: str = "monad-mainnet",
    token_address: Optional[str] = None,
    rpc_url: Optional[str] = None,
    gas_native: int = 21000,
    gas_erc20: int = 120000,
    wait_for_confirmation: bool = True,
    timeout_sec: int = 120,
    verbose: bool = True,
) -> PaymentResult:
    """
    Submit an EVM native or ERC-20 transfer and return a PaymentResult.

    Args:
        private_key_path: Path to an EVM key JSON
            ({"address": "0x...", "private_key": "0x..."}).
        to_address: Destination 0x address.
        amount_raw: Atomic units (wei for native, token units for ERC-20).
            Use request.amount_raw.
        network: "monad-testnet" / "monad-mainnet" (used to pick a
            default RPC if rpc_url isn't provided). "eip155:..." also
            accepted via the same lookup.
        token_address: ERC-20 contract address. None = native transfer
            (MON). Use request.token_address directly.
        rpc_url: Explicit RPC endpoint.
        gas_native / gas_erc20: Gas limit overrides.
        wait_for_confirmation: Block until receipt is mined.
        timeout_sec: Max seconds to wait.
        verbose: Print progress.

    Returns:
        PaymentResult with tx_sig = 0x-prefixed EVM tx hash.
    """
    # Lazy imports
    from web3 import Web3
    try:
        from web3.middleware import ExtraDataToPOAMiddleware  # web3 >= 7
        _poa_mw: Any = ExtraDataToPOAMiddleware
    except ImportError:
        try:
            from web3.middleware import geth_poa_middleware  # web3 < 7
            _poa_mw = geth_poa_middleware
        except ImportError:
            _poa_mw = None
    from eth_account import Account

    rpc = rpc_url or _default_evm_rpc(network)
    w3 = Web3(Web3.HTTPProvider(rpc))
    if _poa_mw is not None:
        try:
            w3.middleware_onion.inject(_poa_mw, layer=0)
        except Exception:
            pass

    kp_path = Path(private_key_path).expanduser()
    with open(kp_path, "r") as f:
        data = json.load(f)
    private_key = data.get("private_key") or data.get("privateKey")
    if not private_key:
        raise ValueError(
            f"{private_key_path}: no 'private_key' field. "
            f"Expected {{\"address\": \"0x...\", \"private_key\": \"0x...\"}}."
        )

    account = Account.from_key(private_key)
    from_addr = account.address

    chain_id = w3.eth.chain_id
    nonce = w3.eth.get_transaction_count(from_addr)
    gas_price = w3.eth.gas_price

    if token_address:
        contract = w3.eth.contract(
            address=w3.to_checksum_address(token_address),
            abi=_ERC20_TRANSFER_ABI,
        )
        tx = contract.functions.transfer(
            w3.to_checksum_address(to_address),
            int(amount_raw),
        ).build_transaction({
            "chainId": chain_id,
            "from": from_addr,
            "nonce": nonce,
            "gas": gas_erc20,
            "gasPrice": gas_price,
        })
        label = f"ERC-20 {token_address}"
    else:
        tx = {
            "chainId": chain_id,
            "to": w3.to_checksum_address(to_address),
            "value": int(amount_raw),
            "nonce": nonce,
            "gas": gas_native,
            "gasPrice": gas_price,
        }
        label = "native"

    signed = account.sign_transaction(tx)
    raw_tx = getattr(signed, "raw_transaction", None) or getattr(signed, "rawTransaction", None)
    if raw_tx is None:
        raise RuntimeError(
            "eth_account signed transaction has no raw_transaction / "
            "rawTransaction attribute — check your web3 / eth_account versions."
        )
    tx_hash_bytes = w3.eth.send_raw_transaction(raw_tx)
    tx_hash_hex = tx_hash_bytes.hex()
    if not tx_hash_hex.startswith("0x"):
        tx_hash_hex = "0x" + tx_hash_hex

    if verbose:
        print(f"  [evm] {label} transfer: {int(amount_raw)} atomic units")
        print(f"  [evm] from     {from_addr}")
        print(f"  [evm] to       {to_address}")
        print(f"  [evm] chainId  {chain_id}")
        print(f"  [evm] nonce    {nonce}")
        print(f"  [evm] gasPrice {gas_price}")
        print(f"  [evm] rpc      {rpc}")
        print(f"  [evm] submitted: {tx_hash_hex}")

    if wait_for_confirmation:
        try:
            rcpt = w3.eth.wait_for_transaction_receipt(tx_hash_bytes, timeout=timeout_sec)
            if verbose:
                print(f"  [evm] mined in block {rcpt.blockNumber}, "
                      f"status={rcpt.status}, gasUsed={rcpt.gasUsed}")
            if rcpt.status != 1:
                raise RuntimeError(
                    f"EVM tx {tx_hash_hex} reverted (status=0). "
                    f"Block: {rcpt.blockNumber}"
                )
        except Exception as e:
            if verbose:
                print(f"  [evm] wait_for_transaction_receipt failed: {e}")
            raise

    return PaymentResult(
        tx_sig=tx_hash_hex,
        chain="evm",
        network=network,
        payment_chain=network,
        payment_asset=None,  # filled in by pay_from_request
    )


# ══════════════════════════════════════════════════════════════════════
# High-level dispatcher
# ══════════════════════════════════════════════════════════════════════

def pay_from_request(
    request: Any,
    svm_keypair: Optional[str] = None,
    evm_keypair: Optional[str] = None,
    svm_rpc: Optional[str] = None,
    evm_rpc: Optional[str] = None,
    verbose: bool = True,
) -> PaymentResult:
    """
    Execute the payment described by a pynukez StorageRequest.

    Inspects the selected payment leg and dispatches to send_sol, send_spl,
    or send_evm.
    Returns a PaymentResult whose confirm_kwargs() can be splatted into
    client.confirm_storage(request.pay_req_id, ...).

    Args:
        request: A pynukez.types.StorageRequest (from client.request_storage()).
        svm_keypair: Path to a Solana keypair JSON. Required for
            Solana quotes.
        evm_keypair: Path to an EVM key JSON. Required for EVM quotes.
        svm_rpc: Optional Solana RPC override.
        evm_rpc: Optional EVM RPC override.
        verbose: Print progress.

    Raises:
        ValueError: Required keypair missing for the payment path.
    """
    if verbose:
        print(f"[pay_from_request] network={request.network!r} "
              f"pay_asset={request.pay_asset!r} is_evm={request.is_evm}")

    if request.is_evm:
        if not evm_keypair:
            raise ValueError(
                "EVM quote but no evm_keypair provided. "
                "Pass evm_keypair=<path to EVM key JSON>."
            )
        # amount_raw is the authoritative field for EVM amounts
        amount_raw = getattr(request, "amount_raw", None)
        if amount_raw is None:
            raise ValueError(
                "EVM quote missing amount_raw. Re-run request_storage() "
                "with explicit pay_network=/pay_asset= — the gateway "
                "didn't populate the EVM amount fields in its 402 response."
            )
        result = send_evm(
            private_key_path=evm_keypair,
            to_address=request.pay_to_address,
            amount_raw=int(amount_raw),
            network=request.network,
            token_address=getattr(request, "token_address", None),
            rpc_url=evm_rpc,
            verbose=verbose,
        )
        # Populate the confirm_storage EVM kwargs from the quote
        result.payment_chain = request.network
        result.payment_asset = request.pay_asset
        return result

    if not svm_keypair:
        raise ValueError(
            "Solana quote but no svm_keypair provided. "
            "Pass svm_keypair=<path to Solana keypair JSON>."
        )
    if (request.pay_asset or "").upper() != "SOL":
        amount_raw = getattr(request, "amount_raw", None)
        if amount_raw is None:
            amount_raw = getattr(request, "amount_lamports", None)
        mint = getattr(request, "token_address", None)
        if not mint and getattr(request, "payment_options", None):
            for opt in request.payment_options:
                if opt.get("pay_asset", "").upper() != str(request.pay_asset).upper():
                    continue
                if opt.get("pay_to_address") != getattr(request, "pay_to_address", None):
                    continue
                mint = opt.get("asset_contract")
                break
        decimals = getattr(request, "token_decimals", None)
        if decimals is None and getattr(request, "payment_options", None):
            for opt in request.payment_options:
                if opt.get("pay_asset", "").upper() == str(request.pay_asset).upper():
                    decimals = opt.get("decimals")
                    break
        if not mint:
            raise ValueError(
                f"Solana SPL quote for {request.pay_asset!r} is missing the "
                "token mint. Re-run request_storage(pay_network='solana:...', "
                "pay_asset=...) with pynukez>=4.0.5."
            )
        if amount_raw is None:
            raise ValueError(
                f"Solana SPL quote for {request.pay_asset!r} is missing "
                "amount_raw / amount_lamports."
            )
        if decimals is None:
            raise ValueError(
                f"Solana SPL quote for {request.pay_asset!r} is missing token decimals."
            )
        result = send_spl(
            keypair_path=svm_keypair,
            to_token_account=request.pay_to_address,
            mint_address=mint,
            amount_raw=int(amount_raw),
            decimals=int(decimals),
            network=request.network,
            rpc_url=svm_rpc,
            verbose=verbose,
        )
        result.payment_chain = request.network
        result.payment_asset = request.pay_asset
        return result

    lamports = getattr(request, "amount_lamports", None)
    if lamports is None:
        raise ValueError(
            "Solana quote missing amount_lamports. The gateway's 402 "
            "response did not populate the Solana amount fields."
        )
    return send_sol(
        keypair_path=svm_keypair,
        to_address=request.pay_to_address,
        amount_lamports=int(lamports),
        network=request.network,
        rpc_url=svm_rpc,
        verbose=verbose,
    )


# ══════════════════════════════════════════════════════════════════════
# CLI — quick manual tests without opening a notebook
# ══════════════════════════════════════════════════════════════════════

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(
        description="Execute a pynukez payment outside the SDK.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:

  # Solana transfer (used manually, e.g. to top up a test wallet)
  python3 nukez_pay.py sol \\
    --keypair ~/.keys/delegators/svm_key.json \\
    --to <dest_pubkey> --lamports 1000000 --network solana-devnet

  # Solana SPL transfer (USDC/USDT/WETH quote payTo is a token account)
  python3 nukez_pay.py spl \\
    --keypair ~/.keys/delegators/svm_key.json \\
    --to-token-account <treasury_ata> \\
    --mint <spl_mint> --amount-raw 20000000 --decimals 6 \\
    --network solana-mainnet

  # EVM native transfer (Monad testnet MON)
  python3 nukez_pay.py evm \\
    --keypair ~/.keys/delegators/evm_key.json \\
    --to 0xDEST --amount-raw 1000000000000000000 \\
    --network monad-testnet

  # ERC-20 transfer (Monad mainnet USDC)
  python3 nukez_pay.py evm \\
    --keypair ~/.keys/delegators/evm_key.json \\
    --to 0xDEST --amount-raw 1000000 \\
    --network monad-mainnet \\
    --token 0xUSDCCONTRACT
        """,
    )
    sub = parser.add_subparsers(dest="cmd", required=True)

    sp = sub.add_parser("sol", help="Send SOL")
    sp.add_argument("--keypair", required=True)
    sp.add_argument("--to", required=True)
    sp.add_argument("--lamports", type=int, required=True)
    sp.add_argument("--network", default="solana-mainnet")
    sp.add_argument("--rpc", default=None)

    tp = sub.add_parser("spl", help="Send Solana SPL token")
    tp.add_argument("--keypair", required=True)
    tp.add_argument("--to-token-account", required=True)
    tp.add_argument("--mint", required=True)
    tp.add_argument("--amount-raw", type=int, required=True)
    tp.add_argument("--decimals", type=int, required=True)
    tp.add_argument("--network", default="solana-mainnet")
    tp.add_argument("--rpc", default=None)

    ep = sub.add_parser("evm", help="Send EVM native or ERC-20")
    ep.add_argument("--keypair", required=True)
    ep.add_argument("--to", required=True)
    ep.add_argument("--amount-raw", type=int, required=True)
    ep.add_argument("--network", default="monad-mainnet")
    ep.add_argument("--token", default=None, help="ERC-20 contract (omit for native)")
    ep.add_argument("--rpc", default=None)

    args = parser.parse_args()

    if args.cmd == "sol":
        result = send_sol(
            keypair_path=args.keypair,
            to_address=args.to,
            amount_lamports=args.lamports,
            network=args.network,
            rpc_url=args.rpc,
        )
    elif args.cmd == "spl":
        result = send_spl(
            keypair_path=args.keypair,
            to_token_account=args.to_token_account,
            mint_address=args.mint,
            amount_raw=args.amount_raw,
            decimals=args.decimals,
            network=args.network,
            rpc_url=args.rpc,
        )
    else:
        result = send_evm(
            private_key_path=args.keypair,
            to_address=args.to,
            amount_raw=args.amount_raw,
            network=args.network,
            token_address=args.token,
            rpc_url=args.rpc,
        )

    print(json.dumps({
        "tx_sig": result.tx_sig,
        "chain": result.chain,
        "network": result.network,
        "payment_chain": result.payment_chain,
        "payment_asset": result.payment_asset,
    }, indent=2))
