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
107 changes: 107 additions & 0 deletions apps/dashboard/analytics/stellar/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React, { useState } from 'react';

interface ProviderMetric {
provider: string;
totalTransfers: number;
successRate: number;
avgLatencyMs: number;
totalVolume: string;
asset: string;
}

const MOCK_METRICS: ProviderMetric[] = [
{ provider: 'AllBridge', totalTransfers: 1240, successRate: 0.98, avgLatencyMs: 320, totalVolume: '1,200,000', asset: 'USDC' },
{ provider: 'Squid', totalTransfers: 870, successRate: 0.96, avgLatencyMs: 410, totalVolume: '850,000', asset: 'USDC' },
{ provider: 'Stargate', totalTransfers: 530, successRate: 0.99, avgLatencyMs: 280, totalVolume: '620,000', asset: 'USDC' },
];

function MetricCard({ label, value }: { label: string; value: string | number }) {
return (
<div style={{ padding: '12px 16px', border: '1px solid #e2e8f0', borderRadius: '8px', background: '#f8fafc', minWidth: '120px' }}>
<div style={{ fontSize: '12px', color: '#64748b', marginBottom: '4px' }}>{label}</div>
<div style={{ fontSize: '20px', fontWeight: 700, color: '#1e293b' }}>{value}</div>
</div>
);
}

function ProviderRow({ metric }: { metric: ProviderMetric }) {
const successPct = (metric.successRate * 100).toFixed(1);
const barWidth = `${metric.successRate * 100}%`;
return (
<tr style={{ borderBottom: '1px solid #e2e8f0' }}>
<td style={{ padding: '10px 12px', fontWeight: 600 }}>{metric.provider}</td>
<td style={{ padding: '10px 12px', textAlign: 'right' }}>{metric.totalTransfers.toLocaleString()}</td>
<td style={{ padding: '10px 12px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ flex: 1, background: '#e2e8f0', borderRadius: '4px', height: '8px' }}>
<div style={{ width: barWidth, background: '#22c55e', borderRadius: '4px', height: '8px' }} />
</div>
<span style={{ fontSize: '13px', minWidth: '40px' }}>{successPct}%</span>
</div>
</td>
<td style={{ padding: '10px 12px', textAlign: 'right' }}>{metric.avgLatencyMs} ms</td>
<td style={{ padding: '10px 12px', textAlign: 'right' }}>${metric.totalVolume} {metric.asset}</td>
</tr>
);
}

export default function StellarAnalyticsDashboard() {
const [metrics] = useState<ProviderMetric[]>(MOCK_METRICS);

const totalTransfers = metrics.reduce((s, m) => s + m.totalTransfers, 0);
const avgSuccess = metrics.reduce((s, m) => s + m.successRate, 0) / metrics.length;
const bestLatency = Math.min(...metrics.map((m) => m.avgLatencyMs));

return (
<div style={{ padding: '24px', fontFamily: 'system-ui, sans-serif', maxWidth: '900px', margin: '0 auto' }}>
<h1 style={{ fontSize: '24px', fontWeight: 700, color: '#0f172a', marginBottom: '4px' }}>
Stellar Cross-Bridge Analytics
</h1>
<p style={{ color: '#64748b', marginBottom: '24px' }}>
Aggregated provider metrics across Stellar bridge integrations
</p>

{/* Summary cards */}
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', marginBottom: '32px' }}>
<MetricCard label="Total Transfers" value={totalTransfers.toLocaleString()} />
<MetricCard label="Avg Success Rate" value={`${(avgSuccess * 100).toFixed(1)}%`} />
<MetricCard label="Best Latency" value={`${bestLatency} ms`} />
<MetricCard label="Active Providers" value={metrics.length} />
</div>

{/* Provider comparison table */}
<div style={{ border: '1px solid #e2e8f0', borderRadius: '8px', overflow: 'hidden' }}>
<div style={{ padding: '14px 16px', background: '#f1f5f9', borderBottom: '1px solid #e2e8f0' }}>
<h2 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: '#1e293b' }}>Provider Metrics</h2>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ background: '#f8fafc' }}>
<th style={{ padding: '10px 12px', textAlign: 'left', fontSize: '13px', color: '#64748b', fontWeight: 600 }}>Provider</th>
<th style={{ padding: '10px 12px', textAlign: 'right', fontSize: '13px', color: '#64748b', fontWeight: 600 }}>Transfers</th>
<th style={{ padding: '10px 12px', textAlign: 'left', fontSize: '13px', color: '#64748b', fontWeight: 600 }}>Success Rate</th>
<th style={{ padding: '10px 12px', textAlign: 'right', fontSize: '13px', color: '#64748b', fontWeight: 600 }}>Avg Latency</th>
<th style={{ padding: '10px 12px', textAlign: 'right', fontSize: '13px', color: '#64748b', fontWeight: 600 }}>Volume</th>
</tr>
</thead>
<tbody>
{metrics.map((m) => (
<ProviderRow key={m.provider} metric={m} />
))}
</tbody>
</table>
</div>

