Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .codex
Empty file.
18 changes: 17 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
cache-dependency-path: backend/package-lock.json
- run: npm ci
working-directory: backend
- run: npm run lint || echo "Linter not configured yet"
- run: npm run lint
working-directory: backend

backend-test:
Expand Down Expand Up @@ -141,6 +141,21 @@ jobs:


# -- Frontend coverage --
frontend-lint:
name: Frontend lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
working-directory: frontend
- run: npm run lint
working-directory: frontend

frontend-coverage:
name: Frontend coverage (vitest >= 95%)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -181,6 +196,7 @@ jobs:
- backend-test
- api-lint
- api-test
- frontend-lint
- frontend-coverage
if: always()
steps:
Expand Down
2 changes: 2 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ DATABASE_URL=postgresql://user:password@localhost:5432/swiftremit
# Stellar Configuration
STELLAR_NETWORK=testnet
HORIZON_URL=https://horizon-testnet.stellar.org
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
NETWORK_PASSPHRASE=Test SDF Network ; September 2015
CONTRACT_ID=CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM
ADMIN_SECRET_KEY=SXXX...

Expand Down
58 changes: 58 additions & 0 deletions backend/src/__tests__/stellar-network.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';
import { Networks } from '@stellar/stellar-sdk';
import {
assertNetworkMatchesRpcEndpoint,
getNetworkPassphrase,
getSorobanRpcUrl,
getStellarRuntimeConfig,
} from '../stellar-network';

describe('stellar-network', () => {
it('derives the testnet passphrase from STELLAR_NETWORK', () => {
expect(getNetworkPassphrase({ STELLAR_NETWORK: 'testnet' } as NodeJS.ProcessEnv)).toBe(
Networks.TESTNET
);
});

it('derives the public passphrase from STELLAR_NETWORK', () => {
expect(getNetworkPassphrase({ STELLAR_NETWORK: 'mainnet' } as NodeJS.ProcessEnv)).toBe(
Networks.PUBLIC
);
});

it('prefers an explicit NETWORK_PASSPHRASE', () => {
expect(
getNetworkPassphrase({
STELLAR_NETWORK: 'testnet',
NETWORK_PASSPHRASE: Networks.PUBLIC,
} as NodeJS.ProcessEnv)
).toBe(Networks.PUBLIC);
});

it('falls back to SOROBAN_RPC_URL when present', () => {
expect(
getSorobanRpcUrl({
SOROBAN_RPC_URL: 'https://soroban.stellar.org',
HORIZON_URL: 'https://horizon-testnet.stellar.org',
} as NodeJS.ProcessEnv)
).toBe('https://soroban.stellar.org');
});

it('rejects a public passphrase against a testnet endpoint', () => {
expect(() =>
assertNetworkMatchesRpcEndpoint(Networks.PUBLIC, 'https://soroban-testnet.stellar.org')
).toThrow(/does not match/i);
});

it('returns validated runtime config', () => {
expect(
getStellarRuntimeConfig({
STELLAR_NETWORK: 'testnet',
SOROBAN_RPC_URL: 'https://soroban-testnet.stellar.org',
} as NodeJS.ProcessEnv)
).toEqual({
rpcUrl: 'https://soroban-testnet.stellar.org',
networkPassphrase: Networks.TESTNET,
});
});
});
59 changes: 59 additions & 0 deletions backend/src/stellar-network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Networks } from '@stellar/stellar-sdk';

const DEFAULT_SOROBAN_RPC_URL = 'https://soroban-testnet.stellar.org';

export function getSorobanRpcUrl(env: NodeJS.ProcessEnv = process.env): string {
return env.SOROBAN_RPC_URL || env.HORIZON_URL || DEFAULT_SOROBAN_RPC_URL;
}

export function getNetworkPassphrase(env: NodeJS.ProcessEnv = process.env): string {
if (env.NETWORK_PASSPHRASE) {
return env.NETWORK_PASSPHRASE;
}

switch ((env.STELLAR_NETWORK || 'testnet').toLowerCase()) {
case 'testnet':
return Networks.TESTNET;
case 'mainnet':
case 'public':
return Networks.PUBLIC;
default:
throw new Error(
`Unsupported STELLAR_NETWORK "${env.STELLAR_NETWORK}". Use "testnet", "mainnet", or set NETWORK_PASSPHRASE explicitly.`
);
}
}

