Skip to content

Tomoya0k/StellarGate

 
 

Repository files navigation

StellarGate

CI

A developer-friendly payment gateway API built on Stellar for accepting, verifying, and managing payments in XLM and USDC.

Think Stripe — but powered by the Stellar blockchain instead of banks.

Overview

StellarGate abstracts Stellar payments into a simple REST API. Developers can create payment intents, receive a destination address and memo, and get notified when payment is confirmed on-chain.

Client App → POST /payments → get address + memo
User pays via Stellar wallet (e.g. Lobstr)
StellarGate detects transaction on Horizon
Payment marked complete → webhook fired to your app

Current Status

This project is under active development. The following is implemented:

  • POST /payments — create a payment intent
  • GET /payments/:id — query payment status
  • GET /payments — list & filter payments (pagination)
  • GET /health — health check
  • SQLite persistence
  • Input validation (asset, amount as exact stroops, webhook URL)
  • Transaction listener (Horizon SSE streaming + interval polling)
  • Payment verification (memo + asset + amount)
  • Webhook dispatch (HMAC-SHA256 signed, with retries)
  • Multi-merchant support (merchant_id per payment)
  • Pending-intent expiry (configurable TTL + payment.expired webhook)
  • Horizon streaming (currently polled on an interval)
  • Dashboard UI

Tech Stack

Getting Started

Prerequisites

Setup

git clone https://github.com/StellarGateLabs/StellarGate.git
cd StellarGate

cp .env.example .env
# Edit .env with your Stellar keys

Environment Variables

