Skip to content

Latest commit

 

History

155 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SwiftRamp Backend

A Fastify service that sits between the SwiftRamp frontend and the swiftramp-swap Soroban contract on Stellar. It does two jobs:

  1. API layer — read-only quotes, relaying client-signed swap transactions, checking swap status, and recent swap history for a wallet.
  2. Rate oracle — polls a real FX rate source on a schedule and pushes set_rate updates on-chain, so the contract's conversion math stays in sync with real-world rates without manual CLI calls.

This service never holds a user's private key. Swaps are built and signed client-side (e.g. via Freighter in the frontend) and only the signed transaction is sent here for submission. The one private key this service does hold is the oracle key — the contract admin's secret key, used only to sign set_rate calls on a timer.


Architecture

┌─────────────┐      quote / submit signed tx      ┌──────────────────┐      Soroban RPC      ┌────────────────────┐
│  Frontend    │ ─────────────────────────────────▶ │  swiftramp-backend│ ─────────────────────▶ │ swiftramp-swap      │
│ (Next.js)    │ ◀───────────────────────────────── │  (this service)   │ ◀───────────────────── │ contract (Soroban)  │
└─────────────┘      status / history JSON          └──────────────────┘      quote/set_rate     └────────────────────┘
                                                              │
                                                              │ scheduled poll (node-cron)
                                                              ▼
                                                     ┌──────────────────┐
                                                     │  FX rate source   │
                                                     │ (open.er-api.com) │
                                                     └──────────────────┘

Project structure

swiftramp-backend/
├── .env.example          # Template for required environment variables
├── .env                  # Your actual secrets/config — never commit this
├── tsconfig.json
├── package.json
└── src/
    ├── server.ts          # Entry point: wires up Fastify, CORS, routes, and the oracle scheduler
    ├── config.ts          # Central env-driven config, validates required vars on boot
    ├── lib/
    │   └── stellar.ts     # All Soroban RPC logic: quote, set_rate, swap submission/status, event history
    ├── routes/
    │   ├── quote.ts       # GET /quote
    │   ├── swap.ts        # POST /swap/submit, GET /swap/:hash/status
    │   └── history.ts     # GET /history/:address
    └── oracle/
        └── rateOracle.ts  # Scheduled job: fetches FX rates, pushes set_rate on-chain

Prerequisites

  • Node.js 18+ (for native fetch support used by the rate oracle)
  • A deployed swiftramp-swap Soroban contract on testnet (or mainnet), already initialized
  • Token contracts registered on that contract for each currency you plan to support (via set_currency_token)
  • The secret key of the contract's admin account

Setup

1. Install dependencies

npm install

2. Configure environment

cp .env.example .env

Then fill in .env:

Variable Description
PORT Port the API listens on (default 4000)
SOROBAN_RPC_URL Soroban RPC endpoint (default: public testnet RPC)
NETWORK_PASSPHRASE Must match the network your contract is deployed to
SWAP_CONTRACT_ID Your deployed contract's C... address
ORACLE_SECRET_KEY Secret key (S...) of the contract's admin account — get it with stellar keys show admin
CURRENCY_TOKENS_JSON JSON map of currency code → token contract address, e.g. {"USD":"C...","NGN":"C..."}
ORACLE_INTERVAL_MS How often the rate oracle runs, in milliseconds (default 5 minutes)

3. Run in development

npm run dev

This starts the server with tsx watch, runs the rate oracle once immediately, and re-runs it on the configured interval.

4. Build and run in production

npm run build
npm start

API reference

GET /health

Liveness check.

{ "ok": true }

GET /quote?from=USD&to=NGN&amount=100

Read-only conversion preview, computed by simulating the contract's own quote function — so the number returned is guaranteed to match what an actual swap would produce, not a client-side copy of the rate table.

Response

{ "from": "USD", "to": "NGN", "sendAmount": "100", "receiveAmount": "158000.0000000" }

POST /swap/submit

Relays an already-signed transaction to the network and waits for confirmation. The frontend builds and signs this transaction itself (e.g. via Freighter) — this endpoint never sees a private key.

Body

{ "signedTxXdr": "<base64 signed transaction envelope>" }

Response

{ "txHash": "a3f...", "receivedAmount": "1580000000000" }

GET /swap/:hash/status

Polls the ledger for a transaction's current status.

Response

{ "status": "SUCCESS", "receivedAmount": "1580000000000" }

status is one of SUCCESS, FAILED, NOT_FOUND.

GET /history/:address

Recent swap contract events involving the given address, either as sender or recipient.

Response

{
  "address": "GDNSOJUOGMIOOBZVSCE2XB7F7WGBHVC3ELL3N47ANO3QFOKN4UMHCIQJ",
  "swaps": [
    {
      "ledger": 123456,
      "txHash": "a3f...",
      "sender": "G...",
      "recipient": "G...",
      "receivedAmount": "1580000000000"
    }
  ]
}

Note: this reads live from the RPC provider's event stream, which typically only retains recent history (days, not months). For a permanent activity log, add a small database that persists events as they're observed rather than re-querying the ledger on every request.


The rate oracle

On startup, and then every ORACLE_INTERVAL_MS, the service:

  1. Fetches USD-based FX rates from a free public source (open.er-api.com, no API key required)
  2. For each currency registered in CURRENCY_TOKENS_JSON, converts that rate to the contract's scaled integer format
  3. Signs and submits a set_rate call using the oracle key, confirming each one lands before moving to the next currency

If the FX source is unreachable, or a specific currency isn't in the response, that run logs an error and skips — it does not crash the server or stop future scheduled runs.

Swapping the FX source: the free API used here is a reasonable default but not guaranteed uptime/SLA. To switch providers, only fetchUsdRates() in src/oracle/rateOracle.ts needs to change — everything downstream (scaling, signing, submission) stays the same.


Security notes

  • ORACLE_SECRET_KEY can move real funds via set_rate authorization and should be treated like any production private key — use a secrets manager in deployment, not a plaintext .env file on a shared server.
  • CORS is currently wide open (origin: true) for development convenience. Restrict this to your actual frontend domain before deploying publicly.
  • This service does not rate-limit or authenticate incoming requests. Add rate limiting (e.g. @fastify/rate-limit) before exposing it beyond local development.

Related repositories

  • Contract: swiftramp-smartcontract — the Soroban contract this service calls (swap, quote, set_rate, etc.)
  • Frontend: the Next.js app — calls /quote for live pricing, and will call /swap/submit once wired up to route signed transactions through this backend instead of hitting Soroban RPC directly.

About

SwiftRamp is a non-custodial currency conversion app built on the Stellar network. It lets someone send value in one currency and have it arrive in another, with every transfer wrapped in a zero-knowledge proof so the amount and resulting balances are never exposed on-chain.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages