The application integrates with Cardano wallets to enable blockchain interactions. This section provides a comprehensive overview of how the OADA UI handles wallet connections, transactions, and blockchain interactions.
Overview
The wallet integration system serves as the bridge between the OADA UI and the Cardano blockchain. It handles:
Wallet connection and disconnection
Transaction building and signing
UTXO management
Network switching
Address validation and verification
Balance tracking
Transaction history
Supported Wallets
The application supports multiple Cardano wallets, each implementing the standard Cardano wallet interface:
Eternl
Nami
Flint
Yoroi
Gero
Each wallet must implement the standard WalletProvider interface to ensure consistent behavior across the application.
Key Components:
The wallet integration is organized in the src/services/wallet/ directory with the following structure:
The Wallet Provider Interface defines the contract for interacting with different Cardano wallet providers. It provides a unified way to access wallet functionality regardless of the specific provider implementation.
// src/store/wallet.ts
// The WalletApiProvider interface defines the contract for wallet integration
// It requires implementations to provide a method for retrieving a WalletApi instance
export interface WalletApiProvider {
getWalletApi(name: string): Promise<WalletApi>;
}
The supported wallet providers are defined in a registry that maps provider names to their availability:
The Lucid wallet API provider implementation handles wallet connection and API access through browser extensions:
// src/store/wallet.ts
// Implementation of the WalletApiProvider interface using Lucid
// Handles wallet connection and API access through browser extensions
export const lucidWalletApiProvider: WalletApiProvider = {
async getWalletApi(provider: string): Promise<WalletApi> {
if (!supportedProviders[provider]) {
throw new Error(`Invalid Wallet Provider: ${provider}`);
}
const context = window as any;
console.log("Cardano wallet providers");
console.log(provider);
console.log(context.cardano);
console.log(context.exodus);
let walletApi = null;
if (provider === "exodus") {
walletApi = (await context.exodus.cardano.enable()) as WalletApi;
} else if (!context.cardano || !context.cardano[provider]) {
throw new Error("cardano provider instance not found in context");
} else {
walletApi = (await context.cardano[provider].enable()) as WalletApi;
}
return walletApi;
},
};
Wallet State Management (src/store/slices/walletSlice.ts)
The Wallet State Management system handles all wallet-related state in the Redux store. Let's break down the key types and interfaces:
// src/store/slices/walletSlice.ts
// Represents a value in the Gimbalabs (GY) format
// Contains lovelace amount and optional asset class quantities
export type GYValue = {
lovelace: string;
[assetClass: string]: string;
};
// src/store/slices/walletSlice.ts
// Represents a reward account with associated payment credentials and value
export type RewardAccount = {
paymentPkh: string;
distId: number;
value: GYValue;
};
This example shows how to implement wallet event monitoring using WebSocket:
// src/components/WalletMonitor.tsx
// Example of monitoring wallet events through WebSocket
const WalletMonitor: FC = () => {
const ws = useContext(WebsocketContext);
const dispatch = useAppDispatch();
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const data = JSON.parse(event.data);
if (isJsonRpcNotif("RewardDistsView", isRewardAccounts)(data)) {
dispatch(setRewardAccounts(data.params));
}
if (data.type === "wallet_update") {
dispatch(updateWalletUtxosThunk());
}
};
ws.addEventListener("message", handleMessage);
return () => ws.removeEventListener("message", handleMessage);
}, [ws, dispatch]);
return null;
};
Error Handling
The error handling system provides custom error types and utility functions:
// src/store/slices/walletSlice.ts
// Custom error types for wallet-related errors
// Error thrown when a wallet provider is not found
class WalletAtProviderDoesNotExist extends Error {
constructor(public wallet: Wallet) {
super(`Wallet at provider ${wallet.provider} does not exist`);
}
}
// Error thrown when a wallet address is not found
class WalletAtAddressDoesNotExist extends Error {
constructor(public wallet: Wallet) {
super(`Wallet at address ${wallet.address} does not exist`);
}
}
// Error thrown when no wallet is selected
class WalletNotSelectedError extends Error {
constructor(message: string = "") {
super(`No wallet selected: ${message}`);
}
}
// Error handling utility function
const handleWalletError = (error: Error) => {
if (error instanceof WalletAtProviderDoesNotExist) {
console.error("Wallet provider not found:", error.wallet.provider);
} else if (error instanceof WalletAtAddressDoesNotExist) {
console.error("Wallet address not found:", error.wallet.address);
} else if (error instanceof WalletNotSelectedError) {
console.error("No wallet selected:", error.message);
} else {
console.error("Unexpected wallet error:", error);
}
};