# NxtGateW — Operations runbook

## Deploy checklist

1. Set environment detection correctly (`config/environment.php` → local vs production `APP_URL`)
2. Configure database credentials in `config/` for the target environment  
   - Local MAMP: `config/database.local.php` → `DB_HOST` = `127.0.0.1:8889`
3. Apply migrations (tracks applied files in `schema_migrations`):
   ```bash
   NXTGATEW_ENV=local php database/migrate.php
   # or only the latest sync / client-mode files:
   NXTGATEW_ENV=local php database/migrate.php --from=015
   ```
   - `014_wallet_lifecycle.sql` — `wallet_lifecycle` (created / funded / split)
   - `015_wallet_client_mode.sql` — `wallet_mode` + `client_external_id`
   - `016_sync_schema_mysql8.sql` — brings legacy DBs up to full wallets / movements / ledger / webhook snapshot columns (MySQL 8 safe)
   - `022_split_complete_default_idle.sql` — new wallets idle until first deposit
   - `023_wallet_index_needed.sql` — `index_needed` queue for sync cron
   - `024_wallet_merchant_transaction_id.sql` — `wallets.merchant_transaction_id`
   - `025_merchant_transaction_id_entities.sql` — `transaction_id` on movements / ledger / webhook log
   - `026_wallet_payment_intents.sql` — multi-order queue (`wallet_payment_intents`, `pending_merchant_transaction_id`)
   - `018_nft_auth_webhooks.sql` — HMAC nonces + NFT webhook endpoints
4. Confirm `CRON_SECRET_TOKEN` in `config/config.php` (or env) matches what cron URLs use
5. Configure explorer API key: `ETHERSCAN_API_KEY` (Apache `SetEnv` / PHP-FPM pool / `config/explorer.local.php`)
6. Configure gas / platform private keys for EVM splits: `GAS_PRIVATE_KEY_POLYGON` / `GAS_PRIVATE_KEY_ETHEREUM` (or historical `PLATFORM_PRIVATE_KEY_*`)
7. Ensure PHP extensions: **bcmath** or **gmp**, plus **curl** for webhook delivery  
   - MAMP often has **bcmath** only — `includes/gmp_bcmath_polyfill.php` (loaded from `config/config.php`) covers GMP calls
8. Install Node deps for wallet generation: `cd contracts && npm install` (needs `ethers`)
9. Local seed logins (after password reset / fresh dump):  
   - `admin@nxtgatew.com` / `Admin123!`  
   - `merchant@nxtgatew.com` / `Merchant123!`

## Cron jobs (web URL)

All web crons expect `?token=` (or POST body) matching `CRON_SECRET_TOKEN`.

| Script | Purpose |
|--------|---------|
| `cron/migrate-schema-web.php` | Apply pending SQL migrations + schema audit (HTTP) |
| `cron/sync-wallet-movements-web.php` | Pull explorer movements into `wallet_movements` / ledger |
| `cron/split-payments-web.php` | Execute on-chain splits for funded wallets |
| `cron/dispatch-wallet-webhooks-web.php` | Retry failed `wallet.balance_changed` / snapshot webhooks |
| `cron/process-nft-operations-web.php` | Broadcast & confirm NFT mints (on-chain) + deliver NFT webhooks |

CLI equivalents (when available): `php cron/dispatch-wallet-webhooks.php`, etc.

Suggested cadence:

| Cron | Cadence |
|------|---------|
| sync-wallet-movements | every **2–5 min** (`&limit=8` — avoids nginx 504) |
| split-payments | every **2 min** (offset from sync) — networks: `ethereum`, `polygon`, `amoy` |
| dispatch-wallet-webhooks | every **2–5 min** |
| process-nft-operations | every **1–2 min** |

Example:

```bash
# Explorer sync is slow (3 API calls × passes × wallet). Always pass limit= on HTTP cron.
curl -sS "https://nxtgatew.com/cron/migrate-schema-web.php?token=$CRON_SECRET_TOKEN"
curl -sS "https://nxtgatew.com/cron/sync-wallet-movements-web.php?token=$CRON_SECRET_TOKEN&limit=8&max_runtime=45"
curl -sS "https://nxtgatew.com/cron/split-payments-web.php?token=$CRON_SECRET_TOKEN&limit=60"
curl -sS "https://nxtgatew.com/cron/dispatch-wallet-webhooks-web.php?token=$CRON_SECRET_TOKEN"
curl -sS "https://nxtgatew.com/cron/process-nft-operations-web.php?token=$CRON_SECRET_TOKEN&limit=20"
```

If sync still returns **504 Gateway Time-out**, lower further (`limit=4`) or increase Plesk/nginx `fastcgi_read_timeout` / `proxy_read_timeout` to ≥90s. Response JSON includes `has_more` when the park was not fully covered this run.

Sync cron writes **`logs/sync-wallet-movements.log`** (split cron uses `logs/split-payments.log`). Bulk sync default queue is **`index_needed_only`** (`wallets.index_needed = 1`) — set after deposit/split, cleared when movements ledger is reconciled. JSON: `wallets_eligible` = index queue size; `wallets_active_total` = full park. Override with `&all_active=1`. Run migration `023_wallet_index_needed.sql` on deploy.

## Explorer & gas

| Concern | Notes |
|---------|-------|
| **Etherscan V2** | Used for Ethereum + Polygon via `chainid`. Key via `ETHERSCAN_API_KEY` |
| **Gas wallet** | Funds deposit wallets with native gas before ERC-20 splits; private key must match network constants |
| **Reconciled wallets** | Resync still runs periodically so new deposits on client wallets reopen the cycle |

## Ledger repair

After migration or data repair:

```bash
php cron/rebuild-balance-history.php --full
```

This truncates history and replays movements — per-movement webhooks may fire again.

## Incident response

| Symptom | Action |
|---------|--------|
| Client create returns 500 about migration 015 | Apply `015_wallet_client_mode.sql` |
| Client create returns 409 | Rare race on duplicate address — client reuse normally returns **200** (`payment_queued` if cycle in progress) |
| Client create returns 503 transaction_id migration | Apply `024_wallet_merchant_transaction_id.sql` |
| Sync 500 wallet_payment_intents | Deploy `includes/wallet_payment_intents.php` + migration `026` |
| No webhooks | Check wallet `callback`, `ipn_token`, then dispatch cron / `webhook_status` |
| Movements empty | Verify `ETHERSCAN_API_KEY`, network, and sync cron |
| Split stuck | Check gas wallet balance / private key env, then split cron logs |
| 401 on merchant API | Confirm both `X-API-Key` and `X-API-Secret`, account `active` |
| NFT mint stuck `pending` | Run `process-nft-operations-web.php`; check operator key / gas / `contract_address` / RPC |
| NFT HMAC always classic-auth | Ensure `X-Api-Signature` is non-empty (LibreSSL: `openssl dgst -hex \| awk '{print $NF}'`) |
| NFT webhooks not delivered | Register endpoint or wallet `callback`; run NFT cron; avoid httpbin timeouts |

## NFT / Amoy → Polygon (on-chain)

1. Migrations `017` + `018` + `020` (wallets.network `amoy`) applied on target DB  
2. Compile & deploy:
   ```bash
   cd contracts && npm install && npx hardhat compile
   export PRIVATE_KEY=0x…          # deployer (funded)
   export AMOY_RPC_URL=https://rpc-amoy.polygon.technology
   OWNER=0x… OPERATOR=0x… npm run deploy:membership:amoy
   # also: deploy:license:amoy · deploy:membership:polygon · deploy:membership:ethereum
   ```
   Script prints `api_network` + PATCH hint; JSON saved under `contracts/deployments/`.
3. Create collection with `"network": "amoy"` (or `polygon` / `ethereum`), then  
   `PATCH /api/v1/nft/collections/{id}` with `{ "contract_address": "0x…" }`  
4. Env (Apache SetEnv / FPM / `config/nft.local.php` from example):
   - Operator = **gas wallet** (same as deposit top-ups / splits): `GAS_PRIVATE_KEY_POLYGON` + `GAS_WALLET_POLYGON`  
     Optional override only: `NFT_OPERATOR_PRIVATE_KEY`
   - `NFT_MINT_CONFIRMATIONS` (default `2`) — blocks before `nft.minted` (ops may sit in `awaiting_confirmation`)
   - Optional: `NFT_HMAC_RATE_LIMIT` (default `120` signed req/min/merchant)
   - Optional: `NFT_LIFECYCLE_ONCHAIN=0` to force DB-only suspend/revoke/burn
   - `AMOY_RPC_URL` / `RPC_URL_POLYGON` / `ETHEREUM_RPC_URL` if not using defaults
5. Preflight: `php tools/check-nft-amoy-ready.php` (RPC, key, schema, lifecycle script, operator balance)  
   Verify contract: `cd contracts && npm run verify:membership:amoy`  
   Published Amoy addresses: `docs/AMOY_DEPLOYMENTS.md`  
6. Mint → cron → explorer tx → webhook `nft.minted`  
7. Lifecycle (minted assets): suspend/reactivate/revoke/burn → `lifecycle-nft.js` then DB (`502 CHAIN_TX_FAILED` on revert)  
8. Schedule cron: see `docs/crontab.nft.example` (every 1–2 min)  
9. Admin: `/admin/nfts` · Merchant UI: `/merchant/nfts` (read-only)  
10. Keep deposit `POST /api/v1/wallets/create` unchanged (client `private_key` still returned)

## Merchant docs

- Hub: `/docs`
- Console: `/docs/merchant-api` (Wallets + **NFT** collections)
- Guide: `/docs/merchant-api/guide` (includes `#nft`)
- NFT: guide `#nft` · console `/docs/merchant-api` · backlog `docs/NFT_TODO.md`
- OpenAPI: `/docs/openapi.yaml`
- Markdown: `docs/MERCHANT_API.md` · `docs/NFT_API.md`
