polyglot-alpha / scripts /deploy_all_contracts.py
licaomeng
deploy: main@8970ffb → HF Spaces (2026-05-27T05:19Z)
88d2f2a
"""Deploy the PolyglotAlpha v2 contract suite to Arc testnet.
Deploys (in dependency order):
1. ReputationRegistry (no constructor args)
2. TranslationAuction (usdc, reputationRegistry)
3. BuilderFeeRouter (usdc, reputationRegistry)
4. JudgePanel (usdc) -- NEW (README §5.6 / §5.22)
QuestionRegistry is **not** redeployed by default — it has no incoming wiring
to the contracts in this MR (per README §5.7 it is its own provenance log,
written to from the orchestrator's settlement step). To redeploy it pass
``--include-question-registry``.
After deployment the script wires up authorization:
reputation.setAuthorized(translationAuction, true)
reputation.setAuthorized(builderFeeRouter, true)
reputation.setAuthorized(judgePanel, true) -- so slashReputation
can be invoked
And it prints copy-pasteable ``.env`` lines.
Usage::
.venv/bin/python scripts/deploy_all_contracts.py
Environment::
ARC_TESTNET_RPC (default https://rpc.testnet.arc.network)
ARC_CHAIN_ID (default 5042002)
HACKATHON_WALLET_PRIVATE_KEY (required)
ARC_TESTNET_USDC_ADDRESS (required)
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
from pathlib import Path
from eth_account import Account
from web3 import Web3
REPO_ROOT = Path(__file__).resolve().parents[1]
FOUNDRY_OUT = REPO_ROOT / "contracts" / "out"
def load_artifact(name: str) -> dict:
"""Load a Foundry artifact (ABI + bytecode) from contracts/out/."""
path = FOUNDRY_OUT / f"{name}.sol" / f"{name}.json"
with path.open("r", encoding="utf-8") as fh:
art = json.load(fh)
return {
"abi": art["abi"],
"bytecode": art["bytecode"]["object"],
}
def wait_receipt(w3: Web3, tx_hash: bytes, timeout: int = 180) -> dict:
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout)
if receipt.status != 1:
raise RuntimeError(f"tx failed: {tx_hash.hex()}")
return receipt
def send_signed(w3: Web3, txn: dict, account) -> dict:
signed = w3.eth.account.sign_transaction(txn, account.key)
raw = getattr(signed, "raw_transaction", None) or signed.rawTransaction
tx_hash = w3.eth.send_raw_transaction(raw)
return wait_receipt(w3, tx_hash)
def deploy_contract(
w3: Web3,
account,
chain_id: int,
name: str,
constructor_args: tuple = (),
) -> tuple[str, int]:
"""Deploy a single contract; returns (address, gas_used)."""
art = load_artifact(name)
contract = w3.eth.contract(abi=art["abi"], bytecode=art["bytecode"])
nonce = w3.eth.get_transaction_count(account.address)
txn = contract.constructor(*constructor_args).build_transaction(
{
"from": account.address,
"nonce": nonce,
"chainId": chain_id,
"gasPrice": w3.eth.gas_price,
"gas": 4_500_000,
}
)
print(f" -> deploying {name}({', '.join(map(str, constructor_args))}) ...")
receipt = send_signed(w3, txn, account)
addr = receipt.contractAddress
print(f" deployed at {addr} (gas {receipt.gasUsed})")
return addr, receipt.gasUsed
def authorize(
w3: Web3,
account,
chain_id: int,
rep_address: str,
rep_abi: list,
who: str,
label: str,
) -> int:
"""Call ReputationRegistry.setAuthorized(who, true); returns gas used."""
contract = w3.eth.contract(address=Web3.to_checksum_address(rep_address), abi=rep_abi)
nonce = w3.eth.get_transaction_count(account.address)
txn = contract.functions.setAuthorized(
Web3.to_checksum_address(who), True
).build_transaction(
{
"from": account.address,
"nonce": nonce,
"chainId": chain_id,
"gasPrice": w3.eth.gas_price,
"gas": 120_000,
}
)
print(f" -> setAuthorized({label} = {who}, true) ...")
receipt = send_signed(w3, txn, account)
print(f" authorized (gas {receipt.gasUsed})")
return receipt.gasUsed
def main() -> int:
parser = argparse.ArgumentParser(description="Deploy PolyglotAlpha v2 contracts")
parser.add_argument(
"--include-question-registry",
action="store_true",
help="Also redeploy QuestionRegistry (not normally required for §5.6 corrections).",
)
args = parser.parse_args()
pk = os.environ.get("HACKATHON_WALLET_PRIVATE_KEY")
if not pk:
print("ERROR: HACKATHON_WALLET_PRIVATE_KEY is required", file=sys.stderr)
return 2
usdc_address = os.environ.get(
"ARC_TESTNET_USDC_ADDRESS",
"0x477fC4C3DcC87C3Ceb13adc931F6bBeDAcCa391D",
)
rpc_url = os.environ.get("ARC_TESTNET_RPC", "https://rpc.testnet.arc.network")
chain_id = int(os.environ.get("ARC_CHAIN_ID", "5042002"))
w3 = Web3(Web3.HTTPProvider(rpc_url))
if not w3.is_connected():
print(f"ERROR: cannot reach Arc RPC at {rpc_url}", file=sys.stderr)
return 3
account = Account.from_key(pk)
print(f"deployer: {account.address}")
print(
f"balance: {w3.from_wei(w3.eth.get_balance(account.address), 'ether')} ETH-equivalent"
)
print(f"chain id: {chain_id}")
print(f"usdc: {usdc_address}")
print()
deployed: dict[str, str] = {}
gas_used: dict[str, int] = {}
# 1. ReputationRegistry
rep_addr, gas = deploy_contract(
w3, account, chain_id, "ReputationRegistry"
)
deployed["ReputationRegistry"] = rep_addr
gas_used["ReputationRegistry"] = gas
# 2. TranslationAuction
auct_addr, gas = deploy_contract(
w3,
account,
chain_id,
"TranslationAuction",
(Web3.to_checksum_address(usdc_address), Web3.to_checksum_address(rep_addr)),
)
deployed["TranslationAuction"] = auct_addr
gas_used["TranslationAuction"] = gas
# 3. BuilderFeeRouter
router_addr, gas = deploy_contract(
w3,
account,
chain_id,
"BuilderFeeRouter",
(Web3.to_checksum_address(usdc_address), Web3.to_checksum_address(rep_addr)),
)
deployed["BuilderFeeRouter"] = router_addr
gas_used["BuilderFeeRouter"] = gas
# 4. JudgePanel (NEW per §5.6 / §5.22)
panel_addr, gas = deploy_contract(
w3, account, chain_id, "JudgePanel",
(Web3.to_checksum_address(usdc_address),),
)
deployed["JudgePanel"] = panel_addr
gas_used["JudgePanel"] = gas
if args.include_question_registry:
qr_addr, gas = deploy_contract(w3, account, chain_id, "QuestionRegistry")
deployed["QuestionRegistry"] = qr_addr
gas_used["QuestionRegistry"] = gas
# 5. Wire up authorization on ReputationRegistry so the three downstream
# contracts can push updates / slash. The deployer is already authorized.
rep_art = load_artifact("ReputationRegistry")
for label, addr in (
("TranslationAuction", auct_addr),
("BuilderFeeRouter", router_addr),
("JudgePanel", panel_addr),
):
gas = authorize(
w3, account, chain_id, rep_addr, rep_art["abi"], addr, label
)
gas_used[f"auth({label})"] = gas
final_balance = w3.from_wei(w3.eth.get_balance(account.address), "ether")
print()
print("=== Deployment summary ===")
for name, addr in deployed.items():
print(f" {name}: {addr} (gas {gas_used[name]})")
print()
print("=== Gas used (authorization) ===")
for label, g in gas_used.items():
if label.startswith("auth("):
print(f" {label}: {g}")
print()
total_gas = sum(gas_used.values())
print(f"total gas: {total_gas}")
print(f"final balance: {final_balance} ETH-equivalent")
print()
print("=== Suggested .env updates ===")
print(f"REPUTATION_REGISTRY_ADDRESS={rep_addr}")
print(f"TRANSLATION_AUCTION_ADDRESS={auct_addr}")
print(f"BUILDER_FEE_ROUTER_ADDRESS={router_addr}")
print(f"JUDGE_PANEL_ADDRESS={panel_addr}")
if "QuestionRegistry" in deployed:
print(f"QUESTION_REGISTRY_ADDRESS={deployed['QuestionRegistry']}")
print()
# Dump a JSON file alongside for programmatic consumers.
out_path = REPO_ROOT / "outputs" / "deployment_v2.json"
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(
json.dumps(
{
"chain_id": chain_id,
"deployer": account.address,
"usdc": usdc_address,
"deployed_at": int(time.time()),
"addresses": deployed,
"gas_used": gas_used,
},
indent=2,
)
)
print(f"wrote {out_path}")
return 0
if __name__ == "__main__":
sys.exit(main())