Files
neuron-tai/scripts/devnet_setup.py

109 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Devnet custodial treasury setup (ADR-0015, US-032).
Creates the mock-USDT SPL mint (6 decimals, matching real USDT) and the
treasury associated token account on Solana devnet, then prints/writes the
.env.devnet values. Real USDT exists only on mainnet — the mint address is
config, so mainnet cutover is a config change.
Usage:
python scripts/devnet_setup.py # full setup
python scripts/devnet_setup.py --mint <ADDR> # reuse existing mint
python scripts/devnet_setup.py --mint <ADDR> --mint-to <WALLET> --amount 25
"""
import argparse
import json
import os
import sys
import time
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "packages", "contracts"))
from solders.keypair import Keypair # noqa: E402
from meshnet_contracts.solana_adapter import SolanaCustodialTreasury # noqa: E402
DEFAULT_KEYPAIR = os.path.expanduser("~/.config/solana/meshnet-treasury.json")
DEFAULT_RPC = "https://api.devnet.solana.com"
# placeholder mint for bootstrapping the adapter before the real mint exists
_SYSTEM_PROGRAM = "11111111111111111111111111111111"
def _load_or_create_keypair(path: str) -> Keypair:
if os.path.exists(path):
with open(path, encoding="utf-8") as fh:
return Keypair.from_bytes(bytes(json.load(fh)))
keypair = Keypair()
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as fh:
json.dump(list(bytes(keypair)), fh)
os.chmod(path, 0o600)
print(f"generated treasury keypair: {path}")
return keypair
def _ensure_sol(treasury: SolanaCustodialTreasury, minimum_sol: float = 0.5) -> None:
balance = treasury.get_sol_balance()
if balance >= minimum_sol:
print(f"treasury SOL balance: {balance:.4f}")
return
print(f"airdropping 2 SOL to {treasury.treasury_wallet} (balance {balance:.4f})…")
signature = treasury.request_airdrop(2.0)
for _ in range(30):
time.sleep(2)
if treasury.get_sol_balance() >= minimum_sol:
print("airdrop confirmed")
return
sys.exit(
f"airdrop {signature} not confirmed — devnet faucet may be rate-limited; retry later"
)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--rpc-url", default=DEFAULT_RPC)
parser.add_argument("--keypair", default=DEFAULT_KEYPAIR, help="Treasury keypair path")
parser.add_argument("--mint", default=None, help="Reuse an existing mock-USDT mint address")
parser.add_argument("--mint-to", default=None, metavar="WALLET",
help="Mint mock USDT to a client wallet (creates their token account)")
parser.add_argument("--amount", type=float, default=100.0, help="Amount for --mint-to")
parser.add_argument("--env-out", default=".env.devnet", help="Env file to write")
args = parser.parse_args()
keypair = _load_or_create_keypair(args.keypair)
bootstrap = SolanaCustodialTreasury(args.rpc_url, args.mint or _SYSTEM_PROGRAM, keypair)
print(f"treasury wallet: {bootstrap.treasury_wallet}")
_ensure_sol(bootstrap)
if args.mint:
treasury = SolanaCustodialTreasury(args.rpc_url, args.mint, keypair)
mint_address = args.mint
print(f"using existing mock-USDT mint: {mint_address}")
else:
treasury, mint_address = bootstrap.create_mock_usdt_mint()
print(f"created mock-USDT mint (6 decimals): {mint_address}")
time.sleep(2) # let the mint transaction confirm before creating ATAs
treasury.ensure_token_account(treasury.treasury_wallet)
print(f"treasury token account: {treasury.treasury_token_account}")
if args.mint_to:
signature = treasury.mint_mock_usdt(args.mint_to, args.amount)
print(f"minted {args.amount} mock USDT to {args.mint_to} (tx {signature})")
env = (
f"MESHNET_SOLANA_RPC_URL={args.rpc_url}\n"
f"MESHNET_USDT_MINT={mint_address}\n"
f"MESHNET_TREASURY_KEYPAIR={args.keypair}\n"
f"MESHNET_TREASURY_WALLET={treasury.treasury_wallet}\n"
f"MESHNET_TREASURY_TOKEN_ACCOUNT={treasury.treasury_token_account}\n"
)
with open(args.env_out, "w", encoding="utf-8") as fh:
fh.write(env)
print(f"\nwrote {args.env_out}:\n{env}")
if __name__ == "__main__":
main()