Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
# CODEOWNERS — critical paths require review from designated owners before merge.
# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners

contracts/ @dreamgeneX
scripts/ @dreamgeneX
docs/ @dreamgeneX
# Smart-contract code — fund-safety-critical; review must go through the
# contract maintainer before any change ships.
contracts/ @dreamgeneX
COMEBACKHERE-contracts/ @dreamgeneX

# ABI metadata generated from contract sources; any change must be
# reviewed alongside the contract source that produced it.
abis/ @dreamgeneX

# Tooling, scripts, and process docs.
scripts/ @dreamgeneX
docs/ @dreamgeneX

# Frontend — assign a frontend team owner when one is established.
# comebackhere-frontend/ @frontend-team
25 changes: 25 additions & 0 deletions .github/workflows/ci-error-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CI - Error Codes Documentation

on:
pull_request:
branches: [main]
paths:
- 'contracts/**'
- 'COMEBACKHERE-contracts/**'
- 'docs/error-codes.md'
- 'scripts/check_error_docs_sync.sh'
- '.github/workflows/ci-error-docs.yml'

permissions:
contents: read

jobs:
check-error-docs-sync:
name: check-error-codes-docs-sync
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Verify contract error variants are documented
run: ./scripts/check_error_docs_sync.sh
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ repos:
- repo: local
hooks:
- id: abi-snapshot-hygiene
name: ABI snapshot hygiene
name: ABI snapshot staging hygiene
entry: scripts/check_abi_snapshot_hygiene.sh
language: system
pass_filenames: false
- id: check-abi-snapshots
name: ABI snapshot content check
entry: scripts/check_abi_snapshots_precommit.sh
language: system
pass_filenames: false
files: '^(COMEBACKHERE-contracts/contracts/|abis/)'
- id: lint-docs
name: Markdown lint
entry: scripts/lint-docs.sh
Expand Down
72 changes: 72 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Architecture

This repository sits at the centre of the COMEBACKHERE protocol and orchestrates three sibling projects:

- **Smart contracts** — Soroban contracts (`invoice`, `treasury`, `compliance`).
- **Backend API** — Node/Express service that fronts the contracts.
- **Frontend UI** — React/Vite app used by merchants, payers, and admins.

Each layer has **two source trees in this repository**, plus CI jobs that pull a third copy from upstream. This document explains which tree to target when contributing.

## Canonical trees (where changes go)

The active source of truth for each layer lives in a tree with the `COMEBACKHERE-` prefix inside this repository:

| Layer | Canonical tree | Why |
| --- | --- | --- |
| Contracts (Rust) | `COMEBACKHERE-contracts/` | `cargo test --manifest-path COMEBACKHERE-contracts/Cargo.toml` is run by `make test` and by `.github/workflows/ci-contracts.yml`. ABI snapshots in `abis/` are generated from this workspace. |
| Backend (Node) | `comebackhere-backend/` | Referenced by `.github/workflows/ci.yml` for `npm ci`, `tsc --noEmit`, `npm run lint`, `npm run build`. |
| Frontend (React) | `comebackhere-frontend/` | Same as backend — the `ci.yml` `working-directory` points here. |

New feature work, bug fixes, and contract changes should target these three directories by default — they're the only ones with a dedicated, contract-specific CI workflow.

## Mirrored trees (older in-tree copies)

The repository also contains a parallel set of directories without the prefix:

- `contracts/` and `backend/` and `frontend/`

These are in-tree copies of the canonical sources at earlier points in time. They are kept because:

- Process docs reference them by path. `docs/error-codes.md` calls out `contracts/invoice/src/lib.rs` as the source of `InvoiceError`; that file still lives here.
- They give reviewers a familiar path while the migration to the `COMEBACKHERE-*` trees completes.

These trees are valid PR targets, but they have a **gap in coverage**: only the `ci-*.yml` workflows that match their files run on a PR touching them. There is no independent `cargo test` for `contracts/Cargo.toml` or `backend/Cargo.toml`, and no independent frontend build for `frontend/`. So a green PR here will not, by itself, prove the change works against the toolchain pinned in the canonical workspace.

If your change is brand-new work, target the `COMEBACKHERE-*` tree to inherit full CI coverage. If your change is intentionally narrow (a doc tweak, a small Rust fix in a file the CI happens to cover), the mirrored tree is fine.

## CI checkout behaviour

Some CI workflows re-check the canonical trees out from upstream rather than using the in-tree copies:

- `ci-contracts.yml`, `ci-abi-snapshots.yml`, `ci-abi-metadata.yml`, `ci-post-deploy-verify.yml`, and `ci-coverage.yml` do `actions/checkout` of `WHEELBACK/COMEBACKHERE-contracts` into the local `COMEBACKHERE-contracts/` path.
- The local `COMEBACKHERE-contracts/` checkout in this repository exists so that `make update-abi-snapshots` and `scripts/check_abi_snapshot_hygiene.sh` work locally without a separate clone.
- A consumer running on a developer's machine can equivalently clone `COMEBACKHERE-contracts` as a sibling directory; `scripts/generate_abi_metadata.sh` looks for both locations.

## Other top-level paths

- `abis/` — committed ABI metadata (`invoice.json`, `treasury.json`, `compliance.json`). Generated from `COMEBACKHERE-contracts/` via `make update-abi-snapshots`.
- `scripts/` — Bash + Python helpers for ABI generation, snapshot hygiene, deployment, and backend env validation.
- `docs/` — error codes, API reference, deployment guides, the ABI snapshot workflow, and other process docs.
- `.github/workflows/` — CI jobs.

## Local development layout

A working clone for end-to-end development looks like this (see `docs/dev-environment.md` for full setup):

```text
~/comebackhere/
├── COMEBACKHERE-contracts/ # canonical contracts
├── COMEBACKHERE/ # this repository
├── comebackhere-backend/ # canonical backend
└── comebackhere-frontend/ # canonical frontend
```

When working inside this repository alone, the in-tree `COMEBACKHERE-contracts/` checkout acts as the canonical contracts tree; the sibling-clone step is optional for backend/frontend contributors.

## Further reading

- [docs/dev-environment.md](docs/dev-environment.md) — full local setup.
- [docs/abi-snapshot-workflow.md](docs/abi-snapshot-workflow.md) — when and how to regenerate `abis/`.
- [docs/error-codes.md](docs/error-codes.md) — contract error enums and their meanings.
- [SECURITY.md](SECURITY.md) — which paths handle fund-safety-critical code.
28 changes: 28 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# Contributing

## Which stack do I work on?

The repository contains two parallel source trees for each layer — Rust
contracts, Node backend, and React frontend. From directory names alone it is
not obvious which tree receives a given change.

The short rule:

- Prefer `COMEBACKHERE-contracts/` for contract changes
- Prefer `comebackhere-backend/` for backend changes
- Prefer `comebackhere-frontend/` for frontend changes

These are the trees built and tested by CI (`make test`,
`.github/workflows/ci-contracts.yml`, `ci.yml`). They are the canonical
source of truth and the only path that gets the full required-status-check
matrix on every PR.

The sibling-less top-level `contracts/`, `backend/`, and `frontend/`
directories are also valid PR targets — they contain older in-tree copies of
the same sources and are still referenced from documentation (for example,
`docs/error-codes.md` cites `contracts/invoice/src/lib.rs` as the source of
`InvoiceError`). They do not, however, have a dedicated CI workflow of their
own, so changes there rely on whichever `ci-*.yml` job happens to match the
files. When in doubt, target the canonical `COMEBACKHERE-*` tree.

See [`ARCHITECTURE.md`](./ARCHITECTURE.md) for the full reasoning, the exact
directory layout, and how the mirrors are kept in step.

## Local hooks

Install [pre-commit](https://pre-commit.com/) and enable the repository hooks:
Expand Down
57 changes: 54 additions & 3 deletions docs/error-codes.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Error Codes

This document maps every `InvoiceError` variant (and other contract error codes) to its numeric value, the condition that triggers it, and the recommended remediation steps for integrators.
This document maps every `InvoiceError`, `ContractError`, `SettlementError`, and `TreasuryError` variant (and other contract error codes) to its numeric value, the condition that triggers it, and the recommended remediation steps for integrators.

> Cross-reference: see [docs/api-reference.md](./api-reference.md) for HTTP-level error shapes returned by the backend.

---

## InvoiceError

Defined in `contracts/invoice/src/lib.rs` and `COMEBACKHERE-contracts/contracts/invoice/src/lib.rs`.
Defined in `contracts/invoice/src/lib.rs`. The `COMEBACKHERE-contracts/contracts/invoice` crate declares a related enum called `ContractError (invoice)` (see below) — variant names overlap but numeric codes are independent.

| Code | Name | Trigger condition | Remediation |
| ------ | ------ | ------------------- | ------------- |
Expand All @@ -27,6 +27,42 @@ Defined in `contracts/invoice/src/lib.rs` and `COMEBACKHERE-contracts/contracts/

---

## ContractError (invoice)

Defined in `COMEBACKHERE-contracts/contracts/invoice/src/lib.rs`. Shares some variant names with `InvoiceError` above, but numeric codes and semantic triggers are independent — branch on the enum name when integrating.

| Code | Name | Trigger condition | Remediation |
| ------ | ------ | ------------------- | ------------- |
| 1 | `Unauthorized` | Caller is neither the merchant nor the customer for the operation. | Sign with the merchant key for cancellation flows or with the customer key for refund flows. The admin key is required for `pause`, `unpause`, `set_treasury`, and `set_grace_window`. |
| 2 | `ContractPaused` | A state-changing call was made while the contract is in a paused state. | Check contract status before submitting. Contact the admin to unpause. Do not retry until the contract is unpaused. |
| 3 | `AlreadyInitialized` | `initialize` was called on a contract that is already set up. | Deployment-time error. Remove the extra initialize call; the contract can only be initialised once. |
| 4 | `InvoiceNotFound` | `get_invoice`, `cancel_invoiced`, `mark_paids`, `request_refund`, `release_escrow`, or `raise_dispute` was called with an ID that does not exist. | Confirm the invoice ID returned by `create_invoice`. |
| 5 | `InvoiceAlreadyPaid` | `mark_paids` was called on an invoice that is no longer in `Pending` status. | Inspect invoice status with `get_invoice_status` before marking paid. Already-paid invoices cannot transition again. |
| 6 | `InvoiceExpired` | `mark_paids` was called after `env.ledger().timestamp() >= invoice.expires_at`. | Pay invoices before the configured expiry. Use `batch_expire` to sweep stale invoices off the books. |
| 7 | `InvoiceCancelled` | `cancel_invoiced` was called on a non-Pending invoice. | Invoices can only be cancelled while `Pending`. Confirm status before cancelling. |
| 8 | `NotMerchant` | `release_escrow` was called by a non-merchant caller. | Sign with the merchant key associated with the invoice to release escrow. |
| 9 | `NotCustomer` | `request_refund` was called by a non-customer caller. | Sign with the customer key associated with the invoice to request a refund. |
| 10 | `RefundNotRequested` | `release_escrow` was called before `request_refund` moved the invoice to `RefundRequested`. | Call `request_refund` first, then wait for the grace window to elapse before `release_escrow`. |
| 11 | `AlreadyRefundRequested` | `request_refund` was called on an invoice that is already in `RefundRequested` status. | Each invoice may transition to `RefundRequested` only once. Inspect status before re-requesting. |
| 12 | `GraceWindowNotExpired` | `release_escrow` was called before `created_at + grace_window`. | Wait until `ledger.timestamp() >= created_at + grace_window`. Admin may reduce `GraceWindow` via `set_grace_window` (default 86 400 seconds). |
| 13 | `DuplicateNonce` | The (merchant, nonce) pair has already been used by a previous invoice. | Generate a fresh nonce for each invoice. Different merchants may reuse the same nonce value without collision. |
| 14 | `TreasuryNotConfigured` | `raise_dispute` was called before the admin ran `set_treasury`. | Admin must call `set_treasury` once before disputes can be raised. |

---

## ContractError (compliance)

Defined in `COMEBACKHERE-contracts/contracts/compliance/src/lib.rs`.

| Code | Name | Trigger condition | Remediation |
| ------ | ------ | ------------------- | ------------- |
| 1 | `Unauthorized` | Caller is not the admin configured in `initialize`. | Sign with the admin key for state-changing calls (`set_status`, `pause`, `unpause`, admin rotation). |
| 2 | `ContractPaused` | A state-changing call was made while the compliance contract is paused. | Compliance check calls return early on pause; defer the user action or have the admin unpause. |
| 3 | `AlreadyInitialized` | `initialize` was called on a contract that is already set up. | Deployment-time error. The compliance contract can only be initialised once. |
| 4 | `AddressNotFound` | A status query (or block/unblock flow) referenced an address that has not been recorded in `Status(Address)`. | Register the address via `set_status` first, or use the `Cleared` default if no entry exists. |

---

## SettlementError

Defined in `contracts/settlement/src/lib.rs`.
Expand All @@ -40,6 +76,21 @@ Defined in `contracts/settlement/src/lib.rs`.

---

## TreasuryError

Defined in `COMEBACKHERE-contracts/contracts/treasury/src/lib.rs`.

| Code | Name | Trigger condition | Remediation |
| ------ | ------ | ------------------- | ------------- |
| 1 | `ContractPaused` | A state-changing call was made while the treasury is in a paused state. | Defer transactions until the admin runs `unpause`. |
| 2 | `NotPending` | `approve_settlement` or `execute_settlement` was called on a settlement that is not in `Pending` status. | Confirm pending status with `get_pending_settlements` before approving or executing. |
| 3 | `InsufficientApprovals` | `execute_settlement` was called before accumulated signer weight reached the configured threshold. | Continue gathering approvals until `approval_weight ≥ threshold`, then call `execute_settlement`. |
| 4 | `TokenNotAllowed` | `propose_settlement` was called with a token not present in the allowlist (when the allowlist is non-empty). | Admin must call `add_token_to_allowlist` for the token before settlements may be proposed against it. |
| 5 | `Unauthorized` | Caller is not registered as a signer (for `propose_settlement`/`approve_settlement`) or not the admin (for `set_signer`, `pause`, etc). | Use a key registered via `initialize` or `set_signer`; admin-only operations require the admin key. |
| 6 | `InvalidThreshold` | `update_threshold` was called with a threshold of 0. | Pass a positive `u32` threshold; the multi-sig cannot function with zero required weight. |

---

## Error shape in API responses

Backend endpoints return errors as JSON:
Expand All @@ -61,7 +112,7 @@ Backend endpoints return errors as JSON:
| ------------- | -------------------------- | --------- |
| 400 | — | Invalid request body (validation failed before hitting the contract). |
| 403 | 1 (`Unauthorized`) | Caller is not authorised for the operation. |
| 404 | 6 (`NotFound`), Settlement 1 | Resource does not exist. |
| 404 | 6 (`NotFound`), 4 (`InvoiceNotFound`/`AddressNotFound`), Settlement 1 | Resource does not exist. |
| 422 | 3, 4, 5, 8, 9, 10, 12, 13 | Contract rejected the transaction. |
| 503 | — | Backend misconfiguration (missing env vars). |
| 504 | — | Transaction confirmation timeout waiting for Soroban. |
52 changes: 52 additions & 0 deletions scripts/check_abi_snapshots_precommit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# scripts/check_abi_snapshots_precommit.sh
#
# Pre-commit wrapper around `make check-abi-snapshots`.
#
# Runs the same check CI runs to verify that the committed ABI metadata
# under abis/ matches what `COMEBACKHERE-contracts/` currently produces.
# On failure, prints a clear remediation message pointing the developer
# at `make update-abi-snapshots` instead of dumping a noisy diff.
#
# Skipped gracefully if the COMEBACKHERE-contracts sibling workspace is
# not present locally, since pre-commit hooks should not block unrelated
# commits when the environment lacks the prerequisites.
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

if [[ ! -d "$ROOT/COMEBACKHERE-contracts" && ! -d "$ROOT/../COMEBACKHERE-contracts" ]]; then
echo "check-abi-snapshots: skipping (COMEBACKHERE-contracts/ sibling not found locally)."
exit 0
fi

if ! command -v make >/dev/null 2>&1; then
echo "check-abi-snapshots: skipping (make not installed in this environment)."
exit 0
fi

if make -C "$ROOT" check-abi-snapshots; then
exit 0
fi

cat >&2 <<'EOF'
ERROR: ABI snapshots in abis/ are out of sync with COMEBACKHERE-contracts/.

To fix this, abort the current commit attempt and re-commit after
regenerating the snapshots:

# 1. Abort the in-flight commit attempt (this script is running as a
# pre-commit hook, so `git commit --amend` would not work here).
git commit --no-verify

# 2. Regenerate the snapshots.
make update-abi-snapshots

# 3. Re-stage and commit the regenerated files.
git add abis/
git commit -m '<your message>'

(Or, if you do not have the COMEBACKHERE-contracts sibling cloned, clone
it first: `git clone https://github.com/WHEELBACK/COMEBACKHERE-contracts ../COMEBACKHERE-contracts`.)
EOF
exit 1
Loading
Loading