Skip to content

Commit 2f08e0b

Browse files
committed
Fixes for railgun
1 parent f3c10f9 commit 2f08e0b

File tree

8 files changed

+636
-39
lines changed

8 files changed

+636
-39
lines changed

package-lock.json

Lines changed: 404 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
"@tanstack/react-query": "^5.90.18",
2222
"@types/snarkjs": "^0.7.9",
2323
"@types/three": "^0.182.0",
24+
"browser-level": "^3.0.0",
2425
"clsx": "^2.1.1",
2526
"ethers": "6.13.1",
27+
"level-js": "^6.1.0",
2628
"lucide-react": "^0.562.0",
2729
"motion": "^12.26.2",
2830
"react": "^19.2.0",
@@ -57,6 +59,8 @@
5759
"typescript-eslint": "^8.46.4",
5860
"vite": "^7.2.4",
5961
"vite-plugin-node-polyfills": "^0.25.0",
62+
"vite-plugin-top-level-await": "^1.6.0",
63+
"vite-plugin-wasm": "^3.5.0",
6064
"vitest": "^4.0.17"
6165
},
6266
"overrides": {
29.1 KB
Binary file not shown.
1.85 MB
Binary file not shown.

src/components/bridge/BridgeWidget.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,12 +354,17 @@ export function BridgeWidget() {
354354
};
355355
}
356356

