"""W16-B helper: fund + register the rotated v2 seeder wallets. The legacy seeder slots (``gemini`` / ``deepseek`` / ``qwen``) carry on-chain reputation below TranslationAuction's 0.7 gate, so every ``submitBid`` from those wallets reverts with ``"reputation gate"``. Rotating to fresh agent names (``gemini-v2`` / ``deepseek-v2`` / ``qwen-v2``) produces wallets with a fresh ReputationRegistry score (initial value 1.0) which sails through the gate. This script: 1. Derives the three v2 wallet addresses from ``HACKATHON_WALLET_PRIVATE_KEY`` (same deterministic SHA-256 derivation used by :mod:`polyglot_alpha.agents.wallets`). 2. Idempotently tops each one up with native ETH + MockUSDC from the operator wallet (skips if already above target). 3. Calls ``TranslationAuction.registerAgent()`` from each wallet (5 USDC stake), skipping wallets already marked ``registered``. Safe to re-run. Prints a summary table with addresses, balances, register tx hashes, and on-chain reputation post-register. Usage:: .venv/bin/python scripts/fund_seeder_wallets_v2.py .venv/bin/python scripts/fund_seeder_wallets_v2.py --dry-run """ from __future__ import annotations import argparse import asyncio import os import sys import time from pathlib import Path from typing import Any from eth_account import Account from eth_account.signers.local import LocalAccount from web3 import Web3 # Make the repo root importable without ``pip install -e .``. _REPO_ROOT = Path(__file__).resolve().parents[1] if str(_REPO_ROOT) not in sys.path: sys.path.insert(0, str(_REPO_ROOT)) import polyglot_alpha # noqa: E402 — runs the .env loader from polyglot_alpha.agents.wallets import derive_agent_wallet # noqa: E402 from polyglot_alpha.chain.auction_client import AuctionClient # noqa: E402 from polyglot_alpha.onchain import ( # noqa: E402 OnChainClient, USDC_DECIMALS, usdc_to_units, ) # v2 seeder slots (W16-B rotation). Mirrored in # ``polyglot_alpha.agents.wallets.AGENT_NAMES`` and the orchestrator's # ``agent_names`` tuple. V2_AGENT_NAMES: tuple[str, ...] = ("gemini-v2", "deepseek-v2", "qwen-v2") ETH_TARGET = float(os.environ.get("AGENT_ETH_TARGET_V2", "0.05")) USDC_TARGET = float(os.environ.get("AGENT_USDC_TARGET_V2", "20.0")) STAKE_USDC = float(os.environ.get("AGENT_STAKE_USDC", "5.0")) # Gas overhead the operator needs to keep for itself (sanity check). OPERATOR_RESERVE_ETH = 0.01 ETH_GAS_LIMIT = 21_000 USDC_TRANSFER_GAS = 80_000 USDC_MINT_GAS = 120_000 def _operator_account() -> LocalAccount: pk = os.environ.get("HACKATHON_WALLET_PRIVATE_KEY") if not pk: sys.exit("HACKATHON_WALLET_PRIVATE_KEY not set; aborting") return Account.from_key(pk) def _send(w3: Web3, txn: dict[str, Any], account: LocalAccount) -> str: signed = w3.eth.account.sign_transaction(txn, account.key) raw_tx = getattr(signed, "raw_transaction", None) or signed.rawTransaction tx_hash = w3.eth.send_raw_transaction(raw_tx).hex() if not tx_hash.startswith("0x"): tx_hash = "0x" + tx_hash receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) if receipt.status != 1: raise RuntimeError(f"tx reverted: {tx_hash}") return tx_hash def _top_up_eth( w3: Web3, operator: LocalAccount, target_address: str, current_balance_wei: int, target_eth: float, ) -> tuple[bool, str]: target_wei = int(target_eth * 1e18) if current_balance_wei >= target_wei: return False, "" delta_wei = target_wei - current_balance_wei nonce = w3.eth.get_transaction_count(operator.address) txn = { "from": operator.address, "to": Web3.to_checksum_address(target_address), "value": delta_wei, "nonce": nonce, "gas": ETH_GAS_LIMIT, "gasPrice": w3.eth.gas_price, "chainId": w3.eth.chain_id, } tx_hash = _send(w3, txn, operator) return True, tx_hash def _top_up_usdc( client: OnChainClient, operator: LocalAccount, target_address: str, current_units: int, target_usdc: float, ) -> tuple[bool, str]: target_units = usdc_to_units(target_usdc) if current_units >= target_units: return False, "" delta_units = target_units - current_units op_bal = int(client.usdc.functions.balanceOf(operator.address).call()) nonce = client.w3.eth.get_transaction_count(operator.address) if op_bal >= delta_units: txn = client.usdc.functions.transfer( Web3.to_checksum_address(target_address), delta_units ).build_transaction( { "from": operator.address, "nonce": nonce, "gas": USDC_TRANSFER_GAS, "gasPrice": client.w3.eth.gas_price, "chainId": client.config.chain_id, } ) tx_hash = _send(client.w3, txn, operator) return True, tx_hash try: txn = client.usdc.functions.mint( Web3.to_checksum_address(target_address), delta_units ).build_transaction( { "from": operator.address, "nonce": nonce, "gas": USDC_MINT_GAS, "gasPrice": client.w3.eth.gas_price, "chainId": client.config.chain_id, } ) tx_hash = _send(client.w3, txn, operator) return True, tx_hash except Exception as exc: # pragma: no cover - real-RPC dependent return False, f"mint failed: {exc!s}" def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=( "Fund + register the W16-B v2 seeder wallets. " "Idempotent: skips any wallet already at/above target balance " "and already-registered agents." ), ) parser.add_argument( "--dry-run", action="store_true", help="Inspect balances + registration state, do not send any tx.", ) parser.add_argument( "--eth-target", type=float, default=ETH_TARGET, help=f"Minimum native balance per agent (default {ETH_TARGET}).", ) parser.add_argument( "--usdc-target", type=float, default=USDC_TARGET, help=f"Minimum MockUSDC balance per agent (default {USDC_TARGET}).", ) parser.add_argument( "--stake-usdc", type=float, default=STAKE_USDC, help=f"Stake amount in USDC for registerAgent (default {STAKE_USDC}).", ) parser.add_argument( "--skip-register", action="store_true", help="Only fund; do not call registerAgent (useful when only re-funding).", ) return parser.parse_args() async def _register_one( auction_client: AuctionClient, onchain: OnChainClient, name: str, pk: str, address: str, stake_usdc: float, ) -> tuple[str, str]: """Call registerAgent if not already registered. Returns (tx_or_status, note).""" if onchain.is_registered(address): return ("already_registered", "skipped") tx_hash = await auction_client.register_agent(agent_pk=pk, stake_usdc=stake_usdc) # Confirm receipt receipt = onchain.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) status = int(getattr(receipt, "status", 0)) if status != 1: return (tx_hash, "REVERTED") return (tx_hash, "ok") async def _async_main(args: argparse.Namespace) -> int: eth_target = float(args.eth_target) usdc_target = float(args.usdc_target) stake_usdc = float(args.stake_usdc) dry_run = bool(args.dry_run) print( f"[fund-v2] target eth={eth_target} usdc={usdc_target} " f"stake={stake_usdc}{' (DRY-RUN)' if dry_run else ''}" ) operator = _operator_account() op_pk = operator.key.hex() if not op_pk.startswith("0x"): op_pk = "0x" + op_pk client = OnChainClient() w3 = client.w3 op_eth_wei = w3.eth.get_balance(operator.address) op_eth = op_eth_wei / 1e18 op_usdc_units = int(client.usdc.functions.balanceOf(operator.address).call()) op_usdc = op_usdc_units / (10 ** USDC_DECIMALS) print( f"[fund-v2] operator={operator.address} " f"eth={op_eth:.6f} usdc={op_usdc:.4f}" ) if op_eth < OPERATOR_RESERVE_ETH: print( f"[fund-v2] WARNING: operator eth {op_eth:.6f} below reserve " f"{OPERATOR_RESERVE_ETH:.4f}; top-ups may fail" ) # Pre-flight USDC sufficiency check for the stake legs. total_usdc_needed_for_topup = 0.0 wallets: dict[str, Any] = {} for name in V2_AGENT_NAMES: w = derive_agent_wallet(op_pk, name) wallets[name] = w cur_usdc = int(client.usdc.functions.balanceOf(w.address).call()) delta = max(usdc_to_units(usdc_target) - cur_usdc, 0) total_usdc_needed_for_topup += delta / (10 ** USDC_DECIMALS) print( f"[fund-v2] usdc needed for top-up across 3 agents: " f"{total_usdc_needed_for_topup:.4f} (op has {op_usdc:.4f})" ) rows: list[dict[str, Any]] = [] auction_client = AuctionClient(onchain=client) for name in V2_AGENT_NAMES: w = wallets[name] addr = w.address eth_bal = w3.eth.get_balance(addr) usdc_bal = int(client.usdc.functions.balanceOf(addr).call()) pre_eth = eth_bal / 1e18 pre_usdc = usdc_bal / (10 ** USDC_DECIMALS) pre_rep = client.get_reputation(addr) pre_reg = client.is_registered(addr) eth_tx: str = "" usdc_tx: str = "" reg_tx: str = "" reg_status: str = "" if dry_run: eth_delta_wei = max(int(eth_target * 1e18) - eth_bal, 0) usdc_delta_units = max(usdc_to_units(usdc_target) - usdc_bal, 0) print( f"[fund-v2] {name:13s} {addr} eth={pre_eth:.6f} " f"usdc={pre_usdc:.4f} rep={pre_rep:.4f} registered={pre_reg} " f"would_send_eth={eth_delta_wei/1e18:.6f} " f"would_send_usdc={usdc_delta_units/(10**USDC_DECIMALS):.4f} " f"would_register={not pre_reg and not args.skip_register}" ) rows.append( { "name": name, "address": addr, "eth": pre_eth, "usdc": pre_usdc, "reputation": pre_rep, "registered": pre_reg, "eth_tx": "", "usdc_tx": "", "reg_tx": "", "reg_status": "dry-run", } ) continue # ------ ETH top-up eth_changed, eth_tx = _top_up_eth( w3, operator, addr, eth_bal, eth_target ) if eth_changed: time.sleep(1) eth_bal = w3.eth.get_balance(addr) # ------ USDC top-up usdc_changed, usdc_tx = _top_up_usdc( client, operator, addr, usdc_bal, usdc_target ) if usdc_changed: time.sleep(1) usdc_bal = int(client.usdc.functions.balanceOf(addr).call()) # ------ Register on auction if not args.skip_register: try: reg_tx, reg_status = await _register_one( auction_client, client, name, w.private_key, addr, stake_usdc ) except Exception as exc: reg_tx = "" reg_status = f"FAILED: {exc!s}" else: reg_status = "skipped (--skip-register)" # Re-read post-register state post_rep = client.get_reputation(addr) post_reg = client.is_registered(addr) post_eth = w3.eth.get_balance(addr) / 1e18 post_usdc = int(client.usdc.functions.balanceOf(addr).call()) / (10 ** USDC_DECIMALS) print( f"[fund-v2] {name:13s} {addr}" f"\n pre eth={pre_eth:.6f} usdc={pre_usdc:.4f} rep={pre_rep:.4f} registered={pre_reg}" f"\n post eth={post_eth:.6f} usdc={post_usdc:.4f} rep={post_rep:.4f} registered={post_reg}" f"\n eth_tx={eth_tx or '(skipped)'}" f"\n usdc_tx={usdc_tx or '(skipped)'}" f"\n reg_tx={reg_tx or '(none)'} status={reg_status}" ) rows.append( { "name": name, "address": addr, "eth": post_eth, "usdc": post_usdc, "reputation": post_rep, "registered": post_reg, "eth_tx": eth_tx, "usdc_tx": usdc_tx, "reg_tx": reg_tx, "reg_status": reg_status, } ) print("\n[fund-v2] summary:") print(f" {'name':13s} {'address':42s} {'eth':>10s} {'usdc':>8s} {'rep':>6s} {'reg':>5s} reg_tx") for r in rows: print( f" {r['name']:13s} {r['address']:42s} " f"{r['eth']:10.6f} {r['usdc']:8.4f} " f"{r['reputation']:6.4f} {str(r['registered']):>5s} {r['reg_tx']}" ) return 0 def main() -> int: args = _parse_args() return asyncio.run(_async_main(args)) if __name__ == "__main__": raise SystemExit(main())