Variable Description Default
PORT HTTP port 3000
DATABASE_URL sqlx connection string sqlite:stellargate.db
STELLAR_NETWORK testnet or public testnet
STELLAR_HORIZON_URL Horizon endpoint testnet
STELLAR_GATEWAY_PUBLIC Your gateway wallet public key
STELLAR_GATEWAY_SECRET Your gateway wallet secret key
ACCEPTED_ASSETS Comma-separated assets to accept. Format: CODE for native (e.g. XLM) or CODE:ISSUER for non-native (e.g. USDC:GISSUER). Adding an asset is config-only — no code changes needed. XLM,USDC:<testnet-issuer>
STELLAR_LISTENER_MODE stream (SSE + poller reconciler) or poll (interval only) stream
POLL_INTERVAL_SECS How often the Horizon poller reconciles 10
PAYMENT_TTL_SECS How long a payment intent stays pending before it is expired (from created_at) 3600
WEBHOOK_SECRET HMAC signing secret for webhooks
WEBHOOK_RETRY_ATTEMPTS Webhook delivery attempts 3
WEBHOOK_RETRY_DELAY_MS Delay between webhook retries 5000
CORS_ALLOWED_ORIGINS Comma-separated allowed CORS origins (e.g. https://app.example.com). Required on public network; omitting on testnet falls back to permissive with a warning. (unset — permissive on testnet)
RATE_LIMIT_REQUESTS_PER_SEC Rate limit for POST /payments (requests per second per IP) 10

DATABASE_URL is a sqlx connection string (sqlite:stellargate.db), not a file path. The Horizon poller stays idle until STELLAR_GATEWAY_PUBLIC is set. The poller pages forward through payments from a cursor persisted in the database, so it never misses an intent regardless of on-chain volume and resumes from where it left off after a restart.

Run

cargo run

Docker Compose

The quickest way to run StellarGate without installing Rust:

cp .env.example .env
# Edit .env with your Stellar keys, then:
docker compose up --build

The API will be available at http://localhost:3000. The SQLite database is stored in a named Docker volume (stellargate_data) so it persists across container restarts. Verify the service is healthy:

curl http://localhost:3000/health
# {"status":"ok"}

To stop and remove containers while keeping the database volume:

docker compose down

Test

cargo test

Tests cover amount/stroops handling, Horizon payment verification, webhook signing, and the HTTP API (create, fetch, list/filter, validation).

API Reference

POST /payments

Create a new payment intent.

Request

{
  "amount": "10.00",
  "asset": "XLM",
  "merchant_id": "your-merchant-id",
  "webhook_url": "https://yourapp.com/webhooks/stellar"
}
Field Type Required Values
amount string Any positive number
asset string XLM or USDC
merchant_id string Any string
webhook_url string Valid HTTPS URL

Response 201 Created

{
  "id": "a1b2c3d4-...",
  "destination_address": "GBBD47IF6LWK7P7...",
  "memo": "A1B2C3D4",
  "amount": "10.00",
  "asset": "XLM",
  "status": "pending",
  "created_at": "2026-04-29T15:00:00",
  "expires_at": "2026-04-29T16:00:00"
}

The user must send exactly amount of asset to destination_address with memo set as the transaction memo. The intent expires at expires_at (default one hour after creation) if unpaid.


GET /payments/:id

Fetch the current status of a payment.

Response 200 OK

{
  "id": "a1b2c3d4-...",
  "merchant_id": "your-merchant-id",
  "destination_address": "GBBD47IF6LWK7P7...",
  "memo": "A1B2C3D4",
  "amount": "10.00",
  "asset": "XLM",
  "status": "pending",
  "tx_hash": null,
  "paid_amount": null,
  "created_at": "2026-04-29T15:00:00",
  "updated_at": "2026-04-29T15:00:00",
  "expires_at": "2026-04-29T16:00:00"
}

Status values

Status Meaning
pending Awaiting payment
completed Payment confirmed on-chain
failed Partial payment or verification failed
expired TTL elapsed before payment arrived; no longer watched

GET /payments

List payments, newest first.

Query parameters

Param Description Default
status Filter by pending, completed, failed, or expired all
limit Page size (1–100) 20
offset Rows to skip 0

Response 200 OK

{
  "total": 42,
  "limit": 20,
  "offset": 0,
  "payments": [ { "id": "...", "status": "pending", "...": "..." } ]
}

GET /health

Cheap liveness probe. Always returns 200 OK as long as the process is running.

200 OK — { "status": "ok" }

GET /ready

Readiness probe. Runs SELECT 1 against the database; returns 503 when unreachable.

200 OK          — { "status": "ok" }
503 Unavailable — { "status": "unavailable" }

Payment Flow

1. Developer calls POST /payments
2. StellarGate returns { destination_address, memo, amount }
3. End user sends payment via any Stellar wallet
4. StellarGate listener detects the transaction on Horizon (SSE stream, ~1s; poller as fallback)
5. Verifies: correct memo + amount + asset
6. Updates payment status and fires a webhook event

Payment Resolution Policy

Every on-chain payment matched by memo, destination, and asset is resolved as follows:

Scenario status Webhook event delta field
Paid exactly the requested amount completed payment.completed not present
Paid more than requested completed payment.overpaid excess amount (should be refunded)
Paid less than requested underpaid payment.underpaid shortfall still owed
Top-up brings cumulative total to exactly expected completed payment.completed not present
Top-up brings cumulative total above expected completed payment.overpaid cumulative excess

Overpayment: The intent is fulfilled and moves to completed. The payment.overpaid event includes a delta field showing the excess amount the merchant should consider refunding to the sender.

Underpayment: The intent moves to underpaid and remains watchable. StellarGate continues polling for a follow-up payment to the same memo. When the cumulative total meets or exceeds the requested amount, the intent completes normally.

Top-up limitation: Only a single follow-up payment is tracked per underpaid intent. If multiple partial payments are needed, the sender should consolidate them — send the full remaining shortfall (shown in delta) in one transaction.

Post-completion payments: Once an intent reaches completed, any further on-chain payments to the same address and memo are not tracked and will not trigger additional webhooks.

Webhook Events

payment.completed

Fired when the cumulative received amount equals the requested amount exactly.

{
  "event": "payment.completed",
  "payment_id": "a1b2c3d4-...",
  "merchant_id": "your-merchant-id",
  "tx_hash": "abc123...",
  "amount": "10.00",
  "paid_amount": "10",
  "asset": "XLM",
  "status": "completed"
}

payment.overpaid

Fired when the cumulative received amount exceeds the requested amount. delta is the excess the merchant should refund.

{
  "event": "payment.overpaid",
  "payment_id": "a1b2c3d4-...",
  "merchant_id": "your-merchant-id",
  "tx_hash": "abc123...",
  "amount": "10.00",
  "paid_amount": "12.5",
  "asset": "XLM",
  "status": "completed",
  "delta": "2.5"
}

payment.underpaid

Fired when a payment is received but falls short of the requested amount. delta is the remaining shortfall. The intent stays open for a top-up.

{
  "event": "payment.underpaid",
  "payment_id": "a1b2c3d4-...",
  "merchant_id": "your-merchant-id",
  "tx_hash": "abc123...",
  "amount": "10.00",
  "paid_amount": "7",
  "asset": "XLM",
  "status": "underpaid",
  "delta": "3"
}

Event types: payment.success (paid in full), payment.failed (underpaid or verification failed), and payment.expired (the intent's TTL elapsed before payment arrived). The event field carries the type; status carries the matching payment status.

Webhooks are signed with X-StellarGate-Signature (HMAC-SHA256) so you can verify authenticity.

Project Structure

src/
├── main.rs          # Entry point, server startup, listener/poller spawn, graceful shutdown
├── lib.rs           # Shared state and module exports
├── config.rs        # Environment configuration
├── db.rs            # Database queries (SQLite)
├── money.rs         # Stroops-based amount parsing/validation
├── horizon.rs       # Horizon polling listener + payment verification
├── expiry.rs        # Background sweeper that expires overdue pending intents
├── webhook.rs       # HMAC-SHA256 signed webhook dispatch
└── api/
    ├── mod.rs       # Axum router, layers (CORS/trace/body-limit), 404 fallback
    └── payments.rs  # Payment handlers (create, get, list)

tests/
└── api_tests.rs     # Integration tests

Contributing

This project is open to contributors. See the Wave Program for scoped issues you can pick up.

To contribute:

  1. Fork the repo
  2. Create a branch: git checkout -b feat/your-feature
  3. Make your changes and add tests
  4. Run cargo test — all tests must pass
  5. Open a pull request

License

MIT

About

Stellar payment gateway built with Rust, Axum, SQLite and Horizon API.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Rust 98.8%
  • Dockerfile 1.2%