{/* Visualization placeholder */}
<div style={{ marginTop: '24px', padding: '20px', border: '1px solid #e2e8f0', borderRadius: '8px', textAlign: 'center', color: '#94a3b8' }}>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ marginBottom: '8px' }}>
<rect x="4" y="28" width="8" height="16" rx="2" fill="#94a3b8" />
<rect x="16" y="18" width="8" height="26" rx="2" fill="#64748b" />
<rect x="28" y="10" width="8" height="34" rx="2" fill="#475569" />
<rect x="40" y="22" width="8" height="22" rx="2" fill="#94a3b8" />
</svg>
<p style={{ margin: 0, fontSize: '14px' }}>Chart visualization — connect to live data source</p>
</div>
</div>
);
}
1 change: 1 addition & 0 deletions src/compression/stellar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './soroban-transaction-compressor';
95 changes: 95 additions & 0 deletions src/compression/stellar/soroban-transaction-compressor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { SorobanTransactionCompressor, SorobanTransactionPayload } from './soroban-transaction-compressor';

const mockPayload: SorobanTransactionPayload = {
txHash: 'abc123',
contractId: 'C_BRIDGE_CONTRACT_XYZ',
method: 'transfer',
args: {
from: 'G_SENDER_ACCOUNT',
to: 'G_RECIPIENT_ACCOUNT',
amount: '100.0000000',
asset: 'USDC',
},
metadata: {
bridgeProvider: 'AllBridge',
sourceChain: 'Stellar',
destinationChain: 'Ethereum',
},
};

describe('SorobanTransactionCompressor', () => {
let compressor: SorobanTransactionCompressor;

beforeEach(() => {
compressor = new SorobanTransactionCompressor();
});

describe('compress', () => {
it('should compress a payload and return base64 data', async () => {
const result = await compressor.compress(mockPayload);
expect(result.data).toBeTruthy();
expect(result.algorithm).toBe('deflate');
expect(result.originalSize).toBeGreaterThan(0);
expect(result.compressedSize).toBeGreaterThan(0);
});

it('should reduce payload size', async () => {
const result = await compressor.compress(mockPayload);
expect(result.compressedSize).toBeLessThan(result.originalSize);
});
});

describe('decompress', () => {
it('should restore original payload after compression', async () => {
const compressed = await compressor.compress(mockPayload);
const decompressed = await compressor.decompress(compressed);
expect(decompressed).toEqual(mockPayload);
});

it('should throw on invalid compressed data', async () => {
await expect(
compressor.decompress({ data: 'invalid_base64!!!', originalSize: 100, compressedSize: 50, algorithm: 'deflate' }),
).rejects.toThrow();
});
});

describe('validate', () => {
it('should return true when decompressed matches original', async () => {
const compressed = await compressor.compress(mockPayload);
const isValid = await compressor.validate(mockPayload, compressed);
expect(isValid).toBe(true);
});

it('should return false on corrupted data', async () => {
const isValid = await compressor.validate(mockPayload, {
data: 'corrupted',
originalSize: 100,
compressedSize: 50,
algorithm: 'deflate',
});
expect(isValid).toBe(false);
});
});

describe('getStats', () => {
it('should return compression statistics', async () => {
const stats = await compressor.getStats(mockPayload);
expect(stats.originalSize).toBeGreaterThan(0);
expect(stats.compressedSize).toBeGreaterThan(0);
expect(stats.compressionRatio).toBeGreaterThan(0);
expect(stats.compressionRatio).toBeLessThan(1);
expect(stats.savedBytes).toBeGreaterThan(0);
});
});

describe('compressBatch / decompressBatch', () => {
it('should compress and decompress a batch of payloads', async () => {
const payloads = [mockPayload, { ...mockPayload, txHash: 'def456', method: 'swap' }];
const compressed = await compressor.compressBatch(payloads);
expect(compressed).toHaveLength(2);

const decompressed = await compressor.decompressBatch(compressed);
expect(decompressed).toEqual(payloads);
});
});
});
109 changes: 109 additions & 0 deletions src/compression/stellar/soroban-transaction-compressor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Injectable, Logger } from '@nestjs/common';
import { deflate, inflate } from 'zlib';
import { promisify } from 'util';