357-
// Show engine error
358-
if (engineError) {
357+
// Show engine error - but only if user is trying to use privacy
358+
if (engineError && !privacyEnabled) {
359+
// Don't show error for standard mode - privacy is optional
360+
return null;
361+
}
362+
363+
if (engineError && privacyEnabled) {
359364
return {
360-
type: 'error',
361-
title: 'Privacy Engine Error',
362-
message: engineError,
365+
type: 'warning',
366+
title: 'Privacy Mode Unavailable',
367+
message: 'The privacy engine could not be initialized. Standard bridging is still available.',
363368
};
364369
}
365370

@@ -445,6 +450,14 @@ export function BridgeWidget() {
445450
enabled={privacyEnabled}
446451
onToggle={handlePrivacyToggle}
447452
disabled={isExecuting || isWalletLoading || !isEngineReady}
453+
disabledReason={
454+
isExecuting ? 'Cannot change during transaction' :
455+
isWalletLoading ? 'Wallet is loading...' :
456+
!isEngineReady && isEngineInitializing ? 'Privacy engine is initializing...' :
457+
!isEngineReady && engineError ? 'Privacy engine unavailable' :
458+
!isEngineReady ? 'Privacy engine not ready' :
459+
undefined
460+
}
448461
/>
449462
<SlippageSettings slippage={slippage} onSlippageChange={setSlippage} />
450463
</div>

src/components/bridge/PrivacyToggle.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface PrivacyToggleProps {
77
enabled: boolean;
88
onToggle: (enabled: boolean) => void;
99
disabled?: boolean;
10+
disabledReason?: string;
1011
}
1112

1213
interface TradeoffItem {
@@ -48,7 +49,7 @@ const TRADEOFFS: TradeoffItem[] = [
4849
},
4950
];
5051

51-
export function PrivacyToggle({ enabled, onToggle, disabled }: PrivacyToggleProps) {
52+
export function PrivacyToggle({ enabled, onToggle, disabled, disabledReason }: PrivacyToggleProps) {
5253
const [showInfo, setShowInfo] = useState(false);
5354
const [showConfirm, setShowConfirm] = useState(false);
5455

@@ -69,6 +70,11 @@ export function PrivacyToggle({ enabled, onToggle, disabled }: PrivacyToggleProp
6970
setShowConfirm(false);
7071
};
7172

73+
// Determine tooltip text
74+
const tooltipText = disabled
75+
? (disabledReason || 'Privacy mode is currently unavailable')
76+
: (enabled ? 'Switch to standard mode' : 'Enable privacy mode');
77+
7278
return (
7379
<>
7480
<div className="flex items-center gap-3">
@@ -85,6 +91,7 @@ export function PrivacyToggle({ enabled, onToggle, disabled }: PrivacyToggleProp
8591
<button
8692
onClick={handleToggleClick}
8793
disabled={disabled}
94+
title={tooltipText}
8895
className={cn(
8996
'flex items-center gap-2 px-3 py-2 rounded-lg transition-all duration-200',
9097
'active:scale-95',

src/services/railgun/engine.ts

Lines changed: 141 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
getNetworkName,
3030
isRailgunSupported,
3131
} from './types';
32+
import leveljs from 'level-js';
3233

3334
// Engine singleton state
3435
let engineState: EngineState = {
@@ -173,32 +174,112 @@ const RPC_PROVIDERS: Record<RailgunChainId, FallbackProviderJsonConfig> = {
173174
},
174175
};
175176

177+
/**
178+
* IndexedDB database name for artifact caching
179+
*/
180+
const ARTIFACT_DB_NAME = 'railgun-artifacts';
181+
const ARTIFACT_STORE_NAME = 'artifacts';
182+
183+
/**
184+
* Open IndexedDB for artifact storage
185+
*/
186+
function openArtifactDB(): Promise<IDBDatabase> {
187+
return new Promise((resolve, reject) => {
188+
const request = indexedDB.open(ARTIFACT_DB_NAME, 1);
189+
190+
request.onerror = () => {
191+
console.error('[RAILGUN] Failed to open artifact database:', request.error);
192+
reject(request.error);
193+
};
194+
195+
request.onsuccess = () => {
196+
resolve(request.result);
197+
};
198+
199+
request.onupgradeneeded = (event) => {
200+
const db = (event.target as IDBOpenDBRequest).result;
201+
if (!db.objectStoreNames.contains(ARTIFACT_STORE_NAME)) {
202+
db.createObjectStore(ARTIFACT_STORE_NAME);
203+
}
204+
};
205+
});
206+
}
207+
176208
/**
177209
* Artifact store configuration for browser environment
178-
* Uses a CDN-based approach for downloading ZK circuit artifacts
210+
* Uses IndexedDB for caching ZK circuit artifacts
179211
*/
180212
const artifactStore = {
181-
get: async (_path: string): Promise<string | Buffer | null> => {
182-
// In browser, return null to trigger download from CDN
183-
return null;
213+
get: async (path: string): Promise<string | Buffer | null> => {
214+
try {
215+
const db = await openArtifactDB();
216+
return new Promise((resolve) => {
217+
const transaction = db.transaction(ARTIFACT_STORE_NAME, 'readonly');
218+
const store = transaction.objectStore(ARTIFACT_STORE_NAME);
219+
const request = store.get(path);
220+
221+
request.onerror = () => {
222+
console.warn('[RAILGUN] Failed to get artifact:', path);
223+
resolve(null);
224+
};
225+
226+
request.onsuccess = () => {
227+
resolve(request.result || null);
228+
};
229+
230+
transaction.oncomplete = () => {
231+
db.close();
232+
};
233+
});
234+
} catch (error) {
235+
console.warn('[RAILGUN] Artifact store get error:', error);
236+
return null;
237+
}
184238
},
185-
store: async (_dir: string, _path: string, _item: string | Uint8Array): Promise<void> => {
186-
// Browser storage is handled internally by the SDK using IndexedDB
239+
240+
store: async (_dir: string, path: string, item: string | Uint8Array): Promise<void> => {
241+
try {
242+
const db = await openArtifactDB();
243+
return new Promise((resolve, reject) => {
244+
const transaction = db.transaction(ARTIFACT_STORE_NAME, 'readwrite');
245+
const store = transaction.objectStore(ARTIFACT_STORE_NAME);
246+
const request = store.put(item, path);
247+
248+
request.onerror = () => {
249+
console.warn('[RAILGUN] Failed to store artifact:', path);
250+
reject(request.error);
251+
};
252+
253+
request.onsuccess = () => {
254+
resolve();
255+
};
256+
257+
transaction.oncomplete = () => {
258+
db.close();
259+
};
260+
});
261+
} catch (error) {
262+
console.warn('[RAILGUN] Artifact store put error:', error);
263+
}
187264
},
188-
exists: async (_path: string): Promise<boolean> => {
189-
// Let the SDK handle artifact caching
190-
return false;
265+
266+
exists: async (path: string): Promise<boolean> => {
267+
try {
268+
const result = await artifactStore.get(path);
269+
return result !== null;
270+
} catch {
271+
return false;
272+
}
191273
},
192274
};
193275

194276
/**
195277
* Create the database storage for browser environment
196-
* Uses IndexedDB via localForage internally
278+
* Uses level-js which is abstract-leveldown compatible and backed by IndexedDB
197279
*/
198-
function createBrowserDatabase(_dbPath: string) {
199-
// The SDK handles browser database creation internally
200-
// This is a no-op for browser environments
201-
return undefined;
280+
function createBrowserDatabase(dbPath: string): ReturnType<typeof leveljs> {
281+
// Create a level-js database (abstract-leveldown compatible, backed by IndexedDB)
282+
return leveljs(dbPath);
202283
}
203284

204285
/**
@@ -226,24 +307,47 @@ async function doInitializeEngine(
226307
config?: Partial<RailgunEngineConfig>
227308
): Promise<boolean> {
228309
try {
310+
console.log('[RAILGUN] Starting engine initialization...');
311+
229312
updateEngineState({
230313
status: 'initializing',
231314
error: undefined,
232315
});
233316

234-
const walletSource = config?.walletSource ?? 'liquyn-swap';
317+
const walletSource = config?.walletSource ?? 'liquynswap';
235318
const poiNodeURLs = config?.poiConfig?.nodeURLs ?? DEFAULT_POI_NODES;
236-
const shouldDebug = config?.shouldDebug ?? false;
319+
const shouldDebug = config?.shouldDebug ?? true; // Enable debug by default for troubleshooting
320+
321+
console.log('[RAILGUN] Config:', { walletSource, poiNodeURLs, shouldDebug });
322+
323+
updateEngineState({ status: 'downloading_artifacts' });
324+
console.log('[RAILGUN] Calling startRailgunEngine...');
237325

238-
// Set up balance update callback
326+
const dbPath = createBrowserDatabase('railgun-db');
327+
console.log('[RAILGUN] Database path:', dbPath);
328+
329+
// Initialize the engine FIRST - callbacks must be set AFTER engine is started
330+
await startRailgunEngine(
331+
walletSource,
332+
dbPath,
333+
shouldDebug,
334+
artifactStore,
335+
false, // useNativeArtifacts - false for browser
336+
false, // skipMerkletreeScans - we want full scanning
337+
poiNodeURLs,
338+
);
339+
340+
console.log('[RAILGUN] Engine started, setting up callbacks...');
341+
342+
// Set up balance update callback AFTER engine is started
239343
setOnBalanceUpdateCallback(async (balanceData) => {
240344
// Balance updated - this will be handled by the wallet module
241345
if (shouldDebug && isDefined(balanceData)) {
242346
console.log('[RAILGUN] Balance update received');
243347
}
244348
});
245349

246-
// Set up merkletree scan callbacks
350+
// Set up merkletree scan callbacks AFTER engine is started
247351
setOnUTXOMerkletreeScanCallback((scanData) => {
248352
if (shouldDebug && isDefined(scanData)) {
249353
const progress = scanData.progress ?? 0;
@@ -258,18 +362,7 @@ async function doInitializeEngine(
258362
}
259363
});
260364

261-
updateEngineState({ status: 'downloading_artifacts' });
262-
263-
// Initialize the engine
264-
await startRailgunEngine(
265-
walletSource,
266-
createBrowserDatabase('railgun-db'),
267-
shouldDebug,
268-
artifactStore,
269-
false, // useNativeArtifacts - false for browser
270-
false, // skipMerkletreeScans - we want full scanning
271-
poiNodeURLs,
272-
);
365+
console.log('[RAILGUN] Callbacks set, loading providers...');
273366

274367
// Load providers for all supported networks
275368
await loadProvidersForNetworks();
@@ -279,8 +372,24 @@ async function doInitializeEngine(
279372

280373
return true;
281374
} catch (error) {
282-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
283-
console.error('[RAILGUN] Engine initialization failed:', errorMessage);
375+
// Log the full error for debugging
376+
console.error('[RAILGUN] Engine initialization failed:', error);
377+
378+
// Extract error message
379+
let errorMessage = 'Unknown error during engine initialization';
380+
if (error instanceof Error) {
381+
errorMessage = error.message;
382+
// Log stack trace for debugging
383+
if (error.stack) {
384+
console.error('[RAILGUN] Stack trace:', error.stack);
385+
}
386+
} else if (typeof error === 'string') {
387+
errorMessage = error;
388+
} else if (error && typeof error === 'object') {
389+
errorMessage = JSON.stringify(error);
390+
}
391+
392+
console.error('[RAILGUN] Error message:', errorMessage);
284393

285394
updateEngineState({
286395
status: 'error',

0 commit comments

Comments
 (0)