Welcome! This guide helps you get from zero to a running local development environment. Thanks for your interest in contributing!
By participating in this project, you agree to follow the Code of Conduct.
| Tool | Version | Notes |
|---|---|---|
| Node.js | 18+ | nodejs.org |
| npm | bundled with Node | or pnpm / bun |
| Rust | stable | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs |
| Soroban CLI | latest | cargo install stellar-cli --locked |
| Freighter wallet | latest | freighter.app — set to Testnet |
git clone https://github.com/FlowwStar/FlowStar.git
cd FlowStar
npm ci # also installs Husky pre-commit hooks via prepare scriptcp .env.local.example .env.localCreate .env.local with:
NEXT_PUBLIC_STREAM_CONTRACT_ID_TESTNET=CBNDCZTRFNTDAPQLPK2ESOKO4XFMSC4PX37QE75BBYFOYIEWIPMHAKFV
# NEXT_PUBLIC_STREAM_CONTRACT_ID_MAINNET=<your mainnet contract id>The contract is already deployed to testnet — use this value as-is for local
development. The app reads NEXT_PUBLIC_STREAM_CONTRACT_ID_TESTNET (or
_MAINNET) based on NEXT_PUBLIC_STELLAR_NETWORK.
npm run devOpen http://localhost:3000.
Mock mode is automatic — no code change required. When
NEXT_PUBLIC_STREAM_CONTRACT_ID_TESTNET (or _MAINNET) is absent from your
.env.local, lib/contract.ts detects isMockMode = !config.streamContractId
and falls back to local mock data automatically. This is the fastest way to
work on UI changes without a Freighter wallet or testnet funds.
Mock streams are defined in lib/mock-data.ts. The app behaves identically —
streams unlock in real time using client-side math. To connect to the real
contract, add the env var and restart the dev server.
feat/NNN-short-description # new feature
fix/NNN-short-description # bug fix
docs/NNN-short-description # documentation
chore/NNN-short-description # maintenance, deps
perf/NNN-short-description # performance
Always include the issue number (NNN) in the branch name.
Follow Conventional Commits:
feat: add cliff amount validation (#42)
fix: correct unlock math for sub-second streams (#87)
docs: expand CONTRIBUTING.md (#163)
chore: bump soroban-sdk to v26 (#101)
- Fork the repo (external contributors) or create a branch (maintainers).
- Open a pull request against
main. - Write a clear PR description — summary, what was tested, any caveats.
- A maintainer will review. Address feedback by pushing new commits (don't force-push during review).
- PRs are squash-merged once approved and CI passes.
Some FlowStar issues are part of external open-source contribution programs, and these labels mark them. FlowStar itself does not pay contributors. The external program decides whether a contribution is rewarded, how much, and when. A label shows that an issue is part of a program. It does not guarantee a reward.
| Label | Meaning |
|---|---|
Stellar Wave |
The issue is part of the Stellar Wave program. |
GrantFox OSS |
The issue is tracked in GrantFox's open-source program. |
Maybe Rewarded |
The issue may be eligible for a GrantFox reward. GrantFox decides whether it is. |
Official Campaign | FWC26, Third Campaign |
The issue belongs to a specific time-boxed campaign in a program. Campaign rules and deadlines come from the program. |
Issues without these labels are ordinary contributions. They are just as welcome, but no program tracks them.
The program's own terms decide eligibility. On FlowStar's side:
- Get assigned before starting. Comment on the issue and wait for a maintainer to assign you. That way two people don't work on the same issue. If you start unassigned work on a program issue, it may not count.
- Only merged PRs count. Maintainers can only confirm contributions that went through the PR process and were merged into
main. A PR that is opened but not merged will not count. - Link the issue. Put
Closes #NNNin the PR description and include the issue number in the branch name and commit messages (see above). That is how program tracking connects your PR to the labeled issue. - Stay inside the issue's scope. Unrelated changes in the same PR slow down review and can hold up the merge.
- Don't let an assigned issue go quiet. If you can no longer work on it, say so on the issue so it can be reassigned.
Security findings follow SECURITY.md instead of this process. Report them privately, not in a labeled public issue.
- Ask questions about a specific issue (scope, approach, assignment) in that issue's comments.
- Ask questions about the program itself (payouts, reward amounts, deadlines, sign-up, account setup) through Stellar Wave's or GrantFox's own channels. FlowStar maintainers can't answer those for the program.
npm test # run once
npm run test:watch # watch modeAll unit tests must live under __tests__/ at the repo root and follow the
naming convention **/*.{test,spec}.{ts,tsx}. Vitest only collects files from
that directory — files placed alongside source code (e.g. under components/
or lib/) are silently ignored and will never run.
The runner is Vitest. Use vi for mocking, not
jest:
import { describe, it, expect, vi } from "vitest";
const spy = vi.fn(); // ✅
const spy = jest.fn(); // ❌ jest is not installedTo run a single test file:
npx vitest run __tests__/lib/unlock-math.test.tsThe suite covers unlock math, formatters, and utility functions.
npm run test:e2e # headlessE2E tests require a running dev server (npm run dev in a separate terminal)
or will start it automatically depending on the config.
Playwright compares screenshots against committed PNG baselines stored in
e2e/__screenshots__/. Baselines must be generated on Linux (via CI) — PNGs
produced on Windows or macOS use different font rendering and will cause
false-positive failures for everyone else. Do not commit locally generated
snapshots.
To regenerate baselines after an intentional UI change, trigger the Update Playwright Snapshots GitHub Actions workflow on your branch (Actions → Update Playwright Snapshots → Run workflow). It commits the correct Linux-rendered PNGs back to your branch automatically.
See docs/TESTING.md for the full workflow, when to regenerate, and how CI handles missing baselines.
cd contracts
cargo test44 tests covering the full stream lifecycle, authorization, cliff edge cases, overdraw protection, integer math, and self-streams.
FlowStar uses Storybook to develop and document UI
components in isolation. Stories live alongside their components as
*.stories.tsx files under components/.
npm run storybookOpens the Storybook UI at http://localhost:6006. The dev server hot-reloads on file changes — no restart needed.
npm run storybook:buildOutput goes to storybook-static/. Useful for CI artifact uploads or
previewing the docs site locally.
Stories use the Component Story Format (CSF3)
with TypeScript. Follow the existing pattern in components/ui/badge.stories.tsx
and components/ui/button.stories.tsx:
import type { Meta, StoryObj } from "@storybook/react";
import { MyComponent } from "./my-component";
const meta = {
title: "UI/MyComponent", // determines the sidebar path in Storybook
component: MyComponent,
parameters: { layout: "centered" },
tags: ["autodocs"], // enables the auto-generated Docs page
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: { /* props */ },
};A few conventions to follow:
- File location: place the story file next to the component it documents,
e.g.
components/ui/my-component.stories.tsx. titlepath: mirror the folder structure —UI/,Streams/,Layout/,Landing/match the subdirectories undercomponents/.autodocstag: includetags: ["autodocs"]on every new story so the Docs tab is generated automatically.argTypes: defineargTypesfor any prop that benefits from a Storybook control (select, boolean, range, etc.) so reviewers can interact with variants without editing code.- Named exports: each meaningful variant gets its own named export (e.g.
Default,Disabled,AllVariants). Avoid a single catch-all story.
| File | Purpose |
|---|---|
.storybook/main.ts |
Story glob (components/**/*.stories.ts?(x)), addons, Next.js framework |
.storybook/preview.ts |
Global parameters, controls matchers, and globals.css import |
The framework is @storybook/nextjs, so Next.js features (Image, Link, App
Router) work inside stories without extra mocking.
All contract source lives in contracts/streaming/src/.
cd contracts/streaming
stellar contract buildOutput: contracts/target/wasm32v1-none/release/flowstar_streaming.wasm
cd contracts
cargo testTo run a single test:
cargo test test_withdraw_after_cliff# Generate and fund a deployer key (first time only)
stellar keys generate deployer --network testnet
stellar keys fund deployer --network testnet
# Deploy
stellar contract deploy \
--wasm contracts/target/wasm32v1-none/release/flowstar_streaming.wasm \
--source deployer \
--network testnetCopy the returned contract ID into your .env.local as
NEXT_PUBLIC_STREAM_CONTRACT_ID_TESTNET.
CI runs two security scripts on every push. Run them locally before opening a PR to catch issues before they block the build.
Scans TypeScript, JavaScript, YAML, and JSON source files for hardcoded secrets (Stripe keys, AWS access key IDs, GitHub tokens, private keys, etc.).
node scripts/check-secrets.mjsExits 0 with ✅ No hardcoded secrets found. when clean. Exits 1 and
prints a ❌ HIGH [SECRETS-001] line for each finding, including the file
path, line number, and the matched pattern label. All findings must be resolved
before merging.
Scans contracts/**/*.rs for four classes of issues:
| ID | Severity | What it catches |
|---|---|---|
| SOROBAN-001 | HIGH | Unchecked arithmetic (+=, -=, *=) on i128/u128/u64 variables |
| SOROBAN-002 | HIGH | Public write functions missing require_auth() |
| SOROBAN-003 | HIGH | persistent().set() calls not paired with extend_ttl() |
| SOROBAN-004 | LOW | panic!("…") with a string literal instead of ContractError |
node scripts/soroban-security-check.mjsExits 0 when clean or when only LOW-severity warnings are present. Exits 1
if any HIGH-severity issue is found. Fix all ❌ HIGH findings before merging;
⚠️ LOW warnings are non-blocking but should be addressed.
- Linting: ESLint (
npm run lint) with config ineslint.config.mjs - Formatting: Prettier with config in
.lintstagedrc - Hooks in
hooks/, utilities inlib/, page components inapp/, shared components incomponents/under the appropriate subdirectory (ui/,streams/,layout/,landing/) - Use
bigintfor all token amounts to match Sorobani128/u64types exactly
Format before committing:
cd contracts/streaming
cargo fmt
cargo clippyHusky runs lint-staged on every commit:
| File type | Checks |
|---|---|
*.ts, *.tsx |
ESLint (auto-fix) + Prettier |
*.json, *.md, *.css |
Prettier |
*.rs |
cargo fmt --check |
To skip in an emergency: git commit --no-verify. Use sparingly.
If hooks aren't running after a fresh clone: npm run prepare. On CI, set
HUSKY=0 to skip hook installation.
Freighter not detected / connecting
- Confirm Freighter is set to Testnet (not Mainnet or Futurenet)
- Make sure the extension is installed and enabled for
localhost - Reload the page after unlocking Freighter
- Try disconnecting and reconnecting the wallet from the app
- Disable other wallet extensions that might conflict
RPC failures / transaction timeouts
- The Soroban testnet RPC (
https://soroban-testnet.stellar.org) can be intermittently slow. Retry the operation. - Check Stellar Status for any ongoing incidents.
- If you see
insufficient resource fee, increase the fee buffer inlib/stellar.ts.
Contract deployment errors
- Ensure your deployer key is funded:
stellar keys fund deployer --network testnet - If the WASM upload fails, try rebuilding:
stellar contract buildthen redeploy - Check that
stellar-cliis up to date:cargo install stellar-cli --locked --force - Verify WASM output exists at
contracts/target/wasm32v1-none/release/flowstar_streaming.wasm Error: account not foundusually means the key isn't funded yet
npm install fails on Husky
- Run
npm run preparemanually to re-install hooks - If on CI, set
HUSKY=0environment variable to skip hook installation
App running in mock mode unexpectedly
- Hard-refresh the browser (
Ctrl+Shift+R) to clear any cached module state - Confirm that
NEXT_PUBLIC_STREAM_CONTRACT_ID_TESTNET(or_MAINNET) is set in.env.localwith a valid contract ID, and that the dev server was restarted after the change. Mock mode activates automatically wheneverisMockMode = !config.streamContractIdis true (i.e. the env var is missing or empty).
By contributing, you agree that your contributions will be licensed under the MIT License.