const deflateAsync = promisify(deflate);
const inflateAsync = promisify(inflate);

export interface SorobanTransactionPayload {
txHash?: string;
contractId: string;
method: string;
args: Record<string, unknown>;
metadata?: Record<string, unknown>;
}

export interface CompressedPayload {
data: string; // base64-encoded compressed bytes
originalSize: number;
compressedSize: number;
algorithm: 'deflate';
}

export interface CompressionStats {
originalSize: number;
compressedSize: number;
compressionRatio: number;
savedBytes: number;
}

@Injectable()
export class SorobanTransactionCompressor {
private readonly logger = new Logger(SorobanTransactionCompressor.name);

/**
* Compress a Soroban transaction payload
*/
async compress(payload: SorobanTransactionPayload): Promise<CompressedPayload> {
const json = JSON.stringify(payload);
const originalSize = Buffer.byteLength(json, 'utf8');

const compressed = await deflateAsync(Buffer.from(json, 'utf8'));
const compressedSize = compressed.length;

this.logger.debug(
`Compressed payload: ${originalSize} -> ${compressedSize} bytes (${Math.round((1 - compressedSize / originalSize) * 100)}% reduction)`,
);

return {
data: compressed.toString('base64'),
originalSize,
compressedSize,
algorithm: 'deflate',
};
}

/**
* Decompress a previously compressed payload
*/
async decompress(compressed: CompressedPayload): Promise<SorobanTransactionPayload> {
const buffer = Buffer.from(compressed.data, 'base64');
const decompressed = await inflateAsync(buffer);
const json = decompressed.toString('utf8');

const payload = JSON.parse(json) as SorobanTransactionPayload;
this.logger.debug(`Decompressed payload: ${compressed.compressedSize} -> ${decompressed.length} bytes`);

return payload;
}

/**
* Validate that a decompressed payload matches the original
*/
async validate(original: SorobanTransactionPayload, compressed: CompressedPayload): Promise<boolean> {
try {
const decompressed = await this.decompress(compressed);
return JSON.stringify(decompressed) === JSON.stringify(original);
} catch {
return false;
}
}

/**
* Get compression statistics for a payload
*/
async getStats(payload: SorobanTransactionPayload): Promise<CompressionStats> {
const compressed = await this.compress(payload);
const { originalSize, compressedSize } = compressed;
return {
originalSize,
compressedSize,
compressionRatio: compressedSize / originalSize,
savedBytes: originalSize - compressedSize,
};
}

/**
* Compress a batch of payloads
*/
async compressBatch(payloads: SorobanTransactionPayload[]): Promise<CompressedPayload[]> {
return Promise.all(payloads.map((p) => this.compress(p)));
}

/**
* Decompress a batch of compressed payloads
*/
async decompressBatch(payloads: CompressedPayload[]): Promise<SorobanTransactionPayload[]> {
return Promise.all(payloads.map((p) => this.decompress(p)));
}
}
1 change: 1 addition & 0 deletions src/detection/rollback/stellar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './soroban-rollback-detector';
Loading
Loading