---

title: GaiaFTCL v2 — activation gate

audience: operator_and_recipients

game: WIKI-V2-ACTIVATION-GATE-001

---

v2 activation gate

GaiaFTCL v2 refuses to launch until the operator issues a per-recipient access code and the recipient pastes it into the activation panel on their Mac. The check is runtime, not build-time: every install — including the operator's own Mac — must enter a code through the panel. The signature verify runs offline against an Ed25519 public key embedded in the v2 build.

This page is the end-to-end test we run before every v2 deploy. It is also the recipient-facing tutorial.

---

TL;DR

Step Who Where
1. Mint signing key (once) Operator gaiaftcl admin v2-keygen
2. Recipient installs v2 DMG, launches Recipient macOS
3. Recipient reads their Mac binding hash from the panel Recipient activation panel
4. Recipient emails operator with hash Recipient email
5. Operator issues a Mac-bound code Operator gaiaftcl admin issue-v2-code …
6. Operator emails code to recipient Operator email
7. Recipient pastes code, clicks Activate Recipient activation panel
8. App writes v2_activation.toml at mode 0600 and loads the wallet App offline verify

After step 8 every subsequent launch reads the activation file and skips the panel.

---

What the recipient sees on first launch

When a v2 build runs and ~/Library/Application Support/GaiaFTCL/v2_activation.toml does not exist, the whole app is gated behind this panel — nothing else loads:

!v2 activation panel on first install

The Mac binding hash shown here is an 8-byte salted SHA-256 of the Mac's IOPlatformSerialNumber. The recipient sends this hash to the operator so the operator can issue a code bound to exactly this Mac. The code will refuse to activate on any other machine.

---

Operator: one-time key generation

The operator's Ed25519 signing key is generated once on a Mac the operator controls and never leaves it. The corresponding public key is embedded in each v2 build at V2ActivationGate.embeddedPublicKeyHex.

gaiaftcl admin v2-keygen

Output:

═══ v2 signing key generated ═══
  key_path:        ~/.gaiaftcl/v2_signing_key.toml
  public_key_hex:  e61573715dc01e861292b48b7fe70537a7895ce0ba775876a89dda994d4aba39

The file ~/.gaiaftcl/v2_signing_key.toml is written at mode 0600. Paste the printed public_key_hex into:

cells/xcode/Sources/GaiaFTCLApp/V2ActivationGate.swift
└─ embeddedPublicKeyHex

Then rebuild and sign the v2 DMG:

bash scripts/build_dmg.sh --channel v2-sovereign --version 2.0.1

The resulting DMG is Developer-ID-signed (WWQQB728U5) and ships with the pubkey baked in.

---

Operator: per-recipient code minting

Once the recipient has installed v2 and sent you their Mac binding hash:

gaiaftcl admin issue-v2-code \
    --email alice@example.org \
    --bind-machine-hash <hash from recipient panel> \
    --expires-days 365

Output:

═══ v2 access code issued ═══
  code_id:         v2-f21433bc8976
  email:           alice@example.org
  issued_at_unix:  1780603354
  expires_at_unix: 1812139354
  machine_hash:    <your-machine-hash>
  channel:         v2-sovereign

─── envelope ───
gftcl2.v1.<base64url-payload>.<base64url-signature>

─── email draft ───
To: alice@example.org
Subject: Your GaiaFTCL v2 access code

Paste this into the "Activate v2" panel on the Mac whose
machine_hash equals <your-machine-hash>:

<envelope>

This code is single-recipient. Do not share.

Every issued code is appended to ~/.gaiaftcl/v2_access_codes_issued.toml for audit.

Copy the email draft into your mail client and send it.

---

Recipient: pasting the code

In the activation panel, the recipient:

1. Clicks Paste (or pastes manually into the access-code field).

2. Confirms the Mac binding hash printed in the panel matches the machine_hash line in the operator's email.

3. Clicks Activate.

If the signature verifies against the build's embedded operator pubkey and the machine hash matches, the gate writes the activation file and loads the wallet:

!Wallet UI loaded after activation

That activation file lives at ~/Library/Application Support/GaiaFTCL/v2_activation.toml, mode 0600. Format:

[v2_activation]
activated_at_iso = "2026-06-04T20:12:17Z"
code_id          = "v2-f21433bc8976"
email            = "alice@example.org"
issued_at_unix   = 1780603354
expires_at_unix  = 1812139354
machine_hash     = "<your-machine-hash>"
channel          = "v2-sovereign"
envelope         = "gftcl2.v1.…"

Subsequent launches: gate reads this file, verifies the envelope offline against the embedded pubkey, and skips the panel entirely.

---

Refusal modes

Panel message Cause
REFUSED: that doesn't look like a v2 access code. Envelope is malformed (not gftcl2.v1.<payload>.<sig>).
REFUSED: code payload is malformed. base64 decode failed or payload JSON is corrupt.
REFUSED: signature did not verify against this build's operator key. Wrong pubkey or tampered envelope.
REFUSED: this code is for a different channel. Code was issued for a non-v2-sovereign channel.
REFUSED: this code has expired. Email the operator for a new one. expires_at_unix < now.
REFUSED: this code is bound to a different Mac (machine hash above). Recipient's machine_hash ≠ payload's machine_hash.
REFUSED: this build has no operator public key embedded — rebuild required. Embedded pubkey is still the placeholder 0000….

---

Revocation

On every successful activation and on each subsequent launch, the app makes a best-effort 6-second GET to https://gaiaftcl.com/downloads/v2_revoked_codes.json. The expected shape:

{ "revoked_code_ids": ["v2-f21433bc8976", "v2-…"] }

If the activated code_id appears in that list, the activation file is rejected and the panel returns. To revoke a code, append its code_id to that JSON file on the apex.

Offline activation still works — revocation kicks in next time the recipient's Mac is online when the app boots.

---

Operator dry-run: verifying a minted code

To smoke-test a freshly-issued code without driving the GUI:

gaiaftcl admin verify-v2-code "<envelope>"

Output on success:

pubkey hex (from file): e61573715dc01e861292b48b7fe70537a7895ce0ba775876a89dda994d4aba39
machine_hash (this Mac): <your-machine-hash>
VERIFY OK
  code_id:    v2-f21433bc8976
  email:      alice@example.org
  expires_at: 1812139354
  machineHash: <your-machine-hash>

Exit codes: 0 OK, 7 verify failed (signature/channel/expiry/machine), other codes for setup errors.

---

Resetting activation on a recipient Mac

If a recipient needs to re-activate (machine swap, expired code, manual reset):

rm ~/Library/Application\ Support/GaiaFTCL/v2_activation.toml

The next launch shows the activation panel again.

---

Threat model — what this does and does not stop

Stops:

Does not stop:

---

Quick reference

# Operator
gaiaftcl admin v2-keygen
gaiaftcl admin show-v2-pubkey
gaiaftcl admin issue-v2-code --email <r> --bind-machine-hash <h> --expires-days 365
gaiaftcl admin verify-v2-code "<envelope>"

# Recipient
# (open v2 DMG, drag GaiaFTCL.app to Applications, double-click)
# panel opens → paste code → Activate

# Reset
rm ~/Library/Application\ Support/GaiaFTCL/v2_activation.toml
Federation cosignature: pending operator signing host (v26). Witness (sha256 of rendered body): cf88037e97b9bc06332ed24d5bbd0db7fc5b86bb689220d06b14afe0b803ed8c. This page serves with a substrate-honest pending-signature notice until the operator's Franklin signer cosigns it.