export function assertNetworkMatchesRpcEndpoint(
networkPassphrase: string,
rpcUrl: string
): void {
const normalizedRpcUrl = rpcUrl.toLowerCase();
const pointsToTestnet = normalizedRpcUrl.includes('testnet');
const pointsToPublicNetwork =
normalizedRpcUrl.includes('mainnet') || normalizedRpcUrl.includes('public');

if (pointsToTestnet && networkPassphrase !== Networks.TESTNET) {
throw new Error(
`Configured network passphrase does not match Soroban RPC endpoint ${rpcUrl}.`
);
}

if (pointsToPublicNetwork && networkPassphrase !== Networks.PUBLIC) {
throw new Error(
`Configured network passphrase does not match Soroban RPC endpoint ${rpcUrl}.`
);
}
}

export function getStellarRuntimeConfig(env: NodeJS.ProcessEnv = process.env): {
rpcUrl: string;
networkPassphrase: string;
} {
const rpcUrl = getSorobanRpcUrl(env);
const networkPassphrase = getNetworkPassphrase(env);

assertNetworkMatchesRpcEndpoint(networkPassphrase, rpcUrl);

return { rpcUrl, networkPassphrase };
}
15 changes: 7 additions & 8 deletions backend/src/stellar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import {
Contract,
SorobanRpc,
TransactionBuilder,
Networks,
Address,
nativeToScVal,
xdr,
} from '@stellar/stellar-sdk';
import { AssetVerification, VerificationStatus } from './types';
import { getStellarRuntimeConfig } from './stellar-network';

const server = new SorobanRpc.Server(
process.env.HORIZON_URL || 'https://soroban-testnet.stellar.org'
);
const { rpcUrl, networkPassphrase } = getStellarRuntimeConfig();
const server = new SorobanRpc.Server(rpcUrl);

export async function storeVerificationOnChain(
verification: AssetVerification
Expand Down Expand Up @@ -49,7 +48,7 @@ export async function storeVerificationOnChain(
// Build transaction
const tx = new TransactionBuilder(account, {
fee: '1000',
networkPassphrase: Networks.TESTNET,
networkPassphrase,
})
.addOperation(
contract.call(
Expand Down Expand Up @@ -118,7 +117,7 @@ export async function simulateSettlement(

const tx = new TransactionBuilder(sourceAccount, {
fee: '100',
networkPassphrase: Networks.TESTNET,
networkPassphrase,
})
.addOperation(
contract.call(
Expand Down Expand Up @@ -176,7 +175,7 @@ export async function cancelRemittanceOnChain(remittanceId: number): Promise<voi

const tx = new TransactionBuilder(account, {
fee: '1000',
networkPassphrase: Networks.TESTNET,
networkPassphrase,
})
.addOperation(
contract.call(
Expand Down Expand Up @@ -236,7 +235,7 @@ export async function updateKycStatusOnChain(
// Build transaction
const tx = new TransactionBuilder(account, {
fee: '1000',
networkPassphrase: Networks.TESTNET,
networkPassphrase,
})
.addOperation(
contract.call(
Expand Down
4 changes: 4 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# Contract Configuration
VITE_CONTRACT_ID=

# Asset Issuers
VITE_USDC_ISSUER=
VITE_EURC_ISSUER=

# USDC Token (Testnet)
VITE_USDC_TOKEN_ID=
20 changes: 20 additions & 0 deletions frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
root: true,
ignorePatterns: ['dist', 'coverage'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
es2022: true,
node: true,
},
rules: {
'no-debugger': 'error',
},
};
6 changes: 6 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# Your deployed contract address (C...)
VITE_CONTRACT_ID=

# Asset issuer addresses
VITE_USDC_ISSUER=
VITE_EURC_ISSUER=

# Testnet USDC token contract address
VITE_USDC_TOKEN_ID=
```
Expand All @@ -50,6 +54,8 @@ VITE_USDC_TOKEN_ID=
| `VITE_HORIZON_URL` | Horizon API endpoint for Stellar operations | `https://horizon-testnet.stellar.org` |
| `VITE_SOROBAN_RPC_URL` | Soroban RPC endpoint for smart contract calls | `https://soroban-testnet.stellar.org` |
| `VITE_CONTRACT_ID` | Your deployed SwiftRemit contract address (starts with `C`) | `CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` |
| `VITE_USDC_ISSUER` | Issuer account for the USDC asset used by wallet payments | `G...` |
| `VITE_EURC_ISSUER` | Issuer account for the EURC asset used by wallet payments | `G...` |
| `VITE_USDC_TOKEN_ID` | Testnet USDC token contract address | `CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA` |

**Important Notes:**
Expand Down
Loading
Loading