"""Provisioning — turn a freshly-logged-in Google user into a billed account. One entry point: provision(user_id, email, username). 1. derive a unique deposit address from the wallet xpub 2. mint a personal API key (dr_u_) 3. create the per-user gist (5 files, seeded with the free grant) 4. register the user in the master registry 5. return the full record (caller caches the key->user map) Idempotent: if the user is already registered, returns the existing record. """ from __future__ import annotations import hashlib from typing import Optional from .. import wallet from . import registry, user_store FREE_CREDITS = 500 # one-time grant on signup (decision 4B) def _build_key(deposit_address: str) -> tuple[str, str, str]: """Build a user's API key FROM their deposit address. The key is deterministic: it's `sk-` + the checksummed ETH deposit address. Because it's derived from the (always-visible) deposit address, it can be recovered at any time — there's no "lost key" problem. Returns (full_key, display_prefix, sha256_hex_hash). """ full = f"sk-{deposit_address}" # e.g. sk-0xAbC1… (the user's credential) a = deposit_address.lower() # lowercase for a tidy display prefix = f"sk-{a[:6]}…{a[-4:]}" # short display: sk-0xabc1…wxyz digest = hashlib.sha256(full.encode()).hexdigest() # stored for record-keeping return full, prefix, digest async def provision(user_id: str, email: str, username: str) -> Optional[dict]: """Provision a new user OR return their existing record. Returns None if disabled.""" # idempotency: already registered? existing = registry.find_user(user_id) if existing: return existing # need the wallet + registry both configured if not wallet.is_configured(): return None _, reg = await registry.get_or_create() index = reg["wallet"]["next_index"] # this user's permanent derivation slot deposit = wallet.derive_deposit_address(index) full_key, prefix, digest = _build_key(deposit) # key derived from the address identity = { "user_id": user_id, "email": email, "username": username, "deposit_address": deposit, "derivation_index": index, "api_keys": [ {"key_id": "k1", "prefix": prefix, "hash": digest, "created": registry._now(), "active": True} ], } gist_id = await user_store.provision_gist(identity, free_credits=FREE_CREDITS) record = { "user_id": user_id, "email": email, "username": username, "gist_id": gist_id, "deposit_address": deposit, "derivation_index": index, "status": "active", "created_at": registry._now(), "api_key_prefix": prefix, # convenience for display (full key returned once to the caller) "api_key_hash": digest, # for key->user resolution at the gate "_plaintext_key": full_key, # ONLY returned once, then stripped by the caller } await registry.register_user(record) return record