A comprehensive SDK for building cross-border payment applications on the Stellar network, featuring time-locked escrow, on-chain exchange rate oracles, and built-in compliance checks.
- Time-Locked Escrow: Secure cross-border settlements with automatic release
- Exchange Rate Oracle: On-chain aggregated rates for USD/EUR/MXN pairs
- Compliance Engine: KYC/AML hooks and regulatory compliance checks
- Dispute Resolution: Automated and manual dispute handling
- Fee Bump Support: Reliable transactions for cross-border senders
- Soroban Contracts: Rust smart contracts for Stellar
- TypeScript SDK: High-level API for contract interaction
- React Components: Pre-built UI components for payments
- Examples: Complete implementation patterns
- Installation
- Quick Start
- Architecture
- Contracts
- TypeScript SDK
- React Components
- Examples
- CLI Tool
- Status Monitoring
- Configuration
- API Reference
- Building
- Testing
- Deployment
- Contributing
- Node.js 18+
- Rust 1.78+ (for contract compilation)
- Stellar CLI 21+ (for contract deployment)
# Clone the repository
git clone https://github.com/Kevin737866/stellar-cross-border-payments-sdk.git
cd stellar-cross-border-payments-sdk
# Install dependencies for all packages (uses npm workspaces)
npm install
# Install Rust wasm target
rustup target add wasm32-unknown-unknowncp .env.example .env
# Edit .env with your contract addresses and keys — see docs/deployment.mdimport { StellarCrossBorderSDK } from '@stellar-cross-border/sdk';
// Configure for testnet
const config = StellarCrossBorderSDK.createTestnetConfig();
const contracts = {
escrow: 'YOUR_ESCROW_CONTRACT_ADDRESS',
rateOracle: 'YOUR_RATE_ORACLE_CONTRACT_ADDRESS',
compliance: 'YOUR_COMPLIANCE_CONTRACT_ADDRESS',
};
const sdk = new StellarCrossBorderSDK(config, contracts);import { Keypair } from 'stellar-sdk';
// Generate keypairs
const sender = Keypair.random();
const receiver = Keypair.random();
// Create payment
const paymentRequest = {
from: sender.publicKey(),
to: receiver.publicKey(),
amount: '1000', // $1000 USD
token: 'USDC',
release_time: Math.floor(Date.now() / 1000) + (24 * 60 * 60), // 24 hours
metadata: {
purpose: new TextEncoder().encode('remittance'),
reference: new TextEncoder().encode('US-MX-2024-001'),
},
};
const result = await sdk.paymentsInstance.createPayment(paymentRequest, {
feeBump: true,
memo: 'Cross-border payment',
});
console.log(`Payment created: ${result.escrowId}`);const rateResult = await sdk.paymentsInstance.getExchangeRate({
from_currency: 'USD',
to_currency: 'MXN',
});
console.log(`1 USD = ${rateResult.rate} MXN`);const complianceResult = await sdk.paymentsInstance.checkCompliance({
from_user: sender.publicKey(),
to_user: receiver.publicKey(),
amount: '1000',
currency: 'USD',
jurisdiction_from: 'US',
jurisdiction_to: 'MX',
});
if (complianceResult.approved) {
console.log('Payment is compliant');
} else {
console.log(`Compliance check failed: ${complianceResult.reason}`);
}stellar-cross-border-payments-sdk/
├── src/ # Soroban contracts (Rust)
│ ├── escrow.rs # Time-locked escrow logic
│ ├── rate_oracle.rs # Exchange rate aggregation
│ ├── compliance.rs # KYC/AML compliance checks
│ └── lib.rs # Contract exports
├── sdk/ # TypeScript SDK
│ ├── src/
│ │ ├── client.ts # Stellar client wrapper
│ │ ├── payments.ts # High-level payment API
│ │ ├── types.ts # TypeScript interfaces
│ │ └── index.ts # Barrel exports
│ └── package.json
├── ui/ # React components
│ ├── src/
│ │ ├── components/ # Payment UI components
│ │ └── hooks/ # React hooks
│ └── package.json
├── cli/ # CLI tool (stellar-payout)
│ ├── src/
│ │ ├── commands/ # batch, status, retry, report
│ │ ├── parsers/ # CSV, JSON, XLSX, MT103
│ │ ├── utils/ # Database, validation, logger
│ │ ├── types.ts # CLI type definitions
│ │ └── index.ts # CLI entry point
│ └── package.json
├── examples/ # Usage examples
│ ├── usd-to-mxn.ts # US to Mexico remittance
│ ├── eur-to-usd.ts # Europe to US business payment
│ ├── escrow-dispute.ts # Dispute resolution
│ ├── payroll-batch.csv # 50-employee payroll sample
│ └── aid-disbursement.ts # UNHCR-style rapid response
└── README.md
The escrow contract provides time-locked payment protection:
// Create escrow
pub fn create_escrow(
env: Env,
sender: Address,
receiver: Address,
amount: i128,
token: Address,
release_time: u64,
metadata: Map<Symbol, Vec<u8>>,
) -> BytesN<32>
// Release funds
pub fn release_escrow(env: Env, escrow_id: BytesN<32>) -> bool
// Refund payment
pub fn refund_escrow(env: Env, escrow_id: BytesN<32>) -> bool
// Open dispute
pub fn dispute_escrow(
env: Env,
escrow_id: BytesN<32>,
challenger: Address,
reason: Symbol,
evidence: Vec<u8>,
) -> boolAggregates exchange rates from multiple sources:
// Submit rate
pub fn submit_rate(
env: Env,
source: Address,
from_currency: Symbol,
to_currency: Symbol,
rate: u128,
confidence: u8,
) -> bool
// Get aggregated rate
pub fn get_rate(env: Env, from_currency: Symbol, to_currency: Symbol) -> AggregatedRateHandles KYC/AML checks and regulatory compliance:
// Check transaction compliance
pub fn check_transaction_compliance(
env: Env,
transaction_id: BytesN<32>,
from_user: Address,
to_user: Address,
amount: i128,
currency: Symbol,
jurisdiction_from: Symbol,
jurisdiction_to: Symbol,
) -> ComplianceCheckLow-level Stellar network interaction:
const client = new StellarClient(config, contracts);
// Get account info
const account = await client.getAccount('GD...');
// Submit transaction
const result = await client.submitTransaction(transactionXdr);
// Get contract data
const data = await client.getContractData(contractId, key);High-level payment operations:
const payments = new StellarPayments(client);
// Create payment
const result = await payments.createPayment(request, options);
// Release escrow
await payments.releaseEscrow(escrowId, signer);
// Get exchange rate
const rate = await payments.getExchangeRate({ from_currency: 'USD', to_currency: 'MXN' });
// Check compliance
const compliance = await payments.checkCompliance(request);Complete payment creation form:
import { PaymentForm } from '@stellar-cross-border/ui';
<PaymentForm
sdk={sdk}
onSuccess={(result) => console.log('Payment created:', result)}
onError={(error) => console.error('Payment failed:', error)}
/>Real-time escrow monitoring:
import { EscrowStatusComponent } from '@stellar-cross-border/ui';
<EscrowStatusComponent
sdk={sdk}
escrowId="ESCROW_ID_HERE"
onStatusChange={(status) => console.log('Status changed:', status)}
showActions={true}
/>Live exchange rate display:
import { ExchangeRateDisplay } from '@stellar-cross-border/ui';
<ExchangeRateDisplay
sdk={sdk}
fromCurrency="USD"
toCurrency="MXN"
amount="1000"
autoRefresh={true}
/>A powerful React hook for managing the entire lifecycle of a Stellar cross-border payment.
import { useStellarPayment } from '@stellar-cross-border/ui';
const MyComponent = ({ sdk, escrowId }) => {
const {
loading,
error,
paymentStatus,
createPayment,
releaseEscrow,
refreshStatus,
} = useStellarPayment(sdk, escrowId, { autoRefresh: true });
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<p>Status: {paymentStatus?.status}</p>
<button onClick={() => releaseEscrow(escrowId, signer)}>Release Payment</button>
</div>
);
};Inputs:
sdk: An instance ofStellarCrossBorderSDK.escrowId(optional): The ID of the escrow to monitor.options(optional):autoRefresh: Automatically polls for status updates (default:true).refreshInterval: Time between polls in milliseconds (default:30000).
Returns: An object containing:
State:
loading:boolean- Indicates if an operation is in progress.error:string | null- Contains error message if an operation fails.paymentStatus:PaymentStatus | null- The current status of the payment.exchangeRate:ExchangeRateResult | null- The latest exchange rate information.complianceCheck:ComplianceCheckResult | null- The result of the compliance check.
Actions:
createPayment(request, options): Initiates a new payment.releaseEscrow(escrowId, signer, options): Releases funds from an escrow.refundEscrow(escrowId, signer, options): Refunds an escrow.disputeEscrow(escrowId, challenger, reason, evidence, signer, options): Opens a dispute.getExchangeRate(request): Fetches the latest exchange rate.checkCompliance(request): Performs a compliance check.getPaymentStatus(escrowId): Fetches the status of a specific escrow.refreshStatus(): Manually triggers a status refresh.clearError(): Resets the error state.
# Run the example
npx ts-node examples/usd-to-mxn.tsThis example demonstrates:
- Creating a cross-border remittance
- Exchange rate conversion
- Compliance checking
- Time-locked escrow
- Payment release
# Run the example
npx ts-node examples/eur-to-usd.tsThis example demonstrates:
- B2B payment workflows
- Enhanced compliance for large amounts
- Business metadata handling
- Multi-step approval process
# Run the example
npx ts-node examples/escrow-dispute.tsThis example demonstrates:
- Dispute creation
- Evidence collection
- Admin resolution
- Refund processing
A purpose-built CLI for processing batch cross-border payments, designed for humanitarian aid organizations, global payroll providers, and neobanks.
# Install globally via npm
cd cli
npm install
npm run build
npm link
# Or run directly
npx stellar-payout --help# Process payments from CSV
stellar-payout batch --input payments.csv --source-secret $SECRET_KEY --network testnet
# Dry-run mode (simulate without submitting)
stellar-payout batch --input payments.csv --source-secret $SECRET_KEY --dry-run
# Process from JSON
stellar-payout batch --input payments.json --format json --source-secret $SECRET_KEY
# Process from Excel
stellar-payout batch --input payments.xlsx --format xlsx --source-secret $SECRET_KEY
# Process SWIFT MT103 messages
stellar-payout batch --input transfers.mt103 --format mt103 --source-secret $SECRET_KEY
# Advanced options
stellar-payout batch --input payments.csv --source-secret $SECRET_KEY \
--max-ops 100 \
--concurrency 5 \
--fee-surge-threshold 100 \
--network testnetQuery the state of any batch and optionally stream live updates while it runs.
Options
| Flag | Env var | Default | Description |
|---|---|---|---|
-b, --batch-id <id> |
— | (omit to list recent) | Batch ID to inspect. Omit to show the 10 most recent batches. |
-f, --follow |
— | false |
Stream live progress updates until the batch finishes (Ctrl+C to stop). Only active while the batch status is running. |
--horizon-url <url> |
HORIZON_URL |
https://horizon-testnet.stellar.org |
Horizon endpoint used for Horizon transaction streaming when --follow is set. |
--db-path <path> |
DB_PATH |
./stellar-payout.db |
Path to the SQLite database written by stellar-payout batch. Must point to the same file used during batch processing. |
--verbose |
— | false |
Print debug output including the Horizon stream endpoint. |
Usage examples
# List the 10 most recent batches across all runs
stellar-payout status
# Inspect a specific batch (snapshot, no streaming)
stellar-payout status --batch-id batch_1735000000000_abc123
# Stream live progress while the batch is running (polls every 2 s,
# also tails new Horizon transactions every 5 s)
stellar-payout status --batch-id batch_1735000000000_abc123 --follow
# Use a non-default database path (must match the path passed to batch)
stellar-payout status --batch-id batch_1735000000000_abc123 \
--db-path /data/payroll.db
# Point at mainnet Horizon when streaming a mainnet batch
stellar-payout status --batch-id batch_1735000000000_abc123 --follow \
--horizon-url https://horizon.stellar.org \
--db-path ./mainnet-payout.db
# Verbose mode — prints the Horizon stream URL and debug info
stellar-payout status --batch-id batch_1735000000000_abc123 --follow --verboseOutput — batch summary table
When a batch ID is provided, the command prints a property/value table:
┌─────────────────┬────────────────────────────────────────────┐
│ Property │ Value │
├─────────────────┼────────────────────────────────────────────┤
│ Batch ID │ batch_1735000000000_abc123 │
│ Status │ completed │
│ Network │ testnet │
│ Source Account │ GABC...XYZ │
│ Total Payments │ 50 │
│ Processed │ 50 │
│ Successful │ 48 │
│ Failed │ 2 │
│ Skipped │ 0 │
│ Started At │ 2024-01-15T09:00:00.000Z │
│ Completed At │ 2024-01-15T09:04:37.000Z │
│ Dry Run │ No │
└─────────────────┴────────────────────────────────────────────┘
It then shows a per-status breakdown of payment entries, full details of any failed entries (up to 20), and a transaction-group table.
Output — recent batches list (no batch ID)
┌──────────────────────────────┬───────────┬───────┬─────────┬────────┬─────────┬──────────────────────────┐
│ Batch ID │ Status │ Total │ Success │ Failed │ Network │ Started │
├──────────────────────────────┼───────────┼───────┼─────────┼────────┼─────────┼──────────────────────────┤
│ batch_1735000000000_abc123 │ completed │ 50 │ 48 │ 2 │ testnet │ 2024-01-15T09:00:00.000Z │
│ batch_1734900000000_def456 │ failed │ 10 │ 0 │ 10 │ testnet │ 2024-01-14T14:22:00.000Z │
└──────────────────────────────┴───────────┴───────┴─────────┴────────┴─────────┴──────────────────────────┘
Interpreting batch and entry statuses
Batch statuses
| Status | Meaning |
|---|---|
created |
Batch initialised but not yet started. |
running |
Actively submitting transactions. |
paused |
Gracefully paused by SIGINT — safe to resume with retry. |
completed |
All entries reached a terminal state (confirmed, failed, or skipped). |
failed |
Batch encountered a fatal error and stopped. |
cancelled |
Manually cancelled. |
Entry statuses
| Status | Meaning |
|---|---|
pending |
Not yet picked up for submission. |
validating |
Destination account and trustline checks in progress. |
submitted |
Transaction sent to Horizon, awaiting ledger confirmation. |
confirmed |
Transaction included in a ledger — payment delivered. |
failed |
Submission or confirmation failed. See the Error column for details. Use stellar-payout retry to resubmit. |
retrying |
Queued for automatic retry with exponential backoff. |
skipped |
Entry excluded (e.g. destination has no trustline and validation failed). |
--follow streaming behaviour
When --follow is passed and the batch status is running, the command:
- Polls the local SQLite database every 2 seconds and prints a progress bar whenever the processed count changes.
- Simultaneously polls the Horizon
/accounts/{sourceAccount}/transactionsendpoint every 5 seconds, printing each new transaction hash and its operation count as it lands on-chain. - Exits automatically when the batch status transitions to
completed,failed, orcancelled. - Stops cleanly on Ctrl+C (SIGINT).
If the batch is already in a terminal status when --follow is supplied, the snapshot is printed and the command exits immediately — no streaming occurs.
Database path:
--followreads from the same SQLite file thebatchcommand writes to. If the batch is running on another machine or in a container, mount or copy the database file to a path accessible locally, then pass it via--db-path.
Horizon URL: The streaming poller uses the
--horizon-urlvalue (orHORIZON_URLenv var). For mainnet batches, ensure this is set tohttps://horizon.stellar.orgor your own Horizon instance, otherwise the transaction tail will be empty.
# Retry with exponential backoff
stellar-payout retry --batch-id <batch_id> --source-secret $SECRET_KEY
# Custom retry parameters
stellar-payout retry --batch-id <batch_id> --source-secret $SECRET_KEY \
--max-retries 5 \
--backoff-base 2000 \
--backoff-max 60000# Generate CSV report
stellar-payout report --batch-id <batch_id> --format csv
# Generate PDF report
stellar-payout report --batch-id <batch_id> --format pdf
# Custom output path
stellar-payout report --batch-id <batch_id> --format pdf --output audit-report.pdfEvery command resolves its option defaults the same way: an explicit CLI flag
wins, otherwise the value is read from the environment (including a .env file
loaded at startup), otherwise a built-in default is used.
| CLI flag | Environment variable | Default |
|---|---|---|
--horizon-url |
HORIZON_URL |
https://horizon-testnet.stellar.org |
--network |
STELLAR_NETWORK |
testnet |
--network-passphrase |
NETWORK_PASSPHRASE |
Test SDF Network ; September 2015 |
--max-fee |
MAX_FEE |
10000 |
--db-path |
DB_PATH |
./stellar-payout.db |
--source-secret |
ADMIN_SECRET_KEY |
(required) |
--escrow-contract |
ESCROW_CONTRACT_ADDRESS |
(required for batch) |
--rate-oracle-contract |
RATE_ORACLE_CONTRACT_ADDRESS |
(required for batch) |
--compliance-contract |
COMPLIANCE_CONTRACT_ADDRESS |
(required for batch) |
destination,amount,asset,memo,escrow_duration
GBDEVU63Y6...,1500.00,USDC,payroll-001,86400
GCFONE23AB...,1200.00,EURC,payroll-002,86400- Transaction Batching: Groups up to 100 payments per ledger transaction (Stellar's 100 op limit)
- Fee Optimization: Uses FEE_BUMP transactions for sender abstraction
- Parallel Submission: Concurrent channels for independent destination corridors
- Smart Queuing: Pauses if network congestion (fee surge pricing >100 stroops)
- Crash Recovery: SQLite-backed state persistence for interrupted batches
- Emergency Stop: SIGINT handling with graceful pause and state preservation
- Address Validation: Checks destination exists + trustline before submission
- Dry-Run Mode: Simulate all transactions without submission
- Multi-Format Input: CSV, JSON, Excel (.xlsx), SWIFT MT103
- Compliance Reports: PDF and CSV audit trail generation
The CLI persists all batch state to a local SQLite database (default: ./stellar-payout.db).
Atomicity guarantees ensure the database is never left in a half-written state:
| Operation | Atomicity guarantee |
|---|---|
| Batch initialisation | initBatch() creates the row and sets status running in one transaction — no stuck created batches |
| Entry seeding | All payment-entry rows inserted in one transaction — either all exist or none do |
| Group confirmation | Group row + every entry row set to confirmed together — a crash after Horizon confirms but before the write completes leaves entries in submitted, not a false confirmed |
| Group failure | Group row + every entry row set to failed together |
| Graceful pause | SIGINT/SIGTERM atomically sets status paused and refreshes all counters |
A batch in running or paused status at startup indicates an interrupted run:
const stale = db.getBatchesNeedingResume();
// Returns both 'paused' (graceful SIGINT) and 'running' (hard crash) batchesTo resume:
db.resumeBatch(batchId); // status → running, counters refreshed
const groups = db.getIncompleteGroups(batchId); // groups not yet confirmed
for (const group of groups) {
const pending = db.getPendingEntriesByGroup(batchId, group.groupIndex);
// re-submit pending / submitted entries only — confirmed entries are skipped
}From the CLI, resume failed entries with:
stellar-payout retry --batch-id <id> --source-secret $SECRET_KEYThe retry command re-submits every entry whose status is failed.
For entries stuck in submitted (submitted to Horizon but not yet confirmed),
re-running the batch command with the same --db-path will detect the incomplete
groups via getIncompleteGroups and re-check Horizon for their status.
The stellar-payout status command is your primary window into any batch — whether it finished hours ago or is actively running right now.
Running status without a batch ID shows the 10 most recent batches:
stellar-payout statusThis is the quickest way to find a batch ID if you did not capture it from the batch command's output.
stellar-payout status --batch-id batch_1735000000000_abc123Prints a full summary: overall counters, a per-status breakdown of payment entries, details for any failed entries, and the transaction-group table.
stellar-payout status --batch-id batch_1735000000000_abc123 --followAttaches to a running batch and streams two sources of updates simultaneously:
- Local DB poll (every 2 s) — progress bar showing confirmed / failed / skipped counts.
- Horizon transaction tail (every 5 s) — prints each new transaction hash and operation count as it lands on-chain.
The command exits automatically when the batch completes. Press Ctrl+C to detach early without affecting the running batch.
The batch command writes state to ./stellar-payout.db by default. If you passed a custom --db-path when running the batch, pass the same path to status:
# Batch was started with a custom DB path
stellar-payout batch --input payments.csv --source-secret $SECRET_KEY \
--db-path /data/payroll-2024-01.db
# Query that same batch
stellar-payout status --db-path /data/payroll-2024-01.db
# Monitor a specific batch in that file
stellar-payout status --batch-id batch_1735000000000_abc123 \
--db-path /data/payroll-2024-01.db --followThe --follow poller connects to Horizon to tail live transactions. Make sure the URL matches the network the batch was submitted to:
# Testnet (default)
stellar-payout status --batch-id <id> --follow
# equivalent to:
stellar-payout status --batch-id <id> --follow \
--horizon-url https://horizon-testnet.stellar.org
# Mainnet
stellar-payout status --batch-id <id> --follow \
--horizon-url https://horizon.stellar.org
# Self-hosted / custom Horizon
stellar-payout status --batch-id <id> --follow \
--horizon-url https://horizon.my-org.internalSet HORIZON_URL in your .env to avoid repeating the flag on every run.
Batch is completed — all entries reached a terminal state. Check Failed count; if > 0, run stellar-payout report for a full audit trail and stellar-payout retry to resubmit failures.
Batch is running — use --follow to watch it live or re-run status periodically.
Batch is paused — stopped by Ctrl+C during a previous run. Use stellar-payout retry to resume failed entries, or re-run stellar-payout batch with --db-path pointing to the same file to resume pending ones.
Batch is failed — a fatal error stopped the batch. Check the Error column in the failed entries table, fix the root cause, then retry with stellar-payout retry.
Entry is submitted but batch is completed — the transaction was sent to Horizon but confirmation was not recorded (e.g. a crash between Horizon confirm and DB write). Re-running stellar-payout batch with the same --db-path will re-check these entries against Horizon.
Entry is skipped — destination account did not exist on-chain or lacked the required trustline at validation time. Verify the destination address and asset issuer, then retry.
Copy .env.example to .env and fill in the values before running the SDK or CLI:
cp .env.example .envThe sections below explain every variable, which layer consumes it, and when you might prefer an explicit CLI flag instead.
| Variable | Required | Description |
|---|---|---|
ADMIN_SECRET_KEY |
Yes | Stellar secret key (S…) used to sign and submit transactions. Never commit this value. |
ADMIN_PUBLIC_KEY |
No | Corresponding public key (G…). Informational — the SDK derives it from ADMIN_SECRET_KEY at runtime. |
CLI flag alternative: Pass
--source-secreton anystellar-payoutcommand to overrideADMIN_SECRET_KEYfor a single run. Prefer the flag in CI pipelines where secrets are injected per-job.
These three variables identify the deployed Soroban contracts. Both the TypeScript SDK and the CLI require them at runtime — they are not known at compile time.
| Variable | Required | Description |
|---|---|---|
ESCROW_CONTRACT_ADDRESS |
Yes | Address of the deployed escrow contract (C…). Controls time-locked payment creation, release, and refund. |
RATE_ORACLE_CONTRACT_ADDRESS |
Yes | Address of the deployed rate-oracle contract. Used when fetching on-chain exchange rates. |
COMPLIANCE_CONTRACT_ADDRESS |
Yes | Address of the deployed compliance contract. Called during KYC/AML checks before every payment. |
CLI flag alternative: Pass
--escrow-contract,--rate-oracle-contract, and--compliance-contractto override per-run. Useful when switching between testnet and mainnet deployments in the same shell session.
SDK usage: Supply these addresses in the
contractsobject passed tonew StellarCrossBorderSDK(config, contracts). The SDK does not read them from the environment automatically — your application code must pass them in.
| Variable | Default | Description |
|---|---|---|
HORIZON_URL |
https://horizon-testnet.stellar.org |
Horizon REST endpoint used by the SDK client and CLI for account lookups and transaction submission. |
SOROBAN_RPC_URL |
https://soroban-testnet.stellar.org |
Soroban RPC endpoint used by the SDK to simulate and invoke smart contracts. |
NETWORK_PASSPHRASE |
Test SDF Network ; September 2015 |
Network identifier included in every transaction envelope. Must match the network you deploy contracts to. |
STELLAR_NETWORK |
testnet |
Shorthand (testnet or mainnet) used by the CLI. Setting this to mainnet automatically switches HORIZON_URL and NETWORK_PASSPHRASE to production values unless they are also set explicitly. |
CLI flag alternative: Pass
--network testnetor--network mainnetand--horizon-url <url>to override per-run.
These variables set default behaviour for the stellar-payout CLI and can be overridden by the corresponding flag on any command.
| Variable | CLI flag | Default | Description |
|---|---|---|---|
MAX_FEE |
--max-fee |
10000 |
Maximum fee (in stroops) the CLI will accept for a transaction. Commands abort if network fees exceed this. |
DB_PATH |
--db-path |
./stellar-payout.db |
Path to the SQLite database used for crash-recovery state. |
# --- Credentials ---
ADMIN_SECRET_KEY=SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ADMIN_PUBLIC_KEY=GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# --- Contract Addresses ---
ESCROW_CONTRACT_ADDRESS=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
RATE_ORACLE_CONTRACT_ADDRESS=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
COMPLIANCE_CONTRACT_ADDRESS=CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# --- Network ---
HORIZON_URL=https://horizon-testnet.stellar.org
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
NETWORK_PASSPHRASE=Test SDF Network ; September 2015
STELLAR_NETWORK=testnet
# --- CLI Defaults ---
MAX_FEE=10000
DB_PATH=./stellar-payout.dbFor a full list of optional variables (fee tuning, compliance thresholds, UI config, external services), see .env.example and docs/deployment.md.
| Scenario | Recommendation |
|---|---|
| Long-running service / always the same network | Set env vars in .env or your deployment environment. |
| CI/CD pipeline with secrets injected per-job | Use CLI flags (--source-secret, --escrow-contract, etc.) so secrets are never written to disk. |
| Switching between testnet and mainnet in one session | Use --network / --horizon-url flags; leave .env pointing at testnet as a safe default. |
| SDK consumer (TypeScript application) | Read contract addresses from your own config layer and pass them to new StellarCrossBorderSDK(config, contracts). The SDK does not auto-load .env. |
interface PaymentRequest {
from: string;
to: string;
amount: string;
token: string;
release_time?: number;
metadata?: Record<string, Uint8Array>;
}interface PaymentOptions {
feeBump?: boolean;
timeout?: number;
memo?: string;
submit?: boolean;
}interface EscrowCreationResult extends TransactionResult {
escrowId: string;
}interface ExchangeRateResult {
rate: string;
timestamp: number;
sources: ExchangeRate[];
aggregated: AggregatedRate;
}interface ComplianceCheckResult extends TransactionResult {
approved: boolean;
reason: string;
rulesTriggered: string[];
}cargo test
# or via root script
npm run test:contractsnpm run test:sdk
# or directly
cd sdk && npm testnpm run test:cli
# or directly
cd cli && npm testnpm testAll payment requests should be validated before submission:
import { validatePaymentRequest } from './src/types';
const request = validatePaymentRequest({
sender: 'GABC...',
receiver: 'GDEF...',
amount: '100.50',
asset: 'USDC',
});Branded types (StellarAddress, AmountString) prevent raw strings from
being passed where validated values are expected.
Metadata must be flat key-value with primitive values only.
See docs/deployment.md for the complete guide, including:
- Compiling the Soroban contracts to WASM
- Deploying to testnet and mainnet with Stellar CLI
- Initialising contract state (admin, restricted jurisdictions, rate sources, compliance records)
- Wiring contract addresses into the SDK and CLI
- Example testnet addresses for quick testing
- Troubleshooting common errors
Quick-start (testnet):
# 1. Compile contracts
npm run build:contracts
# 2. Generate and fund an admin keypair
stellar keys generate admin --network testnet
stellar keys fund admin --network testnet
# 3. Deploy all three contracts
WASM=target/wasm32-unknown-unknown/release/stellar_cross_border_payments_sdk.wasm
ESCROW=$(stellar contract deploy --wasm "$WASM" --source admin --network testnet)
ORACLE=$(stellar contract deploy --wasm "$WASM" --source admin --network testnet)
COMPLIANCE=$(stellar contract deploy --wasm "$WASM" --source admin --network testnet)
# 4. Write addresses to .env
echo "ESCROW_CONTRACT_ADDRESS=$ESCROW" >> .env
echo "RATE_ORACLE_CONTRACT_ADDRESS=$ORACLE" >> .env
echo "COMPLIANCE_CONTRACT_ADDRESS=$COMPLIANCE" >> .env
# 5. Build TypeScript packages
npm run build
# 6. Run a test batch payment
stellar-payout batch --input examples/payroll-batch.csv \
--source-secret "$ADMIN_SECRET_KEY" --network testnet --dry-runnpm run dev:sdk # tsc --watch in sdk/
npm run dev:cli # tsc --watch in cli/
npm run dev:ui # next dev in ui/- Rust:
cargo fmtandcargo clippy - TypeScript: ESLint (
npm run lint) and Prettier - React: Follow React best practices
The repo uses npm workspaces. All packages can be built from the root.
# Full build: Rust contracts → SDK → CLI → UI
npm run build:full
# Or build each layer separately
npm run build:contracts # cargo build --release --target wasm32-unknown-unknown
npm run build:sdk # tsc in sdk/
npm run build:cli # tsc in cli/
npm run build:ui # next build in ui/# Linux / macOS / CI
./scripts/build.sh
# Windows PowerShell
.\scripts\build.ps1
# Type-check only (no emit)
./scripts/build.sh --type-check
.\scripts\build.ps1 -TypeCheck
# Skip contracts, only build TypeScript
./scripts/build.sh --ts-onlyThe three TypeScript packages must be built in this order:
sdk/— compiled first because the CLI workspace resolves the SDK fromsdk/dist.cli/— depends onsdk/distbeing present.ui/— Next.js build, independent of cli and sdk dist (uses workspace package resolution).
The Rust contracts must be compiled before deploying, but the TypeScript packages do not depend on the WASM artefacts at compile time. Contract addresses are supplied at runtime via environment variables.
npm run type-check # all packages
npm run type-check:sdk
npm run type-check:cli
npm run type-check:uiWe welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-change) - Make your changes and add tests
- Run the full build and test suite:
npm run build:full && npm test && cargo test - Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
- Documentation: https://docs.stellar-cross-border.com
- Issues: GitHub Issues
- Discord: Stellar Discord
- Twitter: @StellarOrg
- Stellar Development Foundation for the amazing platform
- Soroban for smart contract support
- The Stellar community for feedback and contributions
- Multi-signature support
- Advanced dispute resolution
- Mobile SDK
- More fiat currency pairs
- DeFi integration
- Advanced analytics
- Compliance automation
- Enterprise features
- Production audit
- SLA guarantees
- 24/7 support
- Global compliance framework
Built with ❤️ for the Stellar ecosystem