diff --git a/docs/api-reference.md b/docs/api-reference.md index c6d9295..55c2b31 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -21,6 +21,30 @@ Returns service health status. --- +### `GET /health/rpc` + +Checks Soroban RPC reachability and current ledger. + +**Response `200`** + +```json +{ + "rpc": "reachable", + "network": "mainnet", + "ledger": 54321678 +} +``` + +#### Errors + +| Status | Description | +| ------ | --------------------------------------- | +| `503` | Soroban RPC unreachable | +| `500` | Unexpected server error | + +--- + + ## Invoices ### `GET /invoices/:id` @@ -304,6 +328,135 @@ Update the treasury approval threshold. --- +### `GET /api/treasury/on-hold-settlements` + +Returns all settlements that are currently on hold. +A hold is placed when a signer flags a settlement as requiring manual review before +execution can proceed. + +**Query parameters:** + +| Parameter | Type | Required | Description | +|-----------|--------|----------|-----------------------------------------------| +| `page` | number | No | Page number (1-based, default: `1`) | +| `limit` | number | No | Results per page (default: `20`, max: `100`) | + +**Response `200`** + +```json +{ + "settlements": [ + { + "id": 7, + "merchant_address": "G...", + "amount": "5000000", + "approvals": [], + "approval_weight": 0, + "status": "OnHold", + "hold_reason": "Merchant KYC under review" + } + ] +} +``` + +#### Errors + +| Status | Description | +| ------ | ----------------------- | +| `500` | Database error | + +--- + +### `POST /api/treasury/release-hold` + +Releases a held settlement, restoring it to `Pending` so the normal approval and +execution flow can resume. Calls `release_hold` on the treasury contract. + +See also: [`release_hold` in the Contract Interaction Guide](./contract-interaction-guide.md#release-a-hold). + +#### Request body + +```json +{ "settlement_id": 7 } +``` + +| Field | Type | Description | +| --------------- | ------ | ------------------------------ | +| `settlement_id` | number | Positive integer settlement ID | + +**Response `200`** + +```json +{ + "id": 7, + "merchant_address": "G...", + "amount": "5000000", + "approvals": [], + "approval_weight": 0, + "status": "Pending", + "hold_reason": null, + "tx_hash": "abc123..." +} +``` + +#### Errors + +| Status | Description | +| ------ | ------------------------------------------------- | +| `400` | `settlement_id` is not a positive integer | +| `409` | Settlement is not currently on hold | +| `422` | Soroban simulation or transaction failure | +| `503` | Missing required environment variables | +| `500` | Unexpected server error | + +--- + +### `POST /api/treasury/escalate-hold` + +Escalates a held settlement to the on-chain dispute-resolution flow. +Calls `raise_dispute` on the treasury contract and begins a multi-sig governance +vote among the configured signers. + +See also: [`raise_dispute` in the Contract Interaction Guide](./contract-interaction-guide.md#raise-a-dispute). + +#### Request body + +```json +{ + "settlement_id": 7, + "reason": "Merchant disputes the invoice amount" +} +``` + +| Field | Type | Required | Description | +| --------------- | ------ | -------- | -------------------------------------------------------- | +| `settlement_id` | number | Yes | Positive integer settlement ID | +| `reason` | string | No | Human-readable reason for escalation (max 512 chars) | + +**Response `200`** + +```json +{ + "dispute_id": "7-1720000001000", + "settlement_id": "7", + "status": "Raised", + "settlement_status": "OnHold", + "tx_hash": "abc123..." +} +``` + +#### Errors + +| Status | Description | +| ------ | ------------------------------------------------- | +| `400` | `settlement_id` is not a positive integer | +| `422` | Soroban simulation or transaction failure | +| `503` | Missing required environment variables | +| `500` | Unexpected server error | + +--- + + ## Invoice Settings ### `GET /api/invoice/grace-window` diff --git a/docs/contract-interaction-guide.md b/docs/contract-interaction-guide.md index 42a5054..77051a5 100644 --- a/docs/contract-interaction-guide.md +++ b/docs/contract-interaction-guide.md @@ -225,6 +225,85 @@ curl -X POST http://localhost:3000/api/treasury/execute-settlement \ --- + +### Place a settlement on hold + +Calls `hold_settlement` on the treasury contract, preventing execution until +explicitly released or escalated. + +#### soroban-cli + +```sh +soroban contract invoke \\ + --id $TREASURY_CONTRACT \\ + --source $SECRET_KEY \\ + --rpc-url $RPC_URL \\ + --network-passphrase "$NETWORK_PASSPHRASE" \\ + -- hold_settlement \\ + --signer $SOURCE_ACCOUNT \\ + --settlement_id 7 \\ + --reason "Awaiting KYC confirmation" +``` + +--- + +### Release a hold + +Calls `release_hold` on the treasury contract, returning the settlement to `Pending` +so the normal approval and execution flow can resume. + +#### soroban-cli + +```sh +soroban contract invoke \\ + --id $TREASURY_CONTRACT \\ + --source $SECRET_KEY \\ + --rpc-url $RPC_URL \\ + --network-passphrase "$NETWORK_PASSPHRASE" \\ + -- release_hold \\ + --signer $SOURCE_ACCOUNT \\ + --settlement_id 7 +``` + +#### API + +```sh +curl -X POST http://localhost:3000/api/treasury/release-hold \\ + -H "Content-Type: application/json" \\ + -d '{ "settlement_id": 7 }' +``` + +--- + +### Raise a dispute (escalate hold) + +Calls `raise_dispute` on the treasury contract, escalating the hold to the +governance dispute-resolution flow and beginning a multi-sig vote. + +#### soroban-cli + +```sh +soroban contract invoke \\ + --id $TREASURY_CONTRACT \\ + --source $SECRET_KEY \\ + --rpc-url $RPC_URL \\ + --network-passphrase "$NETWORK_PASSPHRASE" \\ + -- raise_dispute \\ + --signer $SOURCE_ACCOUNT \\ + --settlement_id 7 \\ + --reason "Merchant disputes the invoice amount" +``` + +#### API + +```sh +curl -X POST http://localhost:3000/api/treasury/escalate-hold \\ + -H "Content-Type: application/json" \\ + -d '{ "settlement_id": 7, "reason": "Merchant disputes the invoice amount" }' +``` + +--- + ### Get / set approval threshold #### soroban-cli — read diff --git a/docs/dev-environment.md b/docs/dev-environment.md index b49e916..41c2102 100644 --- a/docs/dev-environment.md +++ b/docs/dev-environment.md @@ -106,13 +106,51 @@ INVOICE_CONTRACT_ID=$INVOICE_CONTRACT_ID TREASURY_CONTRACT_ID=$TREASURY_CONTRACT_ID COMPLIANCE_CONTRACT_ID=$COMPLIANCE_CONTRACT_ID USDC_CONTRACT_ID=CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4 +MONGO_URI=mongodb://localhost:27017/comebackhere +REDIS_URL=redis://localhost:6379 +WEBHOOK_SECRET= EOF +``` + +Before starting the backend, validate all required environment variables: + +```sh +cd ../COMEBACKHERE +scripts/validate_backend_env.sh ../comebackhere-backend/.env +``` + +A missing or blank required variable causes the script to exit with a clear error message. +To also enforce the optional contract ID variables (e.g. in CI), run with `STRICT=1`: +```sh +STRICT=1 scripts/validate_backend_env.sh ../comebackhere-backend/.env +``` + +Then start the backend: + +```sh +cd ../comebackhere-backend cargo build && cargo run ``` Backend listens on `http://localhost:3000`. +#### Required backend variables + +| Variable | Description | +|------------------|--------------------------------------------------------------------| +| `MONGO_URI` | MongoDB connection string (`mongodb://` or `mongodb+srv://`) | +| `REDIS_URL` | Redis connection string (`redis://`) | +| `WEBHOOK_SECRET` | HMAC secret for signing outgoing webhook payloads (≥ 32 chars) | + +#### Optional contract integration variables + +| Variable | Description | +|-------------------------|-----------------------------------------| +| `INVOICE_CONTRACT_ID` | Deployed invoice contract address | +| `TREASURY_CONTRACT_ID` | Deployed treasury contract address | +| `COMPLIANCE_CONTRACT_ID`| Deployed compliance contract address | + ### Frontend ```sh diff --git a/scripts/deploy_mainnet.sh b/scripts/deploy_mainnet.sh index b713683..2bd47c6 100755 --- a/scripts/deploy_mainnet.sh +++ b/scripts/deploy_mainnet.sh @@ -1,13 +1,91 @@ #!/usr/bin/env bash +# Mainnet deployment entry point for COMEBACKHERE Protocol. +# +# Live deployment requires governance approval, multi-sig signing, and a recorded +# signing ceremony — this script intentionally refuses to submit transactions from +# a single local shell. +# +# Use --dry-run to print the planned actions (contracts, addresses, network config) +# without submitting any transaction. The output is formatted to be easy to paste +# into a deployment-checklist PR or issue. +# +# Usage: +# scripts/deploy_mainnet.sh --dry-run # preview only — zero network-mutating calls +# scripts/deploy_mainnet.sh # refuses; live deploy requires multi-sig ceremony set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" -echo "Mainnet deployment requires multi-sig approval and an external signing ceremony." -echo "Refusing to deploy from a single local shell." +DRY_RUN=0 + +for arg in "$@"; do + case "$arg" in + --dry-run) DRY_RUN=1 ;; + *) + echo "Unknown argument: $arg" >&2 + echo "Usage: $0 [--dry-run]" >&2 + exit 1 + ;; + esac +done + +# ── resolve env ─────────────────────────────────────────────────────────────── # shellcheck disable=SC1091 source scripts/validate_env.sh .env.mainnet mainnet deployment +# ── dry-run mode ────────────────────────────────────────────────────────────── + +if [ "$DRY_RUN" -eq 1 ]; then + ADMIN_PUBLIC_KEY="${ADMIN_PUBLIC_KEY:-}" + USDC_CONTRACT_ID="${USDC_CONTRACT_ID:-}" + TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + + cat <} + SOROBAN_RPC_URL : ${SOROBAN_RPC_URL:-} + SOROBAN_NETWORK_PASSPHRASE : ${SOROBAN_NETWORK_PASSPHRASE:-} + +SIGNING AUTHORITY + ADMIN_PUBLIC_KEY : $ADMIN_PUBLIC_KEY + +CONTRACT ADDRESSES + INVOICE_CONTRACT_ID : ${INVOICE_CONTRACT_ID:-} + TREASURY_CONTRACT_ID : ${TREASURY_CONTRACT_ID:-} + COMPLIANCE_CONTRACT_ID : ${COMPLIANCE_CONTRACT_ID:-} + USDC_CONTRACT_ID : $USDC_CONTRACT_ID + +PLANNED ACTIONS + [1] Verify WASM hashes match deployment-issue expectations + [2] Verify Soroban RPC is reachable at ${SOROBAN_RPC_URL:-} + [3] Verify ADMIN_PUBLIC_KEY is funded and authorised on ${STELLAR_NETWORK:-mainnet} + [4] Deploy invoice contract → INVOICE_CONTRACT_ID + [5] Deploy treasury contract → TREASURY_CONTRACT_ID + [6] Deploy compliance contract → COMPLIANCE_CONTRACT_ID + [7] Initialize contracts with admin $ADMIN_PUBLIC_KEY + [8] Export deployed addresses to artifacts/addresses.json + [9] Run smoke tests (GET /health/rpc + low-value payment) + +DRY RUN COMPLETE — review the above before running the signing ceremony. +Paste this output into the deployment-checklist PR as the pre-flight record. +======================================================== +DRYRUN + exit 0 +fi + +# ── live deploy — refused ───────────────────────────────────────────────────── + +echo "Mainnet deployment requires multi-sig approval and an external signing ceremony." +echo "Refusing to deploy from a single local shell." +echo "" +echo "Run with --dry-run to preview planned actions without submitting transactions." +echo "See docs/MAINNET_DEPLOYMENT.md for the full ceremony checklist." exit 1 diff --git a/scripts/validate_backend_env.sh b/scripts/validate_backend_env.sh new file mode 100755 index 0000000..11a9173 --- /dev/null +++ b/scripts/validate_backend_env.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# Validate that all required comebackhere-backend environment variables are present +# and non-empty before docker-compose up or a local backend start. +# +# Usage: +# scripts/validate_backend_env.sh [ENV_FILE] +# +# If ENV_FILE is supplied the script sources it first; otherwise it checks the +# currently-exported environment. A missing or blank variable causes the script +# to print a clear error message and exit 1 — matching the error-reporting style +# used by the contract deployment validation scripts. +# +# Required variables: +# MONGO_URI — MongoDB connection string (mongo:// or mongodb+srv://) +# REDIS_URL — Redis connection string (redis://) +# WEBHOOK_SECRET — HMAC secret for webhook payload signing +# +# Optionally-checked contract variables (warn only; override with STRICT=1): +# INVOICE_CONTRACT_ID +# TREASURY_CONTRACT_ID +# COMPLIANCE_CONTRACT_ID + +set -euo pipefail + +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +RESET='\033[0m' + +STRICT="${STRICT:-0}" +ENV_FILE="${1:-}" +ERRORS=0 +WARNINGS=0 + +# ── helpers ────────────────────────────────────────────────────────────────── + +err() { + echo -e "${RED}[ERROR]${RESET} $*" >&2 + ERRORS=$(( ERRORS + 1 )) +} + +warn() { + echo -e "${YELLOW}[WARN]${RESET} $*" >&2 + WARNINGS=$(( WARNINGS + 1 )) +} + +ok() { + echo -e "${GREEN}[OK]${RESET} $*" +} + +check_required() { + local var="$1" + local hint="${2:-}" + local val + val="${!var:-}" + if [ -z "$val" ]; then + err "$var is not set or blank.${hint:+ Hint: $hint}" + else + ok "$var" + fi +} + +check_optional() { + local var="$1" + local val + val="${!var:-}" + if [ -z "$val" ]; then + if [ "$STRICT" = "1" ]; then + err "$var is not set (STRICT=1)." + else + warn "$var is not set — required for full contract integration." + fi + else + ok "$var" + fi +} + +# ── source env file if provided ─────────────────────────────────────────────── + +if [ -n "$ENV_FILE" ]; then + if [ ! -f "$ENV_FILE" ]; then + echo -e "${RED}[ERROR]${RESET} ENV_FILE '$ENV_FILE' not found." >&2 + exit 1 + fi + echo "Sourcing $ENV_FILE ..." + # shellcheck disable=SC1090 + set -a + source "$ENV_FILE" + set +a +fi + +# ── required backend variables ──────────────────────────────────────────────── + +echo "" +echo "=== comebackhere-backend required variables ===" + +check_required MONGO_URI \ + "e.g. mongodb://localhost:27017/comebackhere or mongodb+srv://..." + +check_required REDIS_URL \ + "e.g. redis://localhost:6379" + +check_required WEBHOOK_SECRET \ + "HMAC secret used to sign outgoing webhook payloads — must be at least 32 chars" + +# Extra length check for WEBHOOK_SECRET +_ws="${WEBHOOK_SECRET:-}" +if [ -n "$_ws" ] && [ "${#_ws}" -lt 32 ]; then + err "WEBHOOK_SECRET is set but shorter than 32 characters (got ${#_ws}). Use a longer secret." +fi +unset _ws + +# ── optional contract variables (integration) ───────────────────────────────── + +echo "" +echo "=== contract integration variables (optional, STRICT=1 to enforce) ===" + +check_optional INVOICE_CONTRACT_ID +check_optional TREASURY_CONTRACT_ID +check_optional COMPLIANCE_CONTRACT_ID + +# ── summary ─────────────────────────────────────────────────────────────────── + +echo "" +if [ "$ERRORS" -gt 0 ]; then + echo -e "${RED}Validation failed: $ERRORS error(s)${RESET}${WARNINGS:+ and $WARNINGS warning(s)}." >&2 + echo "Fix the variables above before starting the backend." >&2 + exit 1 +fi + +if [ "$WARNINGS" -gt 0 ]; then + echo -e "${YELLOW}Validation passed with $WARNINGS warning(s).${RESET} Contract integration may not function without the optional variables." +else + echo -e "${GREEN}All backend environment variables OK.${RESET}" +fi