| name | e2e-check |
| description | Use when verifying the full solution works end-to-end — after refactoring, before PRs, or after dependency upgrades. Runs quality pipeline, validates example scripts, and executes live Stellar testnet demos for both charge and channel payment modes. |
E2E Check
Full end-to-end verification: quality pipeline, example script validation, and live Stellar testnet demos.
Prerequisites: .env file at project root with testnet keys (see examples/.env.*.example), funded Stellar testnet accounts, pnpm installed.
Run all checks in order. Stop on first failure and report.
Check 1: Full Quality Pipeline
make check
Runs: install -> format-check -> lint -> typecheck -> test -> build.
This includes the mocked integration tier (sdk/src/**/integration/mocked/**) — the
deterministic full-verify()-dispatch tests, including the settling-window TOCTOU end-to-end
regression and the cross-process replay tests. They are CI gates, not an opt-in suite.
Pass: 0 lint errors (warnings OK), all tests pass, build succeeds.
Check 1b: Live Integration Suite (testnet)
The integration/live/** tier holds vitest tests that run against Stellar testnet (network
access + funded accounts required). It is excluded from make check and run on demand:
make test-integration
Currently this is the charge e2e suite (charge/integration/live/accepts/e2e.test.ts) — all
six charge flows submitted as real transactions. Complements the example-script flows in Check 2b.
Pass: All live flows pass.
Integration test layout (sdk/src/<module>/integration/):
| Tier | Path | Runner | Asserts |
|---|
| mocked | integration/mocked/{accepts,rejects}/ | make check | deterministic dispatch — accepts = valid flow succeeds, rejects = malicious input refused |
| live | integration/live/{accepts,rejects}/ | make test-integration | real testnet behavior |
Check 2: Example Script Validation
Verify all example scripts start correctly (imports resolve, env parsing works):
for f in examples/charge-server.ts examples/charge-client.ts examples/charge-client-fee-bump.ts examples/channel-server.ts examples/channel-client.ts examples/channel-close.ts; do
echo "--- $f ---"
timeout 3 npx tsx "$f" 2>&1 | head -3
echo ""
done
Pass criteria per script:
| Script | Expected |
|---|
examples/charge-server.ts | Starts Express on port 3000 (pino JSON log) |
examples/charge-client.ts | Loads keypair, starts client |
examples/charge-client-fee-bump.ts | Loads keypair+fee-bump-key, prints account keys, ECONNREFUSED |
examples/channel-server.ts | Starts Express on port 3001 (pino JSON log) |
examples/channel-client.ts | Loads commitment key, starts client |
examples/channel-close.ts | Env validation error: CLOSE_SECRET is required (expected) |
Fail: Any import error, syntax error, or module-not-found error.
Check 2b: Charge Flow Variations
Six flows are available by combining client and server env vars. Each requires a running
charge server (Terminal 1) and charge client (Terminal 2).
⚠️ Env leak — do NOT source .env in the shell that launches the UNSPONSORED server.
If ENVELOPE_SIGNER_SECRET (or FEE_BUMP_SIGNER_SECRET) reaches the server process, it
silently switches to sponsored mode and advertises "feePayer":true in the challenge.
Flows 1–4 then fail even though the code is correct. Failure signature:
- Flow 1 client errors:
Push mode is not supported for server-sponsored transactions
- Flow 2 gets a 402; server log:
Push mode (type="hash") is not allowed with feePayer=true
- The challenge's base64
request contains "feePayer":true
Safe pattern — pull individual values out of .env instead of sourcing it, and strip the
signer vars from the server's environment as a belt-and-braces guard:
RECIP=$(grep '^STELLAR_RECIPIENT=' .env | cut -d= -f2- | tr -d '"')
env -u ENVELOPE_SIGNER_SECRET -u FEE_BUMP_SIGNER_SECRET \
PORT=3099 STELLAR_RECIPIENT="$RECIP" npx tsx examples/charge-server.ts
Before running flows 1–4, confirm the challenge does NOT contain "feePayer":true.
Only flows 5–6 set the signer secrets — deliberately, in their own server processes.
Server configurations
env -u ENVELOPE_SIGNER_SECRET -u FEE_BUMP_SIGNER_SECRET \
PORT=3099 STELLAR_RECIPIENT=G... npx tsx examples/charge-server.ts
PORT=3099 STELLAR_RECIPIENT=G... ENVELOPE_SIGNER_SECRET=S... \
npx tsx examples/charge-server.ts
PORT=3099 STELLAR_RECIPIENT=G... ENVELOPE_SIGNER_SECRET=S... FEE_BUMP_SIGNER_SECRET=S... \
npx tsx examples/charge-server.ts
Client invocations (Terminal 2, replace S... with real key)
STELLAR_SECRET=S... SERVER_URL=http://localhost:3099 CHARGE_CLIENT_MODE=push \
npx tsx examples/charge-client.ts
STELLAR_SECRET=S... FEE_BUMP_SECRET=S... SERVER_URL=http://localhost:3099 CHARGE_CLIENT_MODE=push \
npx tsx examples/charge-client-fee-bump.ts
STELLAR_SECRET=S... SERVER_URL=http://localhost:3099 \
npx tsx examples/charge-client.ts
STELLAR_SECRET=S... FEE_BUMP_SECRET=S... SERVER_URL=http://localhost:3099 \
npx tsx examples/charge-client-fee-bump.ts
STELLAR_SECRET=S... SERVER_URL=http://localhost:3099 \
npx tsx examples/charge-client.ts
STELLAR_SECRET=S... SERVER_URL=http://localhost:3099 \
npx tsx examples/charge-client.ts
Pass: Each client prints --- Response (200) --- with paid content JSON.
Check 3: Charge E2E Demo
source .env
STELLAR_RECIPIENT="$STELLAR_RECIPIENT" STELLAR_SECRET="$STELLAR_SECRET" timeout 15 ./demo/run.sh
Expected flow:
- Server starts on port 3000 with pino JSON logging
- Client receives 402 Payment Required challenge
- Client signs SAC transfer transaction
- Server verifies and broadcasts on Stellar testnet
- 200 OK with "Payment verified" message
Pass: Client prints --- Response (200) --- with paid content JSON.
Check 4: Channel E2E Demo
source .env
CHANNEL_CONTRACT="$CHANNEL_CONTRACT" COMMITMENT_PUBKEY="$COMMITMENT_PUBKEY" COMMITMENT_SECRET="$COMMITMENT_SECRET" timeout 15 ./demo/run-channel.sh
Expected flow:
- Channel server starts on port 3001 with pino JSON logging
- Client makes 2 requests, signing cumulative commitments off-chain
- Request 1: cumulative 1,000,000 stroops -> 200 OK
- Request 2: cumulative 2,000,000 stroops -> 200 OK
- "No on-chain transaction was needed for this payment!"
Pass: Both requests return 200. Cumulative amount grows between requests.
Check 5: Channel E2E with On-Chain Settlement
Requires the compiled one-way-channel WASM from https://github.com/stellar-experimental/one-way-channel.
WASM_PATH=/Users/marcelosantos/Workspace/one-way-channel/target/wasm32v1-none/release/channel.wasm \
timeout 60 ./demo/run-channel-e2e.sh
Full lifecycle: deploy contract -> 2 off-chain payments -> on-chain close -> balance verified at 0.
Expected flow:
- Deploys one-way-channel contract on Stellar testnet
- Funder opens channel with initial deposit
- 2 off-chain payment commitments via MPP 402 flow
- Recipient closes channel on-chain with latest commitment
- Final balance verified at 0 (all funds claimed)
Pass: Script completes with Channel balance after close: 0 and exit code 0.
Check 5b: Channel E2E with On-Chain Settlement via operator close()
Same lifecycle as Check 5, but settles through the standalone operator close() admin
function (examples/channel-close.ts) instead of the MPP credential flow. This keeps the
operator-close path covered live — the MPP-mode demo (Check 5) does not exercise it.
WASM_PATH=/Users/marcelosantos/Workspace/one-way-channel/target/wasm32v1-none/release/channel.wasm \
CLOSE_MODE=operator timeout 120 ./demo/run-channel-e2e.sh
Pass: Script completes with Channel balance after close: 0 and Step 5 reports
closing channel (operator close()).
Check 6: CHANGELOG Entry
Every PR must add a line to CHANGELOG.md under the ## [Unreleased] heading. Each entry must link to its PR using the format:
- Description of the change [#PR_NUMBER](https://github.com/stellar/stellar-mpp-sdk/pull/PR_NUMBER)
Pass: The diff includes a CHANGELOG.md addition with a PR link in the correct format.
Fail: No CHANGELOG entry, or entry missing the [#N](url) PR link.
Reporting
After running all checks, report:
| # | Check | Status | Notes |
|---|
| 1 | make check (full pipeline + mocked integration) | PASS/FAIL | test count, any errors |
| 1b | Live integration suite (make test-integration) | PASS/FAIL | testnet charge flows |
| 2a | Example scripts (6 scripts) | PASS/FAIL | which scripts failed |
| 2b-1 | Charge: push, no FeeBump | PASS/FAIL | 200 or error |
| 2b-2 | Charge: push + FeeBump | PASS/FAIL | 200 or error |
| 2b-3 | Charge: pull unsponsored | PASS/FAIL | 200 or error |
| 2b-4 | Charge: pull unsponsored + FeeBump | PASS/FAIL | 200 or error |
| 2b-5 | Charge: pull sponsored | PASS/FAIL | 200 or error |
| 2b-6 | Charge: pull sponsored + FeeBump | PASS/FAIL | 200 or error |
| 3 | Charge E2E (demo/run.sh) | PASS/FAIL | final HTTP status |
| 4 | Channel E2E (demo/run-channel.sh) | PASS/FAIL | request count, cumulative amounts |
| 5 | Channel E2E settlement (demo/run-channel-e2e.sh) | PASS/FAIL | balance after close. ATTENTION: this is explained in Check 5 |
| 5b | Channel E2E settlement via operator close() | PASS/FAIL | balance after close (CLOSE_MODE=operator) |
| 6 | CHANGELOG entry | PASS/FAIL | PR link present |