Test with confidence. Build on Stellar.
Esure is an open-source testing and simulation toolkit that helps developers validate Stellar payment flows before integrating them into production applications. It turns multi-step Testnet operations into repeatable scenarios with structured reports, transaction links, balance changes, and readable failure explanations.
Important
Esure is an MVP built exclusively for Stellar Testnet. It must not be used for Mainnet transactions or real-value assets.
| Service | URL |
|---|---|
| Web dashboard | esure-testnet.vercel.app |
| Backend health | esure.onrender.com/health |
| OpenAPI document | esure.onrender.com/openapi.json |
The Render free service may sleep after inactivity, so its first response can take longer than subsequent requests.
- 13 Bundled Scenarios: XLM payments, issued assets, trustlines, expected failures, multi-op transactions (Testnet only)
- Developer-Friendly Errors: Plain-English explanations for 30+ Stellar error codes with causes and fix suggestions
- Command-Line Interface: Run scenarios from terminal or CI pipelines (run from source; not yet published to npm)
- CI/CD Integration: First-class GitHub Actions, GitLab CI, and CircleCI support
- Declarative Testing: Bounded JSON/YAML scenario definitions with schema validation
- Isolated Execution: Fresh Testnet accounts per run, no cross-run contamination
- Comprehensive Reports: Steps, assertions, transaction hashes, balance changes, and readable failures
- Security by Design: Testnet-only, rejects secrets/Mainnet, ephemeral keys, sanitized outputs
- Optional Persistence: PostgreSQL-backed scenario catalogue and version history
See the MVP specification for the complete product scope.
A realistic Stellar payment test spans account creation, Friendbot funding, trustline configuration, transaction submission, ledger confirmation, and balance verification. Rebuilding that flow manually makes failures difficult to reproduce and easy to misdiagnose. Esure packages the full process into versioned scenarios that can be executed repeatedly and inspected through one consistent report format.
Esure is useful for:
- payment and remittance teams validating integration assumptions;
- wallet developers testing asset and trustline behavior;
- educators demonstrating common Stellar transaction flows;
- contributors reproducing protocol-level failures safely on Testnet; and
- CI workflows that need deterministic validation without storing secret keys.
Unlike Stellar Lab, which requires manual step-through and copy/paste workflow, Esure executes multi-step flows declaratively and generates structured reports. Unlike the Stellar Quickstart Docker image, Esure focuses on scenario-driven testing rather than node operation. Unlike hand-written SDK tests, Esure provides repeatable scenario definitions with expected-failure assertions and CI-friendly JSON output.
Esure includes 13 bundled scenarios for Testnet integration testing:
| Scenario | Purpose | Expected result |
|---|---|---|
xlm-payment |
Fund two accounts, transfer 5 XLM, verify balance change | Pass |
issued-asset-payment |
Create TESTUSD trustline, issue 100 TESTUSD, verify balance | Pass |
missing-trustline |
Attempt issued-asset payment without recipient trustline | Controlled op_no_trust failure |
insufficient-xlm-balance |
Attempt to send more XLM than available in account | Controlled op_underfunded failure |
unfunded-destination |
Pay a generated destination that has not been funded or created on the ledger | Controlled tx_failed/op_no_destination failure |
payment-with-memo |
Send XLM with a text memo and verify it on the confirmed transaction | Pass |
multi-operation-transaction |
Execute trustline + payment atomically in single tx | Pass |
trustline-limit-exceeded |
Attempt payment exceeding trustline limit | Controlled op_line_full failure |
trustline-limit-exact |
Pay exactly the fractional trustline limit and verify the final balance | Pass |
trustline-already-exists |
Multiple payments using existing trustline | Pass |
basic-payment-with-checks |
Send XLM and verify both accounts exist after transaction | Pass |
minimum-xlm-payment |
Send minimum viable XLM amount (1 stroop = 0.0000001 XLM) | Pass |
Each run generates fresh Testnet accounts, so repeated executions remain isolated from previous runs. See SCENARIOS.md for complete scenario documentation.
- The dashboard requests the published scenario catalogue from the backend.
- The user selects a scenario and starts a run through the same-origin proxy.
- The backend validates the bounded scenario definition before generating any accounts or making network requests.
- The runner creates isolated accounts, obtains Testnet funds, and executes each declared operation in order.
- Assertions compare the observed transaction result or balance with the scenario expectation.
- The frontend polls the run endpoint and renders progress, transaction links, assertions, and sanitized failures.
- The final structured report is available from the report endpoint.
Browser
|
v
Next.js dashboard and same-origin proxy
|
v
Fastify API and scenario runner
|----------------------|
v v
Stellar Testnet PostgreSQL (optional)
| Directory | Responsibility |
|---|---|
esure-frontend |
Next.js dashboard and browser-facing API proxy |
esure-backend |
Fastify API, validation, reporting, and Testnet execution |
esure-cli |
Command-line interface for terminal and CI usage |
esure-docs |
Product, API, architecture, scenario, CI, and persistence documentation |
esure-contracts |
Design space for post-MVP Soroban fixtures |
.github |
CI, issue templates, contribution guidance, and security policy |
For deeper technical context, read the architecture documentation.
- Git
- Node.js 20 or newer
- npm
- Network access to Stellar Testnet for live runs
git clone https://github.com/Esureorg/Esure.git
cd Esure
cd esure-backend
npm ci
cd ../esure-frontend
npm ci
cd ..macOS and Linux:
cp esure-backend/.env.example esure-backend/.env
cp esure-frontend/.env.example esure-frontend/.env.localWindows PowerShell:
Copy-Item esure-backend/.env.example esure-backend/.env
Copy-Item esure-frontend/.env.example esure-frontend/.env.localThe frontend defaults to a backend at http://127.0.0.1:3001. To use another
backend, set ESURE_BACKEND_URL in esure-frontend/.env.local.
In the first terminal:
cd esure-backend
npm run devVerify the API:
curl http://127.0.0.1:3001/health
curl http://127.0.0.1:3001/api/v1/scenariosIn a second terminal:
cd esure-frontend
npm run devOpen http://localhost:3000.
curl -X POST http://127.0.0.1:3001/api/v1/runs \
-H "content-type: application/json" \
-d '{"scenarioId":"issued-asset-payment","inputs":{}}'The response contains a run ID. Use it to retrieve progress and the final report:
curl http://127.0.0.1:3001/api/v1/runs/RUN_ID
curl http://127.0.0.1:3001/api/v1/runs/RUN_ID/reportNote
The CLI is not yet published to npm. Run it from source as shown below.
Esure includes a command-line interface for terminal and CI pipeline usage:
# Clone and build the CLI (one time)
git clone https://github.com/Esureorg/Esure.git
cd Esure/esure-cli
npm ci
npm run build
# List bundled scenarios
node dist/cli.js list
# Validate a scenario file
node dist/cli.js validate ./my-scenario.yaml
# Run a bundled scenario
node dist/cli.js run xlm-payment
# Run a custom scenario with JSON output
node dist/cli.js run ./my-scenario.yaml --output jsonAdd Esure to your CI pipeline:
# .github/workflows/stellar-tests.yml
- name: Install Esure CLI
run: |
cd esure-cli
npm ci
npm run build
- name: Run Stellar tests
run: |
cd esure-cli
node dist/cli.js run ./tests/payment-flow.yaml --output jsonSee CI_INTEGRATION.md for complete CI/CD integration guide with GitHub Actions, GitLab CI, and CircleCI examples.
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/health |
Confirm the API process is available |
GET |
/ready |
Confirm dependencies are ready |
GET |
/api/v1/scenarios |
List available scenarios |
POST |
/api/v1/scenarios/validate |
Validate a scenario definition |
POST |
/api/v1/runs |
Start a bundled scenario |
GET |
/api/v1/runs/:runId |
Read current run state |
GET |
/api/v1/runs/:runId/report |
Download a structured report |
The complete contract is available from the deployed OpenAPI document and the API documentation.
| Variable | Default | Description |
|---|---|---|
ESURE_BACKEND_URL |
http://127.0.0.1:3001 |
Server-only backend origin used by the Next.js proxy |
Restart the local frontend or redeploy it after changing ESURE_BACKEND_URL.
The accepted integer bounds are inclusive. Backend settings are derived from
loadConfig and take effect when the backend
starts; restart it after changing them.
| Variable | Default | Accepted value / bounds | Effect and applicability |
|---|---|---|---|
HOST |
127.0.0.1 |
String; not validated | Backend bind address. Use 0.0.0.0 where the host must accept external connections. All modes. |
PORT |
3001 |
Integer, 1-65535 |
Backend HTTP port. Render supplies this automatically. All modes. |
LOG_LEVEL |
info |
String; not validated | Currently read by loadConfig but not applied by the server; changing it has no logging effect. |
STELLAR_HORIZON_URL |
https://horizon-testnet.stellar.org |
Valid HTTPS URL containing testnet |
Horizon endpoint. Mainnet URLs are rejected. All modes. |
STELLAR_FRIENDBOT_URL |
https://friendbot.stellar.org |
Must be this exact HTTPS URL | Friendbot endpoint. Other URLs are rejected. All modes. |
RUN_TIMEOUT_MS |
120000 |
Milliseconds; integer, 1000-600000 |
Maximum total duration of one run. All modes. |
STEP_TIMEOUT_MS |
30000 |
Milliseconds; integer, 500-120000 |
Maximum duration of one scenario step. All modes. |
MAX_CONCURRENT_RUNS |
2 |
Active runs; integer, 1-20 |
Maximum simultaneous executions; excess starts are rejected, not queued. All modes. |
METRICS_ENABLED |
false |
Literal true or false |
Enables the process-local aggregate GET /metrics endpoint. Disabled by default; when enabled, restrict access to trusted monitoring systems on a private network or behind an authenticated/allowlisted reverse proxy. Metrics reset on restart and are per process. |
MAX_STORED_RUNS |
500 |
Runs; integer, 10-10000 |
In-memory run-store capacity only; ignored by the PostgreSQL run store. |
RUN_RETENTION_MS |
3600000 |
Milliseconds; integer, 60000-86400000 |
Retention for completed run reports in either run store. PostgreSQL reports past this age are hidden from reads but not deleted by this setting. |
RATE_LIMIT_MAX |
120 |
Requests per window; integer, 1-10000 |
Per-IP fixed-window limit for requests other than run creation. All modes. |
RUN_RATE_LIMIT_MAX |
10 |
Requests per window; integer, 1-1000 |
Per-IP fixed-window limit for run-creation endpoints. All modes. |
RATE_LIMIT_WINDOW_MS |
60000 |
Milliseconds; integer, 1000-3600000 |
Shared fixed-window duration for both rate limits. All modes. |
BODY_LIMIT_BYTES |
16384 |
Bytes; integer, 1024-1048576 |
Maximum request-body size. All modes. |
SCENARIO_DIRECTORY |
Unset | Directory path; not validated | Optional directory of additional bounded JSON/YAML scenarios, loaded at startup. All modes. |
PERSISTENCE_MODE |
disabled |
disabled or published |
published enables the PostgreSQL scenario catalogue and requires DATABASE_URL; startup fails if migrations are not current. |
RUN_PERSISTENCE_ENABLED |
false |
Literal true or false |
true persists run reports in PostgreSQL and requires PERSISTENCE_MODE=published; otherwise reports use the in-memory store. |
DATABASE_URL |
Unset | PostgreSQL connection URL | Application login used by the running backend; required in published mode. Do not use migration credentials here. |
DATABASE_MIGRATION_URL |
Unset | PostgreSQL connection URL | Migration login used only by the explicit db:migrate and db:reconcile commands; not used by the running backend. |
DATABASE_POOL_MAX |
5 |
Connections; integer, 1-20 |
Maximum PostgreSQL pool size. Used only in published mode. |
DATABASE_CONNECTION_TIMEOUT_MS |
5000 |
Milliseconds; integer, 500-30000 |
PostgreSQL connection timeout. Used only in published mode. |
DATABASE_QUERY_TIMEOUT_MS |
5000 |
Milliseconds; integer, 100-30000 |
PostgreSQL statement and query timeout. Used only in published mode. |
DATABASE_IDLE_TIMEOUT_MS |
10000 |
Milliseconds; integer, 1000-60000 |
Time before an idle PostgreSQL pool connection is closed. Used only in published mode. |
RUN_TIMEOUT_MS limits the whole run while STEP_TIMEOUT_MS limits each
individual step; either timeout can end execution first. MAX_CONCURRENT_RUNS
limits active executions, while MAX_STORED_RUNS separately limits only the
in-memory report store. When that store is full it evicts a terminal report; it
does not evict active runs. RUN_RETENTION_MS applies to completed reports in
memory and PostgreSQL, but PostgreSQL rows remain stored. The two rate limits
share RATE_LIMIT_WINDOW_MS and are process-local.
DATABASE_URL is for the application role only. Set DATABASE_MIGRATION_URL
for the one-shot migration commands, using the separate migration role; it does
not require a backend restart because those commands read it when invoked.
Replace credential placeholders with local or provider-managed credentials and
never commit them. See esure-backend/.env.example
and the persistence guide for setup details.
Run the same deterministic checks used by CI:
cd esure-backend
npm run check
npm run build
cd ../esure-frontend
npm run checkNormal automated tests do not submit real transactions. The opt-in Testnet smoke test is documented in the backend README.
The default PERSISTENCE_MODE=disabled configuration keeps runtime state in
memory. Published scenario catalogue persistence can be enabled with PostgreSQL
after applying the checked migrations and configuring the required database
roles. See the persistence guide before enabling it.
| Component | Platform | Root directory |
|---|---|---|
| Frontend | Vercel | esure-frontend |
| Backend | Render | esure-backend |
| Database | PostgreSQL provider | Not applicable |
Set ESURE_BACKEND_URL in Vercel to the public Render backend origin. Keep
secrets and migration credentials in the hosting provider environment settings;
never commit them to the repository.
The Render free tier sleeps after 15 minutes of inactivity. To keep the backend responsive, use a free uptime monitoring service (UptimeRobot, Cron-job.org, or similar) to ping /health every 10-14 minutes. This prevents cold starts for dashboard users. See the troubleshooting section for cold-start delay behavior.
| Capability | Shipped in this checkout | Planned / proposal |
|---|---|---|
| Testnet scenarios and reports | Bounded scenario validation, classic Stellar execution, expected-failure reporting, and dashboard reports are shipped. Mainnet is rejected. | Mainnet use is out of scope. |
| Published scenario catalogue | Optional PostgreSQL catalogue with version history and migration checks; see the persistence implementation. | No additional catalogue phase is recorded. |
| Run history | Reports are in memory by default. RUN_PERSISTENCE_ENABLED=true with PERSISTENCE_MODE=published opts into PostgreSQL report storage; see the run stores. |
Durable run execution, worker leases, and recovery are planned in #21. |
| Primary browser journey | Playwright covers scenario selection, run start, polling, and passed/failed reports in the journey test; the monorepo workflow runs it. | No additional primary-journey work is recorded. |
| Soroban contract testing | Not in the MVP execution path. | Design examples are proposals in esure-contracts. |
Active work is tracked in GitHub Issues.
The free Render backend can sleep after inactivity. Wait for the first request to wake the service, then retry. Check the health endpoint if the delay continues.
Confirm that ESURE_BACKEND_URL contains the backend origin without a trailing
API path, for example https://esure.onrender.com, and redeploy the frontend
after changing a Vercel environment variable.
Published mode fails closed when DATABASE_URL is missing or migrations are
stale. Apply the role bootstrap, migrations, and fixture reconciliation from the
persistence guide, or return to
PERSISTENCE_MODE=disabled while developing without PostgreSQL.
Friendbot and Horizon are external Testnet services and can be temporarily unavailable or rate limited. Inspect the sanitized run error, wait briefly, and retry with a new isolated run.
Esure rejects secret seeds, raw XDR, scripts, arbitrary URLs, unresolved references, unknown scenario properties, and Mainnet configuration. Generated Testnet secrets remain only in process memory during execution and are excluded from API responses, logs, and reports.
Please report vulnerabilities according to the security policy. Do not disclose sensitive findings in a public issue.
Contributions are welcome. Before opening a pull request:
- Read the contribution guide.
- Choose or open a focused GitHub issue.
- Keep changes within the documented Testnet-only safety boundary.
- Run the relevant checks locally.
- Link the pull request to its issue.
Esure is released under the MIT License.