diff --git a/package-lock.json b/package-lock.json
index 3746bfc..35fc407 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -810,8 +810,7 @@
"node_modules/@next/env": {
"version": "15.3.8",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.8.tgz",
- "integrity": "sha512-SAfHg0g91MQVMPioeFeDjE+8UPF3j3BvHjs8ZKJAUz1BG7eMPvfCKOAgNWJ6s1MLNeP6O2InKQRTNblxPWuq+Q==",
- "license": "MIT"
+
},
"node_modules/@next/eslint-plugin-next": {
"version": "15.3.6",
@@ -6801,7 +6800,6 @@
"version": "15.3.8",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.8.tgz",
"integrity": "sha512-L+4c5Hlr84fuaNADZbB9+ceRX9/CzwxJ+obXIGHupboB/Q1OLbSUapFs4bO8hnS/E6zV/JDX7sG1QpKVR2bguA==",
- "license": "MIT",
"dependencies": {
"@next/env": "15.3.8",
"@swc/counter": "0.1.3",
diff --git a/src/app/api/rpc/route.ts b/src/app/api/rpc/route.ts
new file mode 100644
index 0000000..e250995
--- /dev/null
+++ b/src/app/api/rpc/route.ts
@@ -0,0 +1,38 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getNetworkConfig } from '@/lib/network-config';
+
+export async function POST(request: NextRequest) {
+ try {
+ const { network, ...body } = await request.json();
+
+ if (!network || !['testnet', 'mainnet'].includes(network)) {
+ return NextResponse.json({ error: 'Invalid network' }, { status: 400 });
+ }
+
+ const networkConfig = getNetworkConfig(network as 'testnet' | 'mainnet');
+
+ const response = await fetch(networkConfig.rpcUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ });
+
+ if (!response.ok) {
+ return NextResponse.json(
+ { error: `RPC error: ${response.status}` },
+ { status: response.status }
+ );
+ }
+
+ const data = await response.json();
+ return NextResponse.json(data);
+ } catch (error) {
+ console.error('RPC proxy error:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/components/escrow/EscrowDetails.tsx b/src/components/escrow/EscrowDetails.tsx
index c6d8a71..0efc326 100644
--- a/src/components/escrow/EscrowDetails.tsx
+++ b/src/components/escrow/EscrowDetails.tsx
@@ -32,6 +32,7 @@ import { useIsMobile } from "@/hooks/useIsMobile";
// ⬇️ New hooks
import { useEscrowData } from "@/hooks/useEscrowData";
import { useTokenBalance } from "@/hooks/useTokenBalance";
+import { useRecentEvents } from "@/hooks/useRecentEvents";
// (useMemo is consolidated in the import above)
@@ -45,10 +46,22 @@ const EscrowDetailsClient: React.FC = ({
initialEscrowId,
}) => {
const router = useRouter();
- const { currentNetwork } = useNetwork();
+ const { currentNetwork, setNetwork } = useNetwork();
// Input / responsive state
const [contractId, setContractId] = useState(initialEscrowId);
+
+ // Handle network switch, updating contract ID for examples
+ const handleSwitchNetwork = useCallback((network: 'testnet' | 'mainnet') => {
+ setNetwork(network);
+ // If current contract is an example, switch to the example for the new network
+ if (contractId === EXAMPLE_CONTRACT_IDS.testnet || contractId === EXAMPLE_CONTRACT_IDS.mainnet) {
+ const newContractId = EXAMPLE_CONTRACT_IDS[network];
+ setContractId(newContractId);
+ // Update URL
+ router.replace(`/${newContractId}`);
+ }
+ }, [contractId, setNetwork, router, setContractId]);
const isMobile = useIsMobile();
const [isSearchFocused, setIsSearchFocused] = useState(false);
@@ -66,6 +79,12 @@ const isMobile = useIsMobile();
currentNetwork
);
+ // Recent events hook
+ const { events, loading: eventsLoading, error: eventsError } = useRecentEvents(
+ contractId,
+ currentNetwork
+ );
+
const organizedWithLive = useMemo(() => {
if (!organized) return null;
if (!ledgerBalance) return organized; // nothing to override
@@ -97,7 +116,7 @@ const isMobile = useIsMobile();
setTransactionLoading(true);
setTransactionError(null);
try {
- const response = await fetchTransactions(id, { cursor, limit: 20 });
+ const response = await fetchTransactions(id, currentNetwork, { cursor, limit: 20 });
setTransactionResponse(response);
if (cursor) {
setTransactions((prev) => [...prev, ...response.transactions]);
@@ -110,7 +129,7 @@ const isMobile = useIsMobile();
setTransactionLoading(false);
}
},
- []
+ [currentNetwork]
);
// Initial + network-change fetch (escrow + txs)
@@ -292,7 +311,11 @@ useEffect(() => {
)}
{/* Error Display */}
-
+
{/* Content Section (hidden when showing transactions as a page) */}
{!showOnlyTransactions && (
@@ -356,6 +379,82 @@ useEffect(() => {
+
+ {/* Recent Contract Events Section */}
+
+
+
Recent Contract Events
+
+
+
+
Last 7 days (RPC-limited)
+
+ This view shows only events available via Stellar RPC (last ~7 days).
+ Historical events will be available in future versions.
+
+
+
+
+
+
+
+ {eventsLoading ? (
+
+
Loading recent events...
+
+ ) : eventsError ? (
+
+
Unable to fetch recent events from Stellar RPC.
+
+ ) : events.length === 0 ? (
+
+
No contract events found in the last 7 days.
+
+ ) : (
+
+ {events.map((event) => (
+
+
+ Event Type: {event.type}
+ Ledger: {event.ledger}
+
+
+ ID: {event.id}
+
+
+
Topics:
+
+ {event.topics.join(', ')}
+
+
+
+
Value:
+
+ {event.value}
+
+
+
+ ))}
+
+ )}
+
+
+
+
)}
@@ -365,6 +464,7 @@ useEffect(() => {
isOpen={isModalOpen}
onClose={handleModalClose}
isMobile={isMobile}
+ network={currentNetwork}
/>
diff --git a/src/components/escrow/TransactionDetailModal.tsx b/src/components/escrow/TransactionDetailModal.tsx
index 0dc6865..88f9eeb 100644
--- a/src/components/escrow/TransactionDetailModal.tsx
+++ b/src/components/escrow/TransactionDetailModal.tsx
@@ -29,12 +29,14 @@ import {
formatTransactionTime,
truncateHash,
} from "@/utils/transactionFetcher";
+import { NetworkType } from "@/lib/network-config";
interface TransactionDetailModalProps {
txHash: string | null;
isOpen: boolean;
onClose: () => void;
isMobile: boolean;
+ network: NetworkType;
}
export const TransactionDetailModal: React.FC = ({
@@ -42,6 +44,7 @@ export const TransactionDetailModal: React.FC = ({
isOpen,
onClose,
isMobile,
+ network,
}) => {
const [details, setDetails] = useState(null);
const [loading, setLoading] = useState(false);
@@ -54,7 +57,7 @@ export const TransactionDetailModal: React.FC = ({
setError(null);
try {
- const transactionDetails = await fetchTransactionDetails(txHash);
+ const transactionDetails = await fetchTransactionDetails(txHash, network);
setDetails(transactionDetails);
} catch (err) {
setError("Failed to fetch transaction details");
@@ -62,7 +65,7 @@ export const TransactionDetailModal: React.FC = ({
} finally {
setLoading(false);
}
- }, [txHash]);
+ }, [txHash, network]);
const copyToClipboard = async (text: string) => {
try {
diff --git a/src/components/escrow/error-display.tsx b/src/components/escrow/error-display.tsx
index 2a4e452..34136ac 100644
--- a/src/components/escrow/error-display.tsx
+++ b/src/components/escrow/error-display.tsx
@@ -1,24 +1,79 @@
import { motion, AnimatePresence } from "framer-motion"
-import { AlertCircle } from "lucide-react"
+import { AlertCircle, RefreshCw } from "lucide-react"
+import { Button } from "@/components/ui/button"
interface ErrorDisplayProps {
error: string | null
+ onSwitchNetwork?: (network: 'testnet' | 'mainnet') => void
+ onRetry?: () => void
}
-export const ErrorDisplay = ({ error }: ErrorDisplayProps) => {
+export const ErrorDisplay = ({ error, onSwitchNetwork, onRetry }: ErrorDisplayProps) => {
+ if (!error) return null;
+
+ const parts = error.split('•').map(part => part.trim()).filter(part => part);
+
+ // Check if error suggests switching networks
+ const switchSuggestion = parts.find(part => part.includes('Try switching to'));
+ let targetNetwork: 'testnet' | 'mainnet' | null = null;
+ if (switchSuggestion) {
+ if (switchSuggestion.includes('mainnet')) {
+ targetNetwork = 'mainnet';
+ } else if (switchSuggestion.includes('testnet')) {
+ targetNetwork = 'testnet';
+ }
+ }
+
return (
- {error && (
-
-
- {error}
-
- )}
+
+
+
+
+ {parts.length > 1 ? (
+
+
{parts[0]}
+
+ {parts.slice(1).map((line, index) => (
+ - {line}
+ ))}
+
+
+ ) : (
+
{error}
+ )}
+ {/* Action buttons */}
+
+ {targetNetwork && onSwitchNetwork && (
+
+ )}
+ {onRetry && (
+
+ )}
+
+
+
+
)
}
\ No newline at end of file
diff --git a/src/components/escrow/escrow-content.tsx b/src/components/escrow/escrow-content.tsx
index 1ce543c..0c119e3 100644
--- a/src/components/escrow/escrow-content.tsx
+++ b/src/components/escrow/escrow-content.tsx
@@ -6,8 +6,7 @@ import { TitleCard } from "@/components/escrow/title-card"
import { TabView } from "@/components/escrow/tab-view"
import { DesktopView } from "@/components/escrow/desktop-view"
import { WelcomeState } from "@/components/escrow/welcome-state"
-import error from "next/error"
-import { useNetwork } from "@/contexts/NetworkContext"; // Add this line
+import { useNetwork } from "@/contexts/NetworkContext";
interface EscrowContentProps {
@@ -70,7 +69,7 @@ export const EscrowContent = ({
{/* No data state */}
-
+
)
}
diff --git a/src/components/escrow/welcome-state.tsx b/src/components/escrow/welcome-state.tsx
index 51bba43..650d970 100644
--- a/src/components/escrow/welcome-state.tsx
+++ b/src/components/escrow/welcome-state.tsx
@@ -1,17 +1,21 @@
import { motion, AnimatePresence } from "framer-motion"
import { Button } from "@/components/ui/button"
-import { EXAMPLE_CONTRACT_ID } from "@/lib/escrow-constants"
+import { EXAMPLE_CONTRACT_IDS } from "@/lib/escrow-constants"
+import type { NetworkType } from "@/lib/network-config"
interface WelcomeStateProps {
showWelcome: boolean;
handleUseExample?: () => void;
+ network?: NetworkType;
}
-export const WelcomeState = ({
+export const WelcomeState = ({
showWelcome,
- handleUseExample
+ handleUseExample,
+ network = 'mainnet'
}: WelcomeStateProps) => {
const useExample = handleUseExample || (() => {});
+ const exampleId = EXAMPLE_CONTRACT_IDS[network];
return (
@@ -55,7 +59,7 @@ export const WelcomeState = ({
className="text-sm text-primary p-0 h-auto"
onClick={useExample}
>
- {EXAMPLE_CONTRACT_ID}
+ {exampleId}
diff --git a/src/components/ui/theme-toggle.tsx b/src/components/ui/theme-toggle.tsx
index 21a36b0..b46311c 100644
--- a/src/components/ui/theme-toggle.tsx
+++ b/src/components/ui/theme-toggle.tsx
@@ -4,17 +4,7 @@ import { useEffect, useState } from "react";
import { safeLocalStorage } from "@/utils/storage";
export function ThemeToggle() {
- const [isDark, setIsDark] = useState(false);
- const [mounted, setMounted] = useState(false);
- // Only access localStorage after component mounts (client-side only)
- useEffect(() => {
- setMounted(true);
- const stored = safeLocalStorage.getItem("theme");
- if (stored) {
- setIsDark(stored === "dark");
- } else if (typeof window !== 'undefined' && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
- setIsDark(true);
}
}, []);
@@ -22,12 +12,7 @@ export function ThemeToggle() {
useEffect(() => {
if (!mounted) return;
const root = document.documentElement;
- if (isDark) {
- root.classList.add("dark");
- } else {
- root.classList.remove("dark");
- }
- safeLocalStorage.setItem("theme", isDark ? "dark" : "light");
+
}, [isDark, mounted]);
const toggle = () => setIsDark((v) => !v);
diff --git a/src/hooks/useEscrowData.ts b/src/hooks/useEscrowData.ts
index 5a48e3b..30b2cc3 100644
--- a/src/hooks/useEscrowData.ts
+++ b/src/hooks/useEscrowData.ts
@@ -3,6 +3,17 @@ import { useCallback, useEffect, useState } from "react";
import { getLedgerKeyContractCode, type EscrowMap } from "@/utils/ledgerkeycontract";
import { organizeEscrowData, type OrganizedEscrowData } from "@/mappers/escrow-mapper";
import type { NetworkType } from "@/lib/network-config";
+import { EXAMPLE_CONTRACT_IDS } from "@/lib/escrow-constants";
+
+/**
+ * Basic validation for Stellar contract ID format
+ */
+function isValidContractId(contractId: string): boolean {
+ // Stellar contract IDs are 56 characters long and use base32 alphabet
+ if (contractId.length !== 56) return false;
+ const base32Regex = /^[A-Z2-7]+$/;
+ return base32Regex.test(contractId);
+}
/**
* Loads raw escrow contract storage and maps it to OrganizedEscrowData for UI.
@@ -19,14 +30,38 @@ export function useEscrowData(contractId: string, network: NetworkType, isMobile
setLoading(true);
setError(null);
+ // Validate contract ID format
+ if (!isValidContractId(contractId)) {
+ setRaw(null);
+ setOrganized(null);
+ setError(`Invalid contract ID format. Stellar contract IDs should be 56 characters long and contain only uppercase letters A-Z and digits 2-7.`);
+ setLoading(false);
+ return;
+ }
+
try {
const data = await getLedgerKeyContractCode(contractId, network);
- setRaw(data);
- setOrganized(organizeEscrowData(data, contractId, isMobile));
+ if (data.length === 0) {
+ setRaw(null);
+ setOrganized(null);
+ const isExampleContract = contractId === EXAMPLE_CONTRACT_IDS.testnet || contractId === EXAMPLE_CONTRACT_IDS.mainnet;
+ const baseMessage = `No ledger entry found for contract ID "${contractId}".`;
+ const suggestions = isExampleContract
+ ? `•This example contract may not be deployed on ${network}•Use a different contract ID•The contract was recently deployed and not yet indexed`
+ : `•The contract ID is invalid or doesn't exist on ${network}•The contract exists on a different network•The contract was recently deployed and not yet indexed`;
+ setError(`${baseMessage}${suggestions}`);
+ } else {
+ setRaw(data);
+ setOrganized(organizeEscrowData(data, contractId, isMobile));
+ }
} catch (e) {
setRaw(null);
setOrganized(null);
- setError(e instanceof Error ? e.message : "Failed to fetch escrow data");
+ const errorMessage = e instanceof Error ? e.message : "Failed to fetch escrow data";
+ const userFriendlyMessage = errorMessage === "Failed to fetch"
+ ? "Network error: Unable to connect to Stellar RPC. Please check your internet connection or try again later."
+ : errorMessage;
+ setError(userFriendlyMessage);
} finally {
setLoading(false);
}
diff --git a/src/hooks/useRecentEvents.ts b/src/hooks/useRecentEvents.ts
new file mode 100644
index 0000000..adc25fc
--- /dev/null
+++ b/src/hooks/useRecentEvents.ts
@@ -0,0 +1,128 @@
+import { useState, useEffect, useCallback } from 'react';
+import { type NetworkType } from '@/lib/network-config';
+
+export interface ContractEvent {
+ id: string;
+ type: string;
+ ledger: number;
+ contractId?: string;
+ topics: string[]; // base64
+ value: string; // base64
+}
+
+export interface UseRecentEventsResult {
+ events: ContractEvent[];
+ loading: boolean;
+ error: string | null;
+ refetch: () => void;
+}
+
+export function useRecentEvents(
+ contractId: string,
+ network: NetworkType
+): UseRecentEventsResult {
+ const [events, setEvents] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const fetchEvents = useCallback(async () => {
+ if (!contractId) return;
+
+ setLoading(true);
+ setError(null);
+
+ try {
+ // Get latest ledger
+ const latestLedgerResponse = await fetch('/api/rpc', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ network,
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'getLatestLedger',
+ }),
+ });
+
+ if (!latestLedgerResponse.ok) {
+ throw new Error('Failed to get latest ledger');
+ }
+
+ const latestLedgerData = await latestLedgerResponse.json();
+ const endLedger = latestLedgerData.result.sequence;
+
+ // Approximate 7 days: ~5 seconds per ledger, 7*24*3600/5 ≈ 12096
+ const sevenDaysLedgers = Math.floor((7 * 24 * 3600) / 5);
+ const startLedger = Math.max(1, endLedger - sevenDaysLedgers);
+
+ // Get events
+ const eventsResponse = await fetch('/api/rpc', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ network,
+ jsonrpc: '2.0',
+ id: 2,
+ method: 'getEvents',
+ params: {
+ startLedger,
+ filters: [
+ {
+ contractIds: [contractId],
+ type: 'contract',
+ },
+ ],
+ limit: 100,
+ },
+ }),
+ });
+
+ if (!eventsResponse.ok) {
+ throw new Error('Failed to get events');
+ }
+
+ const eventsData = await eventsResponse.json();
+
+ if (eventsData.error) {
+ throw new Error(eventsData.error.message || 'Failed to fetch events');
+ }
+
+ // Map to our interface
+ const mappedEvents: ContractEvent[] = (eventsData.result?.events || []).map((event: {
+ id: string;
+ type: string;
+ ledger: number;
+ contractId?: string;
+ topic: string[];
+ value: string;
+ }) => ({
+ id: event.id,
+ type: event.type,
+ ledger: event.ledger,
+ contractId: event.contractId,
+ topics: event.topic,
+ value: event.value,
+ }));
+
+ // Sort by ledger descending (most recent first)
+ mappedEvents.sort((a, b) => b.ledger - a.ledger);
+
+ setEvents(mappedEvents);
+ } catch (err: unknown) {
+ setError(err instanceof Error ? err.message : 'Failed to fetch recent events');
+ } finally {
+ setLoading(false);
+ }
+ }, [contractId, network]);
+
+ useEffect(() => {
+ fetchEvents();
+ }, [fetchEvents]);
+
+ return {
+ events,
+ loading,
+ error,
+ refetch: fetchEvents,
+ };
+}
\ No newline at end of file
diff --git a/src/lib/network-config.ts b/src/lib/network-config.ts
index fc13b4b..692fb52 100644
--- a/src/lib/network-config.ts
+++ b/src/lib/network-config.ts
@@ -16,7 +16,7 @@ export const NETWORK_CONFIGS: Record = {
},
mainnet: {
name: 'Mainnet',
- rpcUrl: 'https://stellar.api.onfinality.io/public',
+ rpcUrl: 'https://soroban-mainnet.stellar.org',
horizonUrl: 'https://horizon.stellar.org',
networkPassphrase: 'Public Global Stellar Network ; September 2015'
}
@@ -27,85 +27,5 @@ export function getNetworkConfig(network: NetworkType): NetworkConfig {
}
export function getDefaultNetwork(): NetworkType {
- return 'testnet';
-}
-/**
- * Generates a Stellar Lab URL for the contract explorer
- *
- * Stellar Lab URL format:
- * https://lab.stellar.org/smart-contracts/contract-explorer?
- * $=network
- * &id={network}
- * &label={networkLabel}
- * &horizonUrl={horizonUrl}
- * &rpcUrl={rpcUrl}
- * &passphrase={urlEncodedPassphrase}
- * &smartContracts$explorer$contractId={contractId}
- *
- * Note: We manually construct the URL string instead of using URLSearchParams
- * because Stellar Lab requires dollar signs ($) to remain unencoded in the
- * parameter names (e.g., smartContracts$explorer$contractId), and the passphrase
- * should only be encoded once, not double-encoded.
- *
- * @param network - The network type (testnet or mainnet)
- * @param contractId - The contract ID to open in Stellar Lab
- * @returns The complete Stellar Lab URL
- */
-export function getStellarLabUrl(network: NetworkType, contractId: string): string {
- // CONTRACT ID IS REQUIRED - throw error if not provided
- if (!contractId || typeof contractId !== 'string' || contractId.trim() === '') {
- throw new Error(`getStellarLabUrl: contractId is required but was: ${JSON.stringify(contractId)}`);
- }
-
- const config = getNetworkConfig(network);
- const trimmedContractId = contractId.trim();
-
- // Base URL for Stellar Lab contract explorer
- const baseUrl = 'https://lab.stellar.org/smart-contracts/contract-explorer';
-
- // Stellar Lab uses specific RPC endpoints
- // Mainnet uses Ankr RPC, testnet uses official Stellar endpoint
- // Note: URLs use double slashes (https:////) as required by Stellar Lab
- const labRpcUrl = network === 'testnet'
- ? 'https:////soroban-testnet.stellar.org'
- : 'https:////rpc.ankr.com//stellar_soroban';
-
- // Horizon URLs also use double slashes
- const labHorizonUrl = network === 'testnet'
- ? 'https:////horizon-testnet.stellar.org'
- : 'https:////horizon.stellar.org';
-
- // URL encode individual values (but not the parameter names with $)
- // The passphrase format includes /; before the date and trailing semicolon
- // Note: The trailing semicolon in passphrase becomes %3B when encoded
- let encodedPassphrase: string;
- if (network === 'mainnet') {
- // Mainnet passphrase: "Public Global Stellar Network /; September 2015;"
- // When encoded: "Public%20Global%20Stellar%20Network%20/;%20September%202015;"
- // The /; stays as /; (not encoded), the trailing ; becomes part of the encoded value
- encodedPassphrase = encodeURIComponent('Public Global Stellar Network /; September 2015;');
- } else {
- // Testnet passphrase: "Test SDF Network /; September 2015;"
- encodedPassphrase = encodeURIComponent('Test SDF Network /; September 2015;');
- }
-
- const encodedLabel = encodeURIComponent(config.name);
-
- // Stellar Lab URL format uses $=network$id={network} (no & between $=network and id)
- // Passphrase ends with ; (encoded as %3B), then &, then contract ID, then ;; at the very end
- // CONTRACT ID IS REQUIRED - always include it
- const params: string[] = [
- `$=network$id=${network}`, // Special format: $=network$id (no & separator)
- `label=${encodedLabel}`,
- `horizonUrl=${labHorizonUrl}`, // Double slashes preserved
- `rpcUrl=${labRpcUrl}`, // Double slashes preserved
- `passphrase=${encodedPassphrase}`, // Ends with ; (encoded as %3B)
- `smartContracts$explorer$contractId=${trimmedContractId}`, // Dollar signs must remain unencoded - REQUIRED
- ];
-
- const queryString = params.join('&') + ';;'; // Trailing ;; at the very end
- const finalUrl = `${baseUrl}?${queryString}`;
-
- return finalUrl;
}
\ No newline at end of file
diff --git a/src/utils/ledgerkeycontract.ts b/src/utils/ledgerkeycontract.ts
index bf990db..479557d 100644
--- a/src/utils/ledgerkeycontract.ts
+++ b/src/utils/ledgerkeycontract.ts
@@ -1,5 +1,5 @@
import { Contract } from "@stellar/stellar-sdk";
-import { NetworkType, getNetworkConfig } from "@/lib/network-config";
+import { NetworkType } from "@/lib/network-config";
// Define types for the escrow data map
interface EscrowKey {
@@ -46,13 +46,12 @@ export async function getLedgerKeyContractCode(
},
};
- const networkConfig = getNetworkConfig(network);
- const res = await fetch(networkConfig.rpcUrl, {
+ const res = await fetch('/api/rpc', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
- body: JSON.stringify(requestBody),
+ body: JSON.stringify({ network, ...requestBody }),
});
if (!res.ok) {
@@ -67,7 +66,7 @@ export async function getLedgerKeyContractCode(
const entry = json.result.entries[0];
if (!entry) {
- throw new Error("No ledger entry found for this contract ID");
+ return [];
}
const contractData = entry?.dataJson?.contract_data?.val?.contract_instance;
diff --git a/src/utils/transactionFetcher.ts b/src/utils/transactionFetcher.ts
index bb720a5..078ef7b 100644
--- a/src/utils/transactionFetcher.ts
+++ b/src/utils/transactionFetcher.ts
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Contract } from "@stellar/stellar-sdk";
+import { NetworkType } from "@/lib/network-config";
// Types for transaction data
export interface TransactionMetadata {
@@ -38,14 +39,13 @@ export interface TransactionResponse {
retentionNotice?: string;
}
-const SOROBAN_RPC_URL = process.env.NEXT_PUBLIC_SOROBAN_RPC_URL || "https://soroban-testnet.stellar.org";
-
/**
* Fetches recent transactions for a contract using Soroban JSON-RPC
* Gracefully handles retention-related errors
*/
export async function fetchTransactions(
contractId: string,
+ network: NetworkType,
options: FetchTransactionsOptions = {}
): Promise {
try {
@@ -72,12 +72,12 @@ export async function fetchTransactions(
}
};
- const response = await fetch(SOROBAN_RPC_URL, {
+ const response = await fetch('/api/rpc', {
method: "POST",
headers: {
"Content-Type": "application/json",
},
- body: JSON.stringify(requestBody),
+ body: JSON.stringify({ network, ...requestBody }),
});
if (!response.ok) {
@@ -138,7 +138,7 @@ export async function fetchTransactions(
* Fetches detailed information for a specific transaction
* Returns full details with XDR decoded as JSON
*/
-export async function fetchTransactionDetails(txHash: string): Promise {
+export async function fetchTransactionDetails(txHash: string, network: NetworkType): Promise {
try {
const requestBody = {
jsonrpc: "2.0",
@@ -149,12 +149,12 @@ export async function fetchTransactionDetails(txHash: string): Promise