diff --git a/index.html b/index.html
index 3076c14..a32c554 100644
--- a/index.html
+++ b/index.html
@@ -53,7 +53,10 @@
content="Send and receive private payments on Horizen and Stellar using stealth addresses."
/>
-
+
+
+
+
diff --git a/src/App.tsx b/src/App.tsx
index 65d0b57..3958d3c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,10 @@
import { Routes, Route, Navigate } from 'react-router-dom';
import { Header } from '@/components/Header';
import { AutoSign } from '@/components/AutoSign';
+import { TelemetryBanner } from '@/components/TelemetryBanner';
+import Send from '@/pages/Send';
+import Receive from '@/pages/Receive';
+import Privacy from '@/pages/Privacy';
import { HelpButton } from '@/components/HelpButton';
import Send from '@/pages/Send';
import Receive from '@/pages/Receive';
@@ -10,11 +14,13 @@ export function App() {
return (
+
} />
} />
+ } />
} />
} />
} />
@@ -23,4 +29,4 @@ export function App() {
);
-}
+}
\ No newline at end of file
diff --git a/src/components/CkbReceive.tsx b/src/components/CkbReceive.tsx
index 183571e..aef6954 100644
--- a/src/components/CkbReceive.tsx
+++ b/src/components/CkbReceive.tsx
@@ -11,6 +11,7 @@ import {
} from '@wraith-protocol/sdk/chains/ckb';
import { useStealthKeys } from '@/context/StealthKeysContext';
import { CopyButton } from '@/components/CopyButton';
+import { trackEvent } from '@/lib/telemetry';
function CkbStealthRow({ match }: { match: MatchedStealthCell }) {
const [showKey, setShowKey] = useState(false);
@@ -129,6 +130,7 @@ export function CkbReceive() {
);
setMatched(results);
setHasScanned(true);
+ trackEvent('scan_triggered');
} catch (err) {
setError(err instanceof Error ? err.message : 'Scan failed');
} finally {
diff --git a/src/components/CkbSend.tsx b/src/components/CkbSend.tsx
index ae5261c..da23000 100644
--- a/src/components/CkbSend.tsx
+++ b/src/components/CkbSend.tsx
@@ -6,6 +6,7 @@ import {
getDeployment,
} from '@wraith-protocol/sdk/chains/ckb';
import { CopyButton } from '@/components/CopyButton';
+import { trackEvent } from '@/lib/telemetry';
const STEALTH_LOCK_CODE_HASH = getDeployment('ckb').contracts.stealthLockCodeHash;
@@ -71,6 +72,7 @@ export function CkbSend() {
await tx.completeFeeBy(signer, 1000);
const hash = await signer.sendTransaction(tx);
+ trackEvent('send_submitted');
setTxHash(hash);
} catch (err) {
setError(err instanceof Error ? err.message : 'Transaction failed');
diff --git a/src/components/HorizenReceive.tsx b/src/components/HorizenReceive.tsx
index 3ac860a..ebb62de 100644
--- a/src/components/HorizenReceive.tsx
+++ b/src/components/HorizenReceive.tsx
@@ -21,6 +21,7 @@ import {
} from '@wraith-protocol/sdk/chains/evm';
import type { HexString, MatchedAnnouncement } from '@wraith-protocol/sdk/chains/evm';
import { useStealthKeys } from '@/context/StealthKeysContext';
+import { trackEvent } from '@/lib/telemetry';
import { CopyButton } from '@/components/CopyButton';
import { horizenTxUrl, horizenAddrUrl } from '@/lib/explorer';
import { horizenTestnet } from '@/config';
@@ -87,6 +88,7 @@ function StealthRow({
});
setWithdrawHash(hash);
+ trackEvent('withdraw');
onWithdrawn(hash);
} catch (err) {
setError(err instanceof Error ? err.message : 'Withdraw failed');
@@ -278,6 +280,7 @@ export function HorizenReceive() {
);
setMatched(results);
setHasScanned(true);
+ trackEvent('scan_triggered');
} catch (err) {
setError(err instanceof Error ? err.message : 'Scan failed');
} finally {
diff --git a/src/components/HorizenSend.tsx b/src/components/HorizenSend.tsx
index b05e81a..1732619 100644
--- a/src/components/HorizenSend.tsx
+++ b/src/components/HorizenSend.tsx
@@ -5,6 +5,7 @@ import type { HexString, BuildSendStealthResult } from '@wraith-protocol/sdk/cha
import { horizenTxUrl, horizenAddrUrl } from '@/lib/explorer';
import { horizenTestnet } from '@/config';
import { CopyButton } from '@/components/CopyButton';
+import { trackEvent } from '@/lib/telemetry';
export function HorizenSend() {
const { isConnected, address } = useAccount();
@@ -61,6 +62,7 @@ export function HorizenSend() {
});
setStealthResult(result);
+ trackEvent('send_submitted');
sendTransaction({
to: result.transaction.to as `0x${string}`,
diff --git a/src/components/SolanaReceive.tsx b/src/components/SolanaReceive.tsx
index 8856fa2..552daa4 100644
--- a/src/components/SolanaReceive.tsx
+++ b/src/components/SolanaReceive.tsx
@@ -12,6 +12,7 @@ import {
import type { MatchedAnnouncement } from '@wraith-protocol/sdk/chains/solana';
import { useStealthKeys } from '@/context/StealthKeysContext';
import { CopyButton } from '@/components/CopyButton';
+import { trackEvent } from '@/lib/telemetry';
import { solanaTxUrl, solanaAddrUrl } from '@/lib/explorer';
import { SOLANA_NETWORK } from '@/config';
@@ -92,6 +93,7 @@ function SolanaStealthRow({
await connection.confirmTransaction(txId, 'confirmed');
setWithdrawHash(txId);
+ trackEvent('withdraw');
onWithdrawn();
} catch (err) {
setError(err instanceof Error ? err.message : 'Withdraw failed');
@@ -245,6 +247,7 @@ export function SolanaReceive() {
);
setMatched(results);
setHasScanned(true);
+ trackEvent('scan_triggered');
} catch (err) {
setError(err instanceof Error ? err.message : 'Scan failed');
} finally {
diff --git a/src/components/SolanaSend.tsx b/src/components/SolanaSend.tsx
index 2fea1bd..f19d781 100644
--- a/src/components/SolanaSend.tsx
+++ b/src/components/SolanaSend.tsx
@@ -11,6 +11,7 @@ import { buildSendSol, bytesToHex } from '@wraith-protocol/sdk/chains/solana';
import { solanaTxUrl, solanaAddrUrl } from '@/lib/explorer';
import { SOLANA_NETWORK } from '@/config';
import { CopyButton } from '@/components/CopyButton';
+import { trackEvent } from '@/lib/telemetry';
export function SolanaSend() {
const { publicKey, connected, sendTransaction } = useWallet();
@@ -48,6 +49,7 @@ export function SolanaSend() {
amount: lamports,
senderPubkey: publicKey.toBase58(),
});
+ trackEvent('send_submitted');
setStealthResult({
stealthAddress: result.stealthAddress,
ephemeralPubKey: result.ephemeralPubKey,
diff --git a/src/components/StellarReceive.tsx b/src/components/StellarReceive.tsx
index 225aae4..7e39dce 100644
--- a/src/components/StellarReceive.tsx
+++ b/src/components/StellarReceive.tsx
@@ -24,11 +24,9 @@ import type {
} from '@wraith-protocol/sdk/chains/stellar';
import { useStealthKeys } from '@/context/StealthKeysContext';
import { useStellarWallet } from '@/context/StellarWalletContext';
-import { StellarMatchCard } from '@/components/StellarMatchCard';
-import { StellarReceiveView } from '@/components/StellarReceiveView';
-import { QRCodeModal } from '@/components/QRCodeModal';
-import { useStealthLabels } from '@/hooks/useStealthLabels';
-import { useStellarNotifications } from '@/hooks/useStellarNotifications';
+import { CopyButton } from '@/components/CopyButton';
+import { trackEvent } from '@/lib/telemetry';
+import { stellarTxUrl, stellarAddrUrl } from '@/lib/explorer';
import { STELLAR_NETWORK } from '@/config';
import { fetchWithRetry, withRetry, RetryExhaustedError } from '@/lib/stellar/retry';
import { useActivityStore } from '@/stores/activityStore';
@@ -318,7 +316,7 @@ function StellarMatchCardContainer({
}
setWithdrawHash(submitData.hash);
- updateActivity(txHashHex, 'confirmed');
+ trackEvent('withdraw');
onWithdrawn();
} catch (err) {
setRetryStatus('');
@@ -1026,43 +1024,9 @@ export function StellarReceive() {
new URL('../workers/stellar-scanner.worker.ts', import.meta.url),
{ type: 'module' },
);
- workerRef.current.onmessage = (e) => {
- if (e.data.type === 'SUCCESS') {
- const results = e.data.results;
- setMatched(results);
-
- results.forEach((m: MatchedAnnouncement) => {
- addActivity({
- id: m.stealthAddress, // use address as unique id for receives
- chain: 'stellar',
- wallet: address || '',
- kind: 'stealth-receive',
- direction: 'in',
- status: 'confirmed', // immediately confirmed since it's discovered
- timestamp: Date.now(),
- });
- });
-
- setHasScanned(true);
- setIsScanning(false);
- } else if (e.data.type === 'ERROR') {
- setError(e.data.error);
- setIsScanning(false);
- }
- };
-
- workerRef.current.onerror = () => {
- setError('Worker crashed');
- setIsScanning(false);
- };
-
- workerRef.current.postMessage({
- rpcUrl: STELLAR_NETWORK.rpcUrl,
- announcerContract: ANNOUNCER_CONTRACT,
- viewingKey: stellarKeys.viewingKey,
- spendingPubKey: stellarKeys.spendingPubKey,
- spendingScalar: stellarKeys.spendingScalar,
- });
+ setMatched(results);
+ setHasScanned(true);
+ trackEvent('scan_triggered');
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to start worker');
setIsScanning(false);
diff --git a/src/components/StellarSend.tsx b/src/components/StellarSend.tsx
index b625e50..61d13ca 100644
--- a/src/components/StellarSend.tsx
+++ b/src/components/StellarSend.tsx
@@ -18,15 +18,8 @@ import {
} from '@wraith-protocol/sdk/chains/stellar';
import { useStellarWallet } from '@/context/StellarWalletContext';
import { STELLAR_NETWORK } from '@/config';
-import { StellarSendView } from '@/components/StellarSendView';
-import { useActivityStore } from '@/stores/activityStore';
-import { QrReader } from 'react-qr-reader';
-import {
- emptyStellarSendSimulation,
- simulateStellarSendAnnouncement,
- type StellarSendSimulationState,
-} from '@/lib/stellarSimulation';
-import { fetchWithRetry, withRetry, RetryExhaustedError } from '@/lib/stellar/retry';
+import { CopyButton } from '@/components/CopyButton';
+import { trackEvent } from '@/lib/telemetry';
const ANNOUNCER_CONTRACT = 'CCJLJ2QRBJAAKIG6ELNQVXLLWMKKWVN5O2FKWUETHZGMPAD4MHK7WVWL';
const STELLAR_BASE_FEE_XLM = 0.00001;
@@ -403,6 +396,7 @@ export function StellarSend() {
const decoded = decodeStealthMetaAddress(metaAddress);
const result = generateStealthAddress(decoded.spendingPubKey, decoded.viewingPubKey);
setStealthResult(result);
+ trackEvent('send_submitted');
const horizonUrl = STELLAR_NETWORK.horizonUrl;
const networkPassphrase = STELLAR_NETWORK.networkPassphrase;
diff --git a/src/components/TelemetryBanner.tsx b/src/components/TelemetryBanner.tsx
new file mode 100644
index 0000000..3922d99
--- /dev/null
+++ b/src/components/TelemetryBanner.tsx
@@ -0,0 +1,51 @@
+import { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { getConsent, setConsent, type ConsentState } from '@/lib/telemetry';
+
+export function TelemetryBanner() {
+ const [consent, setConsentState] = useState(null);
+
+ useEffect(() => {
+ setConsentState(getConsent());
+ }, []);
+
+ if (consent !== null) return null;
+
+ function handleAccept() {
+ setConsent('accepted');
+ setConsentState('accepted');
+ }
+
+ function handleDecline() {
+ setConsent('declined');
+ setConsentState('declined');
+ }
+
+ return (
+
+
+
+ We use cookieless analytics to improve this demo.{' '}
+
+ Privacy page
+
+ . No wallet addresses or amounts are ever collected.
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/WalletConnect.tsx b/src/components/WalletConnect.tsx
index beedf5c..0cdee19 100644
--- a/src/components/WalletConnect.tsx
+++ b/src/components/WalletConnect.tsx
@@ -4,6 +4,13 @@ import { useWallet } from '@solana/wallet-adapter-react';
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import { ccc } from '@ckb-ccc/connector-react';
import { useChain } from '@/context/ChainContext';
+import { useStellarWallet } from '@/context/StellarWalletContext';
+import { trackEvent } from '@/lib/telemetry';
+
+const btnBase =
+ 'bg-transparent border border-outline-variant px-3 py-1.5 font-heading text-[10px] uppercase tracking-widest text-primary transition-colors hover:bg-surface-bright disabled:opacity-50 sm:px-4 sm:py-2 sm:text-xs h-8 sm:h-9';
+const btnConnected =
+ 'bg-transparent border border-outline-variant px-3 py-1.5 font-mono text-[10px] text-primary transition-colors hover:bg-surface-bright sm:px-4 sm:py-2 sm:text-xs h-8 sm:h-9';
import { useStellarWallet as useStellarWalletContext } from '@/context/StellarWalletContext';
import { useStellarWallet as useStellarWalletHook } from '@/hooks/useStellarWallet';
import { StellarWalletPicker } from '@/components/StellarWalletPicker';
@@ -26,7 +33,7 @@ function HorizenButton() {
})}
>
{!connected ? (
-