From 599789ff91b82d59117c3b0bf096291dd7a00b2f Mon Sep 17 00:00:00 2001 From: Alex Palmer <24211477+ThatAlexPalmer@users.noreply.github.com> Date: Sun, 13 Sep 2026 13:50:02 -0400 Subject: [PATCH] docs: split TAP Admin from demo factory owner on landing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live Plume factory is still 0xcd6… (32 tables, impl 0xB63…). Owner is 0x366a… (deploy key). TAP Admin is 0x3601…. Stop labeling them as one wallet. --- .env.example | 4 ++-- AGENTS.md | 2 +- WARP.md | 4 ++-- app/src/pages/index.tsx | 20 +++++++++++++++++-- .../content/development/factory-deploy.mdx | 7 ++++--- docs/src/content/development/setup.mdx | 6 +++--- scripts/bootstrap-plume.sh | 8 ++++---- 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index 45f750cc..d4c6e44a 100644 --- a/.env.example +++ b/.env.example @@ -54,6 +54,6 @@ NEXT_PUBLIC_API_URL=http://localhost:8293 # Address (not a key) granted OPERATOR_ROLE on newly created cap tables (createCapTable arg). # Does not require that private key on the server unless the API will sign as operator. -# Wallet-first ADMIN already counts as operator onchain. Demo default often TAP Admin; -# self-hosted TA usually uses the server wallet address matching PRIVATE_KEY when set. +# Wallet-first ADMIN already counts as operator onchain. +# Demo: factory owner / deploy key 0x366a… (not TAP Admin 0x3601…). NEXT_PUBLIC_OPERATOR_ADDRESS=0x366aA809015061C101983900d0c2ebf7d71B96AF diff --git a/AGENTS.md b/AGENTS.md index b73ee4d5..494d8cc8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,7 @@ pnpm app:dev # http://localhost:3000/app (reads app/.en ### Factory mental model (do not confuse) -1. **Protocol builder** — ships BUSL contracts; owns the **shared demo** factory on Plume (`0xcd6…`, owner TAP Admin `0x366a…`). Beacon upgrades for that demo deployment. +1. **Protocol builder** — ships BUSL contracts; owns the **shared demo** factory on Plume (`0xcd6…`, factory owner `0x366a…`). TAP Admin (product wallet) is `0x3601…`. Beacon upgrades for that demo deployment. 2. **Transfer-agent business** — deploys **their own** `CapTableFactory` (`pnpm deploy-factory`). That factory is their book of business (many issuer cap tables). 3. **Issuer ADMIN** — calls `createCapTable` on a factory (permissionless); becomes admin of **that** cap table. Wallet manage UI is this path. Using the shared factory ≠ owning it. 4. **Mongo `factories`** — local mirror only (`factory:register` / deploy auto-register). Not onchain ownership. diff --git a/WARP.md b/WARP.md index 30e5839f..a81587cb 100644 --- a/WARP.md +++ b/WARP.md @@ -35,7 +35,7 @@ The protocol uses a three-tier access model: - **ADMIN_ROLE** (asset manager's wallet): Grants/revokes roles, manages cap table governance. When created via the factory, `msg.sender` receives ADMIN. Admins are implicitly operators (`_checkOperatorRole` checks both roles). - **OPERATOR_ROLE**: Optional extra address granted at mint (`NEXT_PUBLIC_OPERATOR_ADDRESS`). Same day-to-day writes as ADMIN. **Not** the server `PRIVATE_KEY`. The product default is the deployer (ADMIN) operates the instance; admins already satisfy operator checks. -- **Factory owner** (wallet that deployed that factory): Controls the `UpgradeableBeacon`, can upgrade the `CapTable` implementation for ALL proxies of **that** factory via `updateCapTableImplementation()`. Has no access to individual cap tables as admin. On the shared Plume demo factory this is the protocol builder (TAP Admin); a licensed transfer agent should deploy their own factory so they own upgrades. +- **Factory owner** (wallet that deployed that factory): Controls the `UpgradeableBeacon`, can upgrade the `CapTable` implementation for ALL proxies of **that** factory via `updateCapTableImplementation()`. Has no access to individual cap tables as admin. On the shared Plume demo factory this is `0x366a…` (deploy key). TAP Admin is `0x3601…`. A licensed transfer agent should deploy their own factory so they own upgrades. **Three keys (do not conflate):** (1) **Factory owner** — cold/infrequent CLI for deploy + beacon upgrades; never the long-running Docker `PRIVATE_KEY`. (2) **Issuer ADMIN** — browser wallet in `/app` that called `createCapTable`. (3) **Server/operator** — optional root `.env` `PRIVATE_KEY` for server-signed API / `deploy-factory`; `NEXT_PUBLIC_OPERATOR_ADDRESS` is only the **address** granted at mint (no key required on server for wallet-first path). Local `PRIVATE_KEY` is **dev/demo only** (placeholder OK; poller read-only). Full write-up: `docs/src/content/development/setup.mdx` → “Three wallets / keys” (`#three-wallets-keys`). @@ -504,7 +504,7 @@ Libraries: 8. **Mixing `/create` and `/register-onchain` semantics**: `/create` makes the server submit onchain; `/register-onchain` assumes the caller already did. Don't reintroduce a `suppliedId`-style overload on the `/create` route — that pattern was explicitly removed. 9. **Optimistic-state dedupe by stakeholder+stockclass**: Don't. Multiple issuances can exist for the same pair; deduping there hides legitimate in-flight rows. Use a TTL (current: 90s) and let the aggregated holding row absorb the new total once the poller catches up. 10. **MongoDB "Connection ended" log lines are not an error**: they're normal idle connection-pool churn (`connectionCount` ticks down as pooled sockets close). A real failure logs "Error connecting to Mongo". The poller printing `Processing for : ` with an advancing block number means it is healthy. -11. **Factory config has two independent sources — don't conflate them**: the server reads the factory from the Mongo `factories` collection (`deployCapTable` uses `factories[0].factory_address`); the frontend reads `NEXT_PUBLIC_FACTORY_ADDRESS` from `app/.env.local`. The Docker app service gets `NEXT_PUBLIC_*` from compose env (root `.env`). `pnpm app:dev` reads only `app/.env.local` — keep both files aligned. The factory address is deployment-specific (deployer wallet + nonce) and the implementation is an **upgradeable** beacon target, so **never hardcode them**: `pnpm deploy-factory` auto-registers both from the real deploy, and `pnpm factory:register --factory ` reads the current implementation from the factory onchain (`upsertFactory` keeps a single record — one operator factory, many cap tables). Keep the Mongo factory and `app/.env.local` on the same address. A factory's **owner** (the wallet that deployed it) controls beacon upgrades for all its cap tables. On the shared Plume demo factory that is TAP Admin (`0x366a…`), not your issuer wallet. Only reuse a factory whose owner wallet you control. +11. **Factory config has two independent sources — don't conflate them**: the server reads the factory from the Mongo `factories` collection (`deployCapTable` uses `factories[0].factory_address`); the frontend reads `NEXT_PUBLIC_FACTORY_ADDRESS` from `app/.env.local`. The Docker app service gets `NEXT_PUBLIC_*` from compose env (root `.env`). `pnpm app:dev` reads only `app/.env.local` — keep both files aligned. The factory address is deployment-specific (deployer wallet + nonce) and the implementation is an **upgradeable** beacon target, so **never hardcode them**: `pnpm deploy-factory` auto-registers both from the real deploy, and `pnpm factory:register --factory ` reads the current implementation from the factory onchain (`upsertFactory` keeps a single record — one operator factory, many cap tables). Keep the Mongo factory and `app/.env.local` on the same address. A factory's **owner** (the wallet that deployed it) controls beacon upgrades for all its cap tables. On the shared Plume demo factory that is `0x366a…` (deploy key), not TAP Admin `0x3601…`. Only reuse a factory whose owner wallet you control. 12. **Docker Next rewrites vs browser**: `NEXT_PUBLIC_API_URL` drives Next **server-side** `/api/*` rewrites. In the Docker app container use `http://server:8293`. Host `pnpm app:dev` uses `http://localhost:8293` in `app/.env.local`. Wrong value → mint onchain succeeds but register shows Internal Server Error. 13. **Issuing a stakeholder's first stock**: the Issue Stock dropdown needs the issuer's stakeholders, so `GET /cap-table/holdings/stock` returns `stakeholders` (and `stockClasses`) — the manage UI can populate the dropdown before any issuance exists. Don't source the stakeholder list only from `holdings[]`; it's empty until stock is issued, which would make a fresh cap table unable to issue its first shares after a page reload. 14. **Nav issuer id**: company section links must use the real UUID from `router.query.issuerId` (or path), never a pattern string from `pathname` — otherwise users land on `/app/companies/%5BissuerId%5D`. diff --git a/app/src/pages/index.tsx b/app/src/pages/index.tsx index a56a086f..c56a2860 100644 --- a/app/src/pages/index.tsx +++ b/app/src/pages/index.tsx @@ -62,9 +62,25 @@ export default function Home() { - TAP Admin / factory owner (demo) + TAP Admin - + + 0x3601a913fD3466f30f5ABb978E484d1B37Ce995D + + + + + Factory owner (beacon upgrades) + + 0x366aA809015061C101983900d0c2ebf7d71B96AF diff --git a/docs/src/content/development/factory-deploy.mdx b/docs/src/content/development/factory-deploy.mdx index 17ccf7a8..4d67c099 100644 --- a/docs/src/content/development/factory-deploy.mdx +++ b/docs/src/content/development/factory-deploy.mdx @@ -6,7 +6,7 @@ A **CapTableFactory** is the onchain house for a transfer-agent business: it dep | Role | Meaning | | --- | --- | -| Factory owner | Wallet that deployed the factory (beacon upgrades). For the shared Plume demo this is TAP Admin — not every issuer wallet. | +| Factory owner | Wallet that deployed the factory (beacon upgrades). For the shared Plume demo this is `0x366a…`, not TAP Admin `0x3601…`. | | Issuer ADMIN | Wallet that called `createCapTable` on a factory. | | Mongo register | Local DB row so *this* API knows which factory address to use — not ownership. | @@ -62,8 +62,9 @@ REUSE_TAP_FACTORY=1 pnpm bootstrap | Actor / deployment | Address | Capability | | --- | --- | --- | -| Shared demo `CapTableFactory` | `0xcd6Df14406b0569ceEABa884A18717774EdeaCA1` | Permissionless issuer cap-table creation | -| Factory owner (TAP Admin) | `0x366aA809015061C101983900d0c2ebf7d71B96AF` | Beacon upgrades for this factory | +| TAP Admin | `0x3601a913fD3466f30f5ABb978E484d1B37Ce995D` | Product / issuer wallet (mints cap tables through the demo factory) | +| Shared demo `CapTableFactory` | `0xcd6Df14406b0569ceEABa884A18717774EdeaCA1` | Permissionless issuer cap-table creation (32 tables; live impl `0xB63C…`) | +| Factory owner | `0x366aA809015061C101983900d0c2ebf7d71B96AF` | Beacon upgrades for this factory (deploy key, not TAP Admin) | You can mint issuer cap tables through this factory (`createCapTable` is permissionless). **You do not own upgrades.** Production transfer agents should deploy their own factory. diff --git a/docs/src/content/development/setup.mdx b/docs/src/content/development/setup.mdx index 1a841133..d44abcf3 100644 --- a/docs/src/content/development/setup.mdx +++ b/docs/src/content/development/setup.mdx @@ -23,7 +23,7 @@ These are easy to conflate. They are **not** the same wallet. - **Wallet-first `/app` UI** — ADMIN signs in the browser. Poller needs RPC only. Leave `PRIVATE_KEY=UPDATE_ME` (or unset). Server runs read-only for chain writes; that is expected. ADMIN alone is enough for the manage UI. - **`NEXT_PUBLIC_OPERATOR_ADDRESS`** — an **address** granted `OPERATOR_ROLE` on **new** mints (for server/automation later). Setting it does **not** put a key on the server. You only need that private key in `.env` if the API itself will sign as operator. - **Factory owner key** — never put this in the long-running Docker/API env. Compromise would let an attacker upgrade every cap table on that factory. Inject only for a one-shot `pnpm deploy-factory` or upgrade script (separate env file / CI secret), then remove. -- **Shared Plume demo factory** (`0xcd6…`) — owner is TAP Admin (`0x366a…`), not your issuer wallet. Reusing it does not make you factory owner. +- **Shared Plume demo factory** (`0xcd6…`) — factory owner is `0x366a…` (deploy key). TAP Admin is `0x3601…`. Reusing the factory does not make you factory owner. `PRIVATE_KEY` in local `.env` is **dev/demo only**. Optional for the wallet-first path. Never put production or factory-owner keys here. Do not commit real keys. @@ -56,7 +56,7 @@ Wallet connection is first-party (EIP-6963). Install a browser extension (Rabby, ## Bootstrap the stack ```bash -# Demo / issuer-dev: register TAP’s shared Plume factory in Mongo (owner is TAP Admin, not you) +# Demo / issuer-dev: register TAP’s shared Plume factory in Mongo (owner is 0x366a…, not TAP Admin 0x3601…) REUSE_TAP_FACTORY=1 pnpm bootstrap # Transfer-agent path: after bootstrap with no factory, deploy your own instead @@ -77,7 +77,7 @@ Prefer **host** `pnpm app:dev` for wallet work. Docker app is optional. | Path | Command | You own beacon upgrades? | | --- | --- | --- | -| Shared demo factory | `REUSE_TAP_FACTORY=1 pnpm bootstrap` | **No** (protocol builder / TAP Admin) | +| Shared demo factory | `REUSE_TAP_FACTORY=1 pnpm bootstrap` | **No** (factory owner `0x366a…`) | | Your TA factory | `pnpm deploy-factory` | **Yes** | Issuers can mint cap tables on either factory (`createCapTable` is permissionless). Using the demo factory is normal for local product work; production transfer agents should deploy their own. diff --git a/scripts/bootstrap-plume.sh b/scripts/bootstrap-plume.sh index d9e6b291..1fd62f04 100755 --- a/scripts/bootstrap-plume.sh +++ b/scripts/bootstrap-plume.sh @@ -14,7 +14,7 @@ set -e # 7. Bring up mongodb + server (+ app) via docker compose # 8. Wait for API health # 9. Factory: never hardcode impl. Deploy your own (pnpm deploy-factory) or -# REUSE_TAP_FACTORY=1 to register the shared demo factory (owner = TAP Admin). +# REUSE_TAP_FACTORY=1 to register the shared demo factory (owner = 0x366a deploy key). # # Overrides: API_URL, REUSE_TAP_FACTORY=1, SKIP_APP=1 (mongo+server only). # @@ -26,7 +26,7 @@ ROOT_DIR="$(dirname "$SCRIPT_DIR")" cd "$ROOT_DIR" # Shared demo CapTableFactory on Plume (protocol-builder demo deployment). -# Factory owner (beacon upgrades) is TAP Admin 0x366a… — NOT your issuer wallet. +# Factory owner (beacon upgrades) is 0x366a… (deploy key). TAP Admin is 0x3601…. # createCapTable is permissionless; issuers mint cap tables through this factory # without owning it. Licensed TAs should deploy their OWN factory instead. TAP_FACTORY_ADDRESS="0xcd6Df14406b0569ceEABa884A18717774EdeaCA1" @@ -130,7 +130,7 @@ if [ "${FACTORY_COUNT:-0}" -gt 0 ]; then echo "✅ Factory already registered in Mongo (count=$FACTORY_COUNT) — leaving as-is." elif [ "${REUSE_TAP_FACTORY:-0}" = "1" ]; then echo "🌱 REUSE_TAP_FACTORY=1 — registering shared demo factory $TAP_FACTORY_ADDRESS (impl read onchain)..." - echo " Beacon upgrades stay with that factory's owner (TAP Admin / protocol builder)." + echo " Beacon upgrades stay with that factory's owner (0x366a… deploy key)." echo " This is the demo/issuer-dev path — not the same as owning a transfer-agent factory." pnpm factory:register --factory "$TAP_FACTORY_ADDRESS" else @@ -138,7 +138,7 @@ else echo " Transfer-agent / production path — deploy YOUR factory (you become owner):" echo " pnpm deploy-factory # CapTable + CapTableFactory + auto Mongo register" echo " Then set NEXT_PUBLIC_FACTORY_ADDRESS in app/.env.local to the printed factory address." - echo " Demo/issuer-dev path — reuse TAP's shared Plume factory (owner is TAP Admin, not you):" + echo " Demo/issuer-dev path — reuse TAP's shared Plume factory (owner is 0x366a…, not TAP Admin 0x3601…):" echo " REUSE_TAP_FACTORY=1 pnpm bootstrap" fi