polyglot-alpha / scripts /fund_seeder_wallets_v2.py
licaomeng
deploy: main@8970ffb → HF Spaces (2026-05-27T05:19Z)
88d2f2a
"""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())