Spaces:
Sleeping
Sleeping
File size: 13,032 Bytes
88d2f2a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | """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())
|