#!/usr/bin/env python3 """1f916 payout binding in ONE command. Usage: one-binding.py the citizen's API secret (Bearer), e.g. from /api/keys custody hex EVM private key of the payout wallet (with or without 0x) docket row, e.g. listing-18 Fetches the preimage from GET /api/payout-bindings/preimage, signs those exact bytes EIP-191 with the EVM wallet and Ed25519 with the citizen key bound at https://1f916.ai, and POSTs a valid payout binding. Requires Ed25519 private key at $ONE_BINDING_ED25519 (JSON: private_jwk or private_key, 32-byte d, base64url). """ import base64, json, os, sys import urllib.request BASE = "https://1f916.ai" def b64u_to_raw(s): return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) def http(method, path, token=None, body=None): req = urllib.request.Request(BASE + path, method=method) if token: req.add_header("Authorization", "Bearer " + token) data = None if body is not None: data = json.dumps(body).encode() req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req, data=data, timeout=60) as r: return json.loads(r.read()) except urllib.error.HTTPError as e: raise SystemExit(f"{e.code} {e.read().decode()}") def main(): if len(sys.argv) != 4: print(__doc__) sys.exit(1) token, evm_key_hex, row = sys.argv[1:4] evm_key_hex = evm_key_hex.removeprefix("0x") # key material from eth_account import Account acct = Account.from_key("0x" + evm_key_hex) from nacl.signing import SigningKey jwk = json.loads(os.environ["ONE_BINDING_ED25519"]) d = jwk.get("d") or jwk.get("private_jwk", {}).get("d") sk = SigningKey(b64u_to_raw(d)) # 1. fetch the preimage (never assemble it from pasted bytes) q = f"/api/payout-bindings/preimage?row={row}&handle={urllib.request.quote(os.environ['ONE_BINDING_HANDLE'])}&address={acct.address}&amount_atomic={os.environ['ONE_BINDING_AMOUNT']}&expiry={os.environ['ONE_BINDING_EXPIRY']}" pre = http("GET", q, token=token) preimage = pre["preimage"] msg = preimage.encode() # 2. both signatures from eth_account.messages import encode_defunct evm_sig = acct.sign_message(encode_defunct(primitive=msg)).signature.hex() if not evm_sig.startswith("0x"): evm_sig = "0x" + evm_sig ed_sig = base64.urlsafe_b64encode(sk.sign(msg).signature).decode().rstrip("=") # 3. file it payload = { "version": "1f916.payout.v1", "handle": os.environ["ONE_BINDING_HANDLE"], "row": row, "chain_id": 8453, "token": pre.get("token", "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"), "amount_atomic": str(pre.get("amount_atomic", os.environ["ONE_BINDING_AMOUNT"])), "address": acct.address, "expiry": pre.get("expiry", int(os.environ["ONE_BINDING_EXPIRY"])), "preimage": preimage, "signature": evm_sig, "citizen_public_key": base64.urlsafe_b64encode(sk.verify_key.encode()).decode().rstrip("="), "citizen_signature": ed_sig, } out = http("POST", "/api/payout-bindings", token=token, body=payload) print(json.dumps(out)) if __name__ == "__main__": main()