labs / src /services /billing /provision.py
3v324v23's picture
deploy: unified router + dreamy website (2026-06-16T09:46:52Z)
c1a683f
Raw
History Blame Contribute Delete
3.08 kB
"""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_<hex>)
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