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
256 changes: 217 additions & 39 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dependencies": {
"@hookform/resolvers": "^5.9.1",
"@stellar/freighter-api": "^6.0.1",
"@stellar/stellar-sdk": "^17.2.1",
"html2canvas": "^1.4.1",
"lucide-react": "^1.7.0",
"qrcode": "^1.5.1",
Expand Down
50 changes: 47 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,25 @@ import { GovernanceProposalCreatePage } from "./pages/GovernanceProposalCreatePa
import { AdminAlertsInbox } from "./pages/AdminAlertsInbox";
import { PayoutSchedule } from "./pages/PayoutSchedule";
import { GovernanceVoteReceiptDemo } from "./pages/GovernanceVoteReceiptDemo";
import { useContractRead } from "./hooks/useContractRead";
import { getContractSummary } from "./lib/soroban/revoraContract";
import {
CONTRACT_ID,
buildContractExplorerUrl,
buildStellarLabUrl,
} from "./constants/stellar";

const LIVE_URL = "https://revora-frontend-omega.vercel.app";
const REPO_URL = "https://github.com/RevoraOrg/Revora-Frontend";
const CONTRACT_ID = "CABIJLQV4TKBHMETNIISV4OT76EDR5WXCZVJJLXHA6AEIII2JVYAHP2I";
const STELLAR_LAB_URL = `https://lab.stellar.org/r/testnet/contract/${CONTRACT_ID}`;
const STELLAR_EXPERT_URL = `https://stellar.expert/explorer/testnet/contract/${CONTRACT_ID}`;
const STELLAR_LAB_URL = buildStellarLabUrl();
const STELLAR_EXPERT_URL = buildContractExplorerUrl();

function SiteFooter() {
const { data: contractSummary, isLoading, isError, error, retry } = useContractRead(
getContractSummary,
{ retryCount: 2, retryDelay: 1000 },
);

return (
<footer className="site-footer">
<div className="site-footer__row">
Expand All @@ -37,6 +48,39 @@ function SiteFooter() {
</span>
</div>
<code className="site-footer__contract">{CONTRACT_ID}</code>

{/* On-chain status panel */}
<div className="site-footer__status">
{isLoading && (
<div className="site-footer__status-loading">
<span className="text-muted">Loading contract data...</span>
</div>
)}
{isError && (
<div className="site-footer__status-error">
<span className="text-muted" style={{ color: '#ef4444' }}>
Failed to load contract data: {error}
</span>
<button
type="button"
onClick={retry}
className="link-styled"
style={{ background: 'none', border: 'none', padding: 0, marginLeft: '0.5rem', cursor: 'pointer' }}
>
Retry
</button>
</div>
)}
{contractSummary && !isLoading && (
<div className="site-footer__status-success">
<span className="text-muted">
{contractSummary.periodCount} period{contractSummary.periodCount !== 1 ? 's' : ''} registered,
{contractSummary.authorizedOfferingsCount} authorized offering{contractSummary.authorizedOfferingsCount !== 1 ? 's' : ''}
</span>
</div>
)}
</div>

<div className="site-footer__row">
<a href={STELLAR_EXPERT_URL} className="link-styled" target="_blank" rel="noopener noreferrer">Stellar Expert</a>
<a href={STELLAR_LAB_URL} className="link-styled" target="_blank" rel="noopener noreferrer">Stellar Lab</a>
Expand Down
66 changes: 66 additions & 0 deletions src/constants/stellar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Unit tests for Stellar network configuration
*/

import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
DEFAULT_NETWORK,
DEFAULT_NETWORK_PASSPHRASE,
DEFAULT_RPC_URL,
DEFAULT_CONTRACT_ID,
DEFAULT_EXPLORER_BASE,
DEFAULT_STELLAR_LAB_BASE,
NETWORK,
NETWORK_PASSPHRASE,
RPC_URL,
CONTRACT_ID,
EXPLORER_BASE,
STELLAR_LAB_BASE,
buildContractExplorerUrl,
buildStellarLabUrl,
buildTransactionExplorerUrl,
} from './stellar';

describe('stellar constants', () => {
beforeEach(() => {
// Reset import.meta.env before each test
vi.stubGlobal('import.meta.env', {});
});

it('has correct default values', () => {
expect(DEFAULT_NETWORK).toBe('TESTNET');
expect(DEFAULT_NETWORK_PASSPHRASE).toBeDefined();
expect(DEFAULT_RPC_URL).toBe('https://soroban-testnet.stellar.org');
expect(DEFAULT_CONTRACT_ID).toBe('CABIJLQV4TKBHMETNIISV4OT76EDR5WXCZVJJLXHA6AEIII2JVYAHP2I');
expect(DEFAULT_EXPLORER_BASE).toBe('https://stellar.expert/explorer/testnet');
expect(DEFAULT_STELLAR_LAB_BASE).toBe('https://lab.stellar.org/r/testnet');
});

it('uses defaults when no env vars are set', () => {
expect(NETWORK).toBe(DEFAULT_NETWORK);
expect(NETWORK_PASSPHRASE).toBe(DEFAULT_NETWORK_PASSPHRASE);
expect(RPC_URL).toBe(DEFAULT_RPC_URL);
expect(CONTRACT_ID).toBe(DEFAULT_CONTRACT_ID);
expect(EXPLORER_BASE).toBe(DEFAULT_EXPLORER_BASE);
expect(STELLAR_LAB_BASE).toBe(DEFAULT_STELLAR_LAB_BASE);
});

it('builds contract explorer URLs', () => {
const url = buildContractExplorerUrl();
expect(url).toBe(`${DEFAULT_EXPLORER_BASE}/contract/${DEFAULT_CONTRACT_ID}`);

const customUrl = buildContractExplorerUrl('CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
expect(customUrl).toBe(`${DEFAULT_EXPLORER_BASE}/contract/CXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`);
});

it('builds Stellar Lab URLs', () => {
const url = buildStellarLabUrl();
expect(url).toBe(`${DEFAULT_STELLAR_LAB_BASE}/contract/${DEFAULT_CONTRACT_ID}`);
});

it('builds transaction explorer URLs', () => {
const txHash = 'abc123';
const url = buildTransactionExplorerUrl(txHash);
expect(url).toBe(`${DEFAULT_EXPLORER_BASE}/tx/${txHash}`);
});
});
124 changes: 124 additions & 0 deletions src/constants/stellar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Stellar Network Configuration — Soroban RPC & Contract Constants
*
* Defines network endpoints, contract IDs, and explorer URLs for the
* Revora revenue-deposit contract on Stellar testnet.
*/

import { Networks } from '@stellar/stellar-sdk';

export type NetworkType = 'TESTNET' | 'PUBLIC';

export const DEFAULT_NETWORK: NetworkType = 'TESTNET';
export const DEFAULT_NETWORK_PASSPHRASE = Networks.TESTNET;
export const DEFAULT_RPC_URL = 'https://soroban-testnet.stellar.org';
export const DEFAULT_CONTRACT_ID = 'CABIJLQV4TKBHMETNIISV4OT76EDR5WXCZVJJLXHA6AEIII2JVYAHP2I';
export const DEFAULT_EXPLORER_BASE = 'https://stellar.expert/explorer/testnet';
export const DEFAULT_STELLAR_LAB_BASE = 'https://lab.stellar.org/r/testnet';

/**
* Validates that a string is a valid Stellar contract ID (starts with 'C' and is 56 chars)
*/
function isValidContractId(id: string): boolean {
return typeof id === 'string' && id.length === 56 && id.startsWith('C');
}

/**
* Validates that a URL string is a valid HTTPS URL
*/
function isValidHttpsUrl(url: string): boolean {
try {
const parsed = new URL(url);
return parsed.protocol === 'https:';
} catch {
return false;
}
}

/**
* Validates network type
*/
function isValidNetwork(network: string): network is NetworkType {
return network === 'TESTNET' || network === 'PUBLIC';
}

/**
* Reads an environment variable with validation and fallback to default.
* Logs an error to console and falls back to default if validation fails.
*/
function getEnvVarWithFallback<T>(
envVarName: string,
defaultValue: T,
validator: (value: string) => boolean,
): T {
const envValue = import.meta.env[envVarName];

if (envValue === undefined || envValue === '') {
return defaultValue;
}

if (!validator(envValue)) {
console.error(
`[Stellar Config] Invalid ${envVarName}: "${envValue}". Falling back to default:`,
defaultValue,
);
return defaultValue;
}

return envValue as T;
}

/**
* Network configuration with environment variable overrides.
* Override with VITE_STELLAR_NETWORK, VITE_STELLAR_RPC_URL, etc.
*/
export const NETWORK = getEnvVarWithFallback(
'VITE_STELLAR_NETWORK',
DEFAULT_NETWORK,
isValidNetwork,
);

export const NETWORK_PASSPHRASE = getEnvVarWithFallback(
'VITE_STELLAR_NETWORK_PASSPHRASE',
DEFAULT_NETWORK_PASSPHRASE,
() => true, // Network passphrase can be any string
);

export const RPC_URL = getEnvVarWithFallback(
'VITE_STELLAR_RPC_URL',
DEFAULT_RPC_URL,
isValidHttpsUrl,
);

export const CONTRACT_ID = getEnvVarWithFallback(
'VITE_STELLAR_CONTRACT_ID',
DEFAULT_CONTRACT_ID,
isValidContractId,
);

export const EXPLORER_BASE = getEnvVarWithFallback(
'VITE_STELLAR_EXPLORER_BASE',
DEFAULT_EXPLORER_BASE,
isValidHttpsUrl,
);

export const STELLAR_LAB_BASE = getEnvVarWithFallback(
'VITE_STELLAR_LAB_BASE',
DEFAULT_STELLAR_LAB_BASE,
isValidHttpsUrl,
);

/**
* Helper to build explorer URLs for contracts and transactions
*/
export function buildContractExplorerUrl(contractId: string = CONTRACT_ID): string {
return `${EXPLORER_BASE}/contract/${contractId}`;
}

export function buildStellarLabUrl(contractId: string = CONTRACT_ID): string {
return `${STELLAR_LAB_BASE}/contract/${contractId}`;
}

export function buildTransactionExplorerUrl(txHash: string): string {
return `${EXPLORER_BASE}/tx/${txHash}`;
}
Loading
Loading