diff --git a/docs/PAYRAM_HEADLESS_AGENT.md b/docs/PAYRAM_HEADLESS_AGENT.md index 1a5b8fb..238efad 100644 --- a/docs/PAYRAM_HEADLESS_AGENT.md +++ b/docs/PAYRAM_HEADLESS_AGENT.md @@ -110,7 +110,7 @@ Run from repo root: `./setup_payram_agents.sh [command]` | `status` | Check API reachable and auth (token saved / valid) | | `setup` | First-time: register root user + create default project | | `signin` | Sign in; saves token to `.payraminfo/headless-tokens.env` | -| `ensure-config` | Seed `payram.frontend` and `payram.backend` for local API (needed for payment creation) | +| `ensure-config` | Ensure the canonical server URL (`payram.server.url` via `/system/site-url`) is set — payment-link creation 500s without it. Falls back to the legacy `payram.frontend`/`payram.backend` keys on older cores | | `ensure-wallet` | Create random BTC wallet or link existing to project (for payment links) | | `deploy-scw` | Deploy ETH/EVM smart-contract deposit wallet; then auto-link to project | | `deploy-scw-flow` | Generate mnemonic -> fund deployer -> balance check -> deploy SCW | @@ -133,7 +133,7 @@ Set these for non-interactive or scripted runs. For agents, prefer env-driven, n | `PAYRAM_PAYMENT_EMAIL` | — | Customer email for payment link | | `PAYRAM_PAYMENT_AMOUNT` | `10` | Amount in USD for payment link | | `PAYRAM_CUSTOMER_ID` | from signin | Usually from token file after signin | -| `PAYRAM_FRONTEND_URL` | `http://localhost` | Used by ensure-config (local) | +| `PAYRAM_FRONTEND_URL` | `http://localhost` | Used by ensure-config's legacy fallback only (older cores; current cores derive the URL from the request) | | `PAYRAM_NETWORK` | `mainnet` | One-step flow network selection (`mainnet` default - real payments; `testnet` to try with free coins) | | `PAYRAM_NODE_MODE` | `docker` | JS runtime: `docker` or `host` | | `PAYRAM_NODE_DOCKER_IMAGE` | `node:20-bullseye-slim` | Docker image used for JS scripts | @@ -213,7 +213,7 @@ The one-step flow does: 2. Install or restart PayRam using `setup_payram.sh` (fresh install asks its one-time questions in the terminal; see Prerequisites). 3. Re-reads `config.env` and waits for API readiness at the real port. 4. Auth (`setup` if no root user, else `signin`). -5. `ensure-config` for local frontend/backend settings. +5. `ensure-config` so the server URL (`payram.server.url`) is set for payment links. 6. Wallet flow (MVF: **USDC on Base**): - Default: EVM smart-contract wallet deploy (blocking, guided gas funding). The master (deployer) wallet is generated locally and is **ops-only** - diff --git a/scripts/deploy-scw-eth.js b/scripts/deploy-scw-eth.js index 7af759a..59cd9f7 100644 --- a/scripts/deploy-scw-eth.js +++ b/scripts/deploy-scw-eth.js @@ -88,8 +88,10 @@ async function main() { const factoryAddress = factoryData.address; const factoryId = factoryData.id; const abi = typeof factoryData.abi === 'string' ? JSON.parse(factoryData.abi) : factoryData.abi; - if (!factoryAddress || !abi) { - console.error('Factory contract missing address or abi'); + // factoryId is part of the registration URL after the deploy tx — validate + // it here so a malformed response can't burn gas and then POST to /undefined. + if (!factoryAddress || !abi || factoryId == null || String(factoryId).trim() === '') { + console.error('Factory contract missing address, abi, or id'); process.exit(1); } console.log('Factory contract OK:', factoryAddress); @@ -99,6 +101,18 @@ async function main() { const provider = new ethers.JsonRpcProvider(PAYRAM_ETH_RPC_URL); const signer = wallet.connect(provider); + // Resolve the project BEFORE paying gas: current core registers SCWs only + // on the project-scoped route (the unscoped legacy route no longer exists), + // so an empty project id would burn the deploy gas and then fail to + // register the wallet with PayRam. + const projectId = (process.env.PAYRAM_PROJECT_ID || '').trim(); + if (!projectId) { + console.error( + 'PAYRAM_PROJECT_ID is not set - refusing to deploy. The tx would spend gas on-chain but SCW registration with PayRam would fail. Set PAYRAM_PROJECT_ID (the script normally resolves it via get_first_project_id) and retry.' + ); + process.exit(1); + } + // 3) Salt bytes32 (same idea as frontend: keccak256 of random) const salt = ethers.keccak256(ethers.toUtf8Bytes(`${Date.now()}-${Math.random().toString(36).slice(2)}`)); @@ -143,17 +157,15 @@ async function main() { // 5) Register with backend. The route moved across core versions: // current core: POST /api/v1/project/{projectID}/wallets/deposit/scw/blockchains_contract/{id} // older images: POST /api/v1/wallets/deposit/scw/blockchains_contract/{id} - // Body ({name, transactionHash}) is the same in both. Try the - // project-scoped path when we know the project, fall back to legacy on - // 404/405 so the script works against whichever image is deployed. - const projectId = (process.env.PAYRAM_PROJECT_ID || '').trim(); + // Body ({name, transactionHash}) is the same in both. projectId was + // validated before the deploy tx, so the scoped path is always first; + // legacy stays as a 404/405 fallback for older images. const registerBody = JSON.stringify({ name: PAYRAM_SCW_NAME, transactionHash: txHash }); const registerHeaders = { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }; - const candidates = []; - if (projectId) { - candidates.push(`${PAYRAM_API_URL}/api/v1/project/${projectId}/wallets/deposit/scw/blockchains_contract/${factoryId}`); - } - candidates.push(`${PAYRAM_API_URL}/api/v1/wallets/deposit/scw/blockchains_contract/${factoryId}`); + const candidates = [ + `${PAYRAM_API_URL}/api/v1/project/${projectId}/wallets/deposit/scw/blockchains_contract/${factoryId}`, + `${PAYRAM_API_URL}/api/v1/wallets/deposit/scw/blockchains_contract/${factoryId}`, + ]; // candidates always has at least the legacy URL, so registerRes is always set. let registerRes; diff --git a/setup_payram_agents.sh b/setup_payram_agents.sh index c837264..42d3d20 100755 --- a/setup_payram_agents.sh +++ b/setup_payram_agents.sh @@ -1000,7 +1000,16 @@ print(' '.join(x['code'] for x in d if str(x.get('status','active')).lower()=='a res=$(api GET "/api/v1/system/workers/status" "" true) parse_response "$res" - workers_body="$HTTP_BODY" + # A host without supervisord answers 500 here; without this guard every + # chain would falsely degrade to listener-down with a bogus restart hint. + local workers_available="yes" + if [[ "$HTTP_CODE" != "200" ]]; then + workers_available="no" + workers_body='{"status":[]}' + echo "Note: worker status unavailable (HTTP $HTTP_CODE) - listener state unknown; RPC/block-age checks still run." + else + workers_body="$HTTP_BODY" + fi echo "Node sync status (thresholds: 10m, BTC 90m):" local issues=0 chain @@ -1049,7 +1058,9 @@ elif age>=0: else: print('healthy no-timestamp')" 2>/dev/null || echo "unreachable parse-failed") || true fi - [[ "$verdict" == "healthy" && "$listener_up" == "no" ]] && verdict="listener-down" + # Only assert listener-down when worker status was actually available. + [[ "$workers_available" == "yes" && "$verdict" == "healthy" && "$listener_up" == "no" ]] && verdict="listener-down" + [[ "$workers_available" == "no" ]] && listener_up="unknown" local flag="✓" [[ "$verdict" == "lagging" ]] && flag="⚠" @@ -1270,11 +1281,37 @@ cmd_signin() { ensure_config() { ensure_token || return 1 local base_url="${PAYRAM_API_URL}" + local res + # Current cores keep ONE canonical server URL (payram.server.url), set via + # /system/site-url; the old payram.frontend/payram.backend keys are + # hard-deleted on boot, so writing them is a silent no-op and payment-link + # creation 500s while payram.server.url is unset. GET always returns 200, + # with {"siteUrl":null} when the key is absent. + res=$(api GET "/api/v1/system/site-url" "" true) + parse_response "$res" + if [[ "$HTTP_CODE" == "200" ]]; then + if echo "$HTTP_BODY" | grep -q '"siteUrl":[[:space:]]*null'; then + # POST takes no URL — core derives the origin from THIS request. + # Only the scheme is accepted in the body; pass it explicitly so + # an https install behind a proxy is not downgraded to http. + local scheme="http" + [[ "$base_url" == https://* ]] && scheme="https" + res=$(api POST "/api/v1/system/site-url" "{\"scheme\":\"$scheme\"}" true) + parse_response "$res" + case "$HTTP_CODE" in + 200) echo "Set server URL (payram.server.url) from ${base_url}" ;; + 403) echo "Server URL is unset and this token is not root — sign in as the root member and re-run ensure-config, or payment-link creation will fail." ; return 1 ;; + *) echo "Warning: could not set server URL (HTTP $HTTP_CODE) — payment-link creation may fail until it is set." ; return 1 ;; + esac + fi + return 0 + fi + # Older cores (no /system/site-url): fall back to the legacy split keys, + # which only ever mattered on localhost installs. local frontend_url="${PAYRAM_FRONTEND_URL:-http://localhost}" if [[ "$base_url" != *"localhost"* && "$base_url" != *"127.0.0.1"* ]]; then return 0 fi - local res res=$(api GET "/api/v1/configuration/key/payram.frontend" "" true) parse_response "$res" if [[ "$HTTP_CODE" == "404" || "$HTTP_CODE" == "500" ]] || ! echo "$HTTP_BODY" | grep -q '"key"'; then @@ -2609,7 +2646,9 @@ flow_main() { fi log "Ensuring config..." - ensure_config + # Tolerated failure: the rest of the flow (wallets, gas) is still useful; + # ensure_config already printed why payment-link creation would fail. + ensure_config || log "Config warning: server URL not set — payment-link creation may fail (see above)." if [[ "$setup_mode" == "operator" ]]; then log "Operator lane: configuring fee collectors + default fees..." diff --git a/updater-configs/upgrade-policy.json b/updater-configs/upgrade-policy.json index 39e7041..607c61b 100644 --- a/updater-configs/upgrade-policy.json +++ b/updater-configs/upgrade-policy.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "latest": "3.5.0", + "latest": "3.5.2", "updater_api_init_version": "1.9.6", "arch_support": { "arm64": "1.9.1" @@ -12,6 +12,8 @@ } ], "releases": [ + "3.5.2", + "3.5.1", "3.5.0", "3.4.0", "3.3.0", @@ -75,5 +77,5 @@ "1.0.1", "1.0.0" ], - "dashboard_max": "3.5.0" + "dashboard_max": "3.5.2" } \ No newline at end of file