A single, explicit contract for what "build" and "release" mean for each package in this monorepo — build target, environment assumptions, artifact names, and migration steps — so release readiness doesn't depend on an individual maintainer remembering the right command sequence per package.
This document sits above two existing, narrower documents:
- RELEASE_POLICY.md — versioning policy (SemVer, version sync) and npm publish mechanics for the publishable packages.
- .github/RELEASE_PROCESS.md — what
scripts/verify-release-locally.shactually checks today (contracts + SDK in depth; everything else only typechecked).
scripts/verify-release-locally.sh is the closest thing this repo has to a
CI-safe validation path today (see docs/QUALITY_GATE.md for
why it isn't wired into actual CI). Reading it line by line against the five
packages this issue asks the contract to cover:
| Package | What verify-release-locally.sh does |
|---|---|
contracts (EVM) |
Full: Hardhat compile, Foundry compile, bytecode consistency, both test suites, checksums |
packages/sdk |
Full: build, export validation, import testing, test suite, size check, checksums |
coordinator |
Typecheck only (tsc --noEmit) — never runs pnpm build, never runs its test suite as part of this script |
resolver |
Typecheck only — same gap |
frontend |
Typecheck only — same gap |
relayer |
Not referenced anywhere in the script. No build, no typecheck, no test run. |
soroban (Rust/Stellar contracts) |
Not referenced anywhere in the script. EXPORT_PATHS=("... soroban ...") on line 181 checks the SDK's soroban TypeScript subpath export — it does not touch soroban/Cargo.toml or run stellar contract build. |
So today, a release can pass local verification with the relayer failing to
build and the Soroban contracts failing to compile, and nothing catches it
until someone runs pnpm --filter @wafflefinance/relayer build or cd soroban && stellar contract build by hand. This is the gap the contract below is
scoped to close.
For each package, the columns below are what "the build succeeded" and "this artifact is releasable" concretely mean.
| Build command | pnpm --filter @wafflefinance/contracts compile (Hardhat) + forge build (Foundry, run from contracts/) |
| Build target | Solidity → EVM bytecode + ABI, dual-toolchain for reproducibility |
| Artifact | contracts/artifacts/contracts/**/*.json (Hardhat), Foundry's own out/ — validated critical contracts are HTLCEscrow.sol, ResolverRegistry.sol |
| Environment assumptions | Network config in contracts/hardhat.config.ts; .env RPC vars for deploy:sepolia/deploy:mainnet |
| Migration steps | None (contracts are immutable once deployed); a new deployment gets a new address recorded in deployments.testnet.json and env.example (see docs/QUALITY_GATE.md check #8) |
| Local verification today | Full — covered by verify-release-locally.sh |
| Build command | pnpm --filter @wafflefinance/sdk build |
| Build target | TypeScript → dist/ (ESM), with subpath exports ethereum, soroban, secrets, state-machine, solana, assets, types per RELEASE_POLICY.md |
| Artifact | packages/sdk/dist/index.js + .d.ts, one pair per subpath export; published to npm as @wafflefinance/sdk |
| Environment assumptions | None at build time — the SDK is environment-agnostic by design |
| Migration steps | None — consumers pin a version; breaking changes are a major version bump per RELEASE_POLICY.md |
| Local verification today | Full — covered by verify-release-locally.sh |
| Build command | pnpm --filter @wafflefinance/coordinator build (tsc) |
| Build target | TypeScript → dist/index.js, run with node dist/index.js — not published to npm (RELEASE_POLICY.md lists it as the one private package) |
| Artifact | coordinator/dist/. No Dockerfile in this repo (see docs/DEPLOYMENT_ROLLBACK_RUNBOOK.md) — the release artifact is the compiled JS, not a container image |
| Environment assumptions | DATABASE_URL, COORDINATOR_PORT, RPC URLs for Ethereum/Soroban/Solana — see env.example |
| Migration steps | SQL migrations in coordinator/migrations/, auto-applied on startup and tracked in the schema_migrations table — see coordinator/docs/migrations.md. A release that adds a migration file must be deployed with a DB backup taken first (pnpm --filter @wafflefinance/coordinator db:backup) |
| Local verification today | Partial — typechecked only; pnpm build and the test suite are not part of verify-release-locally.sh |
| Build command | pnpm --filter @wafflefinance/relayer build (tsc), or docker build -f relayer/Dockerfile . for the container artifact |
| Build target | TypeScript → dist/index.js; Dockerfile produces a node:22-alpine runtime image exposing port 8080 |
| Artifact | relayer/dist/ and/or the built Docker image |
| Environment assumptions | RELAYER_ADMIN_API_KEY, RELAYER_ADMIN_PRIVATE_KEY, ETHEREUM_RPC_URL, STELLAR_HORIZON_URL — several of these are read in code but currently missing from env.example (see docs/QUALITY_GATE.md) |
| Migration steps | None (stateless relayer; persistence is delegated to the coordinator) |
| Local verification today | None — not referenced in verify-release-locally.sh at all |
| Build command | pnpm --filter @wafflefinance/resolver build (tsc), or docker build -f resolver/Dockerfile . |
| Build target | TypeScript → dist/index.js; Dockerfile produces a node:20-alpine image with ENTRYPOINT ["node", "dist/index.js"] CMD ["run"] |
| Artifact | resolver/dist/ and the Docker image — the only package with a CI-gated Docker publish step described in .github/RELEASE_PROCESS.md (resolver-docker job, gated on verification passing) — though see docs/QUALITY_GATE.md for the fact that no .github/workflows/ currently exists to run that job |
| Environment assumptions | RESOLVER_ETH_PRIVATE_KEY, RESOLVER_STELLAR_SECRET, RESOLVER_HEALTH_PORT, RESOLVER_METRICS_PORT (the latter two missing from env.example) |
| Migration steps | None (stateless; registry state lives on-chain) |
| Local verification today | Partial — typechecked only |
| Build command | pnpm --filter @wafflefinance/frontend build (tsc && vite build) |
| Build target | TypeScript + Vite → static assets in frontend/dist/ |
| Artifact | frontend/dist/, deployed via Vercel — see vercel.json (buildCommand builds the SDK first, then the frontend, then copies frontend/dist to the repo-root dist/ that Vercel serves) |
| Environment assumptions | VITE_API_BASE_URL, VITE_NETWORK_MODE, VITE_*_RPC_URL — build-time-inlined, not runtime-configurable once built. VITE_ENABLE_MOCK_DATA is read in code but missing from env.example |
| Migration steps | None |
| Local verification today | Partial — typechecked only |
| Build command | stellar contract build (per-package, htlc and resolver-registry) or cargo build --release --target wasm32-unknown-unknown |
| Build target | Rust → WASM, per soroban/Cargo.toml workspace (contracts/htlc, contracts/resolver-registry), release profile with lto = true, strip = "symbols" |
| Artifact | WASM binaries under the stellar contract build output directory (see soroban/scripts/deploy.sh, which calls stellar contract build --print-build-dir) |
| Environment assumptions | Soroban CLI (stellar) installed and on PATH; deploy step additionally needs a funded Stellar account and network passphrase (testnet/mainnet, per soroban/scripts/deploy.sh) |
| Migration steps | None (contracts are immutable once deployed; a redeploy is a new contract ID, recorded the same way as the EVM side — SOROBAN_HTLC_TESTNET etc. in env.example) |
| Local verification today | None — not part of verify-release-locally.sh, and versioned independently (workspace.package.version = "0.1.0" in soroban/Cargo.toml) from the npm packages' 1.0.0 |
RELEASE_POLICY.md requires every
package.json to set "repository": {"url": "https://github.com/waffle-finance/waffle-finance-core.git"}. The npm
packages follow this. soroban/Cargo.toml's [workspace.package], however,
sets repository = "https://github.com/karagozemin/wafflefinance" and
homepage to the same URL — a different GitHub account/repo name entirely.
Anyone following that link from crate metadata lands somewhere other than
this repository. This should be corrected to match the npm packages'
convention the next time soroban/Cargo.toml is touched.
The contract above implies verify-release-locally.sh should gain two new
sections to match its existing contracts/SDK depth:
- Relayer:
pnpm --filter @wafflefinance/relayer build+pnpm --filter @wafflefinance/relayer test, mirroring the pattern already used for the SDK section of the script. - Soroban:
stellar contract build --package wafflefinance-htlcand--package wafflefinance-resolver-registry(orcargo build --release --target wasm32-unknown-unknownas a CLI-less fallback), pluscargo testfor the Rust unit/property tests already present undersoroban/contracts/. - Coordinator, resolver, frontend: upgrade their existing typecheck-only
steps to also run
pnpm build(and, ideally, their test suites) rather than typecheck alone — a package can typecheck cleanly and still fail to build (e.g. a bundler-specific import issue).
This document defines the contract — the build target, artifact, environment
assumptions, and migration steps per package, plus the specific coverage gaps
found in the current local verification script. Extending
scripts/verify-release-locally.sh (and the .github/RELEASE_PROCESS.md
workflow it describes) to actually enforce items 1–3 above is out of scope for
this documentation pass and is the natural next implementation step.