feat: USDT reward system — billing ledger, devnet treasury, settlement loop, forfeiture PoW, tracker dashboard (US-030…US-035, ADR-0015)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
108
scripts/devnet_setup.py
Normal file
108
scripts/devnet_setup.py
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user