Skip to content
Open
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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ jobs:
- uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.93.0"
components: rustfmt
targets: wasm32-unknown-unknown

- name: Cache cargo registry and build
uses: Swatinem/rust-cache@v2
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.93.0"
components: rustfmt
targets: wasm32-unknown-unknown

- name: Cache cargo registry and build
uses: Swatinem/rust-cache@v2

- name: Validate tag matches Cargo.toml version
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)"
exit 1
fi

- name: Run tests
run: cargo test

- name: Build deterministic WASM
run: |
docker build --platform linux/amd64 \
-f docker/Dockerfile.build \
-o artifacts .

- name: Install git-cliff
uses: kenji-miyake/setup-git-cliff@v2

- name: Generate changelog
id: changelog
run: |
TAG_COUNT=$(git tag -l 'v*' | wc -l)
if [ "$TAG_COUNT" -le 1 ]; then
git-cliff --tag "$GITHUB_REF_NAME" > RELEASE_NOTES.md
else
git-cliff --latest > RELEASE_NOTES.md
fi

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes-file RELEASE_NOTES.md \
artifacts/streampay_contracts.optimized.wasm \
artifacts/streampay_contracts.checksum
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
.env
.idea
*.iml
docs/
artifacts/
docs/superpowers/
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Changelog

All notable changes to streampay-contracts are documented here.

## [0.1.0] - 2026-03-24

### Added

- Add StreamPay Soroban contract and tests

### CI

- Add fmt, build, test workflow
- Pin Rust toolchain to 1.93.0
- Add git-cliff configuration for changelog generation
- Add deterministic WASM build Dockerfile
- Add release workflow for tagged WASM builds

### Documentation

- Add README and contributor onboarding
- *(contracts)* Add release process design spec
- *(contracts)* Address spec review findings
- *(contracts)* Add release process implementation plan
- *(contracts)* Add release process guide

### Fixed

- *(contracts)* Move soroban-sdk testutils to dev-dependencies


3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ default = []
testutils = ["soroban-sdk/testutils"]

[dependencies]
soroban-sdk = "22.0"

[dev-dependencies]
soroban-sdk = { version = "22.0", features = ["testutils"] }
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,29 @@ On every push/PR to `main`, GitHub Actions runs:

Ensure all three pass before merging.

## Releases

Tagged releases follow [semver](https://semver.org/). Each release includes an optimized WASM artifact and SHA-256 checksum.

See [docs/RELEASE.md](docs/RELEASE.md) for the full release process, including how to verify WASM builds.

## Project structure

```
streampay-contracts/
├── src/
│ └── lib.rs # Contract and tests
│ └── lib.rs # Contract and tests
├── docker/
│ └── Dockerfile.build # Deterministic WASM builder
├── .github/workflows/
│ ├── ci.yml # Format, build, test
│ └── release.yml # Tagged release workflow
├── docs/
│ └── RELEASE.md # Release process guide
├── cliff.toml # Changelog generator config
├── rust-toolchain.toml # Pinned Rust version
├── Cargo.toml
├── .github/workflows/ci.yml
├── CHANGELOG.md
└── README.md
```

Expand Down
45 changes: 45 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[changelog]
header = """
# Changelog

All notable changes to streampay-contracts are documented here.\n
"""
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
footer = ""
trim = true

[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_parsers = [
{ message = "^.*!:", group = "Breaking Changes" },
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^doc", group = "Documentation" },
{ message = "^refactor", group = "Changed" },
{ message = "^ci", group = "CI" },
{ message = "^release", skip = true },
{ message = "^chore", skip = true },
]
protect_breaking_commits = true
filter_commits = false
tag_pattern = "v[0-9].*"
skip_tags = ""
ignore_tags = ""
topo_order = false
sort_commits = "oldest"
31 changes: 31 additions & 0 deletions docker/Dockerfile.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
ARG RUST_VERSION=1.93.0
FROM rust:${RUST_VERSION}-slim-bookworm AS builder

# Install dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config \
libssl-dev \
binaryen \
&& rm -rf /var/lib/apt/lists/*

# Install stellar-cli (pinned version)
ARG STELLAR_CLI_VERSION=25.2.0
RUN cargo install --locked stellar-cli --version ${STELLAR_CLI_VERSION}

# Add WASM target
RUN rustup target add wasm32-unknown-unknown

WORKDIR /build
COPY . .

# Build the optimized WASM
RUN stellar contract build --release \
&& wasm-opt -Oz \
target/wasm32-unknown-unknown/release/streampay_contracts.wasm \
-o streampay_contracts.optimized.wasm \
&& sha256sum streampay_contracts.optimized.wasm > streampay_contracts.checksum

# Output stage — copies artifacts out via docker build -o
FROM scratch AS artifacts
COPY --from=builder /build/streampay_contracts.optimized.wasm /
COPY --from=builder /build/streampay_contracts.checksum /
93 changes: 93 additions & 0 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Release Process

Guide for cutting releases of streampay-contracts.

## Pre-release Checklist

- [ ] All tests pass (`cargo test`)
- [ ] Version bumped in `Cargo.toml`
- [ ] `soroban-sdk` testutils feature is in `[dev-dependencies]`, not `[dependencies]`
- [ ] Changes are merged to `main`

## Semver Bump Rules

| Bump | When |
|------|------|
| **Major** | Breaking entry point changes (renamed/removed functions, changed params) OR storage-migration-required changes |
| **Minor** | New entry points, new features backward-compatible with existing streams |
| **Patch** | Bug fixes, doc changes, or any non-breaking change that alters the compiled WASM hash |

Every on-chain-distinguishable build (different WASM hash) gets at minimum a patch bump.

## Cutting a Release

1. **Bump version** in `Cargo.toml`:
```toml
version = "X.Y.Z"
```

2. **Commit the version bump**:
```bash
git add Cargo.toml
git commit -m "release(contracts): vX.Y.Z"
```

3. **Tag the release**:
```bash
git tag vX.Y.Z
```

4. **Push to main with tag**:
```bash
git push origin main --tags
```

5. **CI takes over** — the release workflow:
- Validates the tag matches `Cargo.toml`
- Runs tests
- Builds a deterministic WASM via Docker
- Generates a changelog entry with `git-cliff`
- Creates a GitHub Release with the `.wasm` and `.checksum` attached

## Verifying a WASM Build

Anyone can verify that a release artifact is reproducible:

```bash
# Clone the repo at the release tag
git checkout vX.Y.Z

# Build with Docker (use --platform on Apple Silicon)
docker build --platform linux/amd64 -f docker/Dockerfile.build -o artifacts .

# Compare checksums
cat artifacts/streampay_contracts.checksum
# Should match the .checksum file from the GitHub Release
```

The canonical hash is produced on `linux/amd64`. On Apple Silicon, the `--platform linux/amd64` flag is required for matching hashes.

## Version Sources

These must stay in sync on every release:

- `Cargo.toml` `version` field
- Git tag (`vX.Y.Z`)

## Troubleshooting

### Tag version doesn't match Cargo.toml
The release workflow validates that the git tag matches the version in `Cargo.toml`. If they diverge, the workflow fails. Fix by deleting the incorrect tag and re-tagging:

```bash
git tag -d vX.Y.Z
git push origin :refs/tags/vX.Y.Z
# Fix Cargo.toml, commit, then re-tag
git tag vX.Y.Z
git push origin main --tags
```

### Docker build fails
- Ensure Docker is installed and running
- Check that `docker/Dockerfile.build` references valid pinned versions
- Try `docker build --no-cache` to rebuild from scratch
Loading
Loading