Optim Finance
  • INTRODUCTION
    • Introduction
    • Roadmap
  • OADA
    • Overview
    • OADA 🟣 and sOADA 🟢
    • Flow of Funds
    • User Guides
      • Passive yield with sOADA
      • Epoch Stake Auction
    • AMOs
      • Splash DEX AMO
      • Stake Auction AMO
      • Staking AMO
    • UNHCR Donation Module
      • Automated Yield Donation Protocol
      • NFT Impact Certificate
      • Integration with the OADA Ecosystem
      • Humanitarian Partnership
      • Future Extensions
    • Governance
    • Resources
  • OTOKEN Framework
    • Introduction
      • Key Benefits
      • Who is it for?
      • Inspirations & Applications
    • Framework
      • Key Concepts
      • Use Cases
      • OTOKENs
    • Core Concepts
      • OTOKEN and sOTOKEN
      • Algorithmic Market Operations (AMOs)
      • Balancing Stability, Yield, and Adaptability
    • System Architecture
      • OTOKEN Policy
      • Staking AMO
      • Collateral Management AMO
    • Extensions & Other Modules
      • DEX AMO (Liquidity & Peg Stability)
      • Stake Auction AMO
      • Borrowing & Lending AMOs
      • Other AMOs & Opportunities
    • Multiple OTOKEN Deployments
      • Ecosystem Synergy
      • Not Just Synthetic Assets
    • Vision
      • Key Pillars of the OToken Framework
      • Future Directions & Opportunities
      • An Invitation to Innovate
    • Bug Bounty Program
  • LIQUIDITY BONDS
    • Overview
    • Bond App FAQ
    • Use Cases
      • ISPO Bonds
      • SPO Bonds
    • Bond Architecture
      • Validators
      • High Level Workflow
      • Scripts Technical
      • Transaction Flow
      • Pooled Loans
    • Guides for SPOs
      • Bond Creation
      • Bond Sales
      • SPO Bond Issue Summary
      • Bond Verification
    • Liquidity Bonds Audit
  • OUSD
    • OUSD Reserves
      • Reserve Criteria
        • Stability and Reputation
        • Compliance
        • Smart Contract Security
    • Ongoing Reserves Management
      • Reserve Asset Valuation Calculation
      • Dynamic Reserve Asset Adjustment Metrics
        • Dynamic Reserves Adjustment
    • Yield, Staking, and Flow of Funds
      • Yield Modules
        • OUSD DEX AMO
        • Future Modules (v2)
      • Staking AMO
      • sOUSD Redemption Mechanism
    • Peg Protection
      • Market Depth and Liquidity
    • Governance and Risk Framework
      • Risk Capital Requirements
      • First-Loss Capital Structure
      • Asset Allocation Framework
        • Static Governance Parameters
        • Dynamic Allocation System
    • Financial Engineering Audit
  • Leviathan
    • System Architecture
      • Background
      • Concurrency Limitations
      • Complexity in Transaction and Contract Management
    • Core Concepts
      • Deterministic Transaction
        • Guaranteed Transaction
      • Instant Finality
        • Liveness and Safety
        • Probabilistic Finality vs Instant Finality
      • Account Abstraction
        • Concept of Account Abstraction
        • Technical Implementation
        • Security and Operational Implications
      • Intent Based Transactions
        • The Infrastructure and Process of IBTs
        • Declarative Constraints in IBTs
      • Layer 2
        • Types of Layer 2 Solutions
      • Sequencers
        • Core Functions of Sequencers
        • Role in Layer 2 Rollups
        • Challenges
    • System Components
      • Understanding the System Components
      • Optim-Account (Intents to enable tx chain)
        • User Interaction and Intent Submission
        • Intent Structuring and Authentication
        • Smart Contract Functionalities and Operational Parameters
        • The Necessity of an Account-Based Framework
        • Account Abstraction and Its Role in Leviathan
      • Leviathan Sequencer System (tx chain building)
        • The Role of the Leviathan Sequencer System in Conjunction with The Optim Account
        • Sequencing and Ordering of Transactions
        • The Role of Time in the System
        • The Pragmatic Leviathan: Dealing with Potential Changes
      • The Role of OADA in the Leviathan System
        • Operational Simplification of Staking Mechanisms via OADA Integration
        • Facilitating Time Dilation and Composability
    • Processes
      • Entering Leviathan
      • Transaction Execution
      • Leaving Leviathan
    • High Level Overview
      • System Design
        • Account Abstraction Functionality
        • Guaranteed Transactions
        • Instant Finality
        • Unbreakable Transaction Chaining
        • Layer 2 Execution Environment
        • Future Sequencer Network
      • System Context
        • Limitations of current transactions chaining paradigm
        • Limitations of current inter dApp composability issues
        • Explanation of basic design and non-custodial asset inputs
        • Intent Based Transactions
        • Account Base vs eUTxO model app architecture
      • Theoretical Applications
  • GOVERNANCE
    • Governance Overview
      • Proposal Temp Check
      • Governance Proposal
        • On/Off Chain Mechanics
      • ODAO
    • Tokenomics
      • Categories
      • Vesting
    • Optim DAO Wallets
    • Protocol Profits
  • GUIDES
    • Transaction Chaining
      • Background
      • Overview
      • Pool Transaction Chaining
    • OPTIMiz Conversion
  • ODAO Stack
    • Introduction
    • Design Principles
    • Why Optim DAO Stack?
      • Current Limitations
      • ODAO Solutions
    • Key Features
      • Snapshot Voting
      • Treasury Management
      • Proposal Execution
    • System Architecture
      • Modular Framework
      • On-Chain Logic
      • Off-Chain Operations
      • User Interfaces
    • Core Modules
      • Snapshot Voting Module
      • Treasury Management Module
      • Proposal Execution Module
    • Future Roadmap
      • Potential Future Enhancements
      • Long Term Vision
  • OADA UI
    • Setup
      • Installation
      • Development Workflow
      • Troubleshooting
      • Development Tips
      • Open Source Contributions
      • FAQ
    • Key Functionalities
      • Wallet Integration
      • Dashboard
      • Transaction Management
        • UTxO Management
        • Transaction Creation and Conversion
        • Transaction Monitoring
      • Real-time Updates
        • Portfolio Value Tracking
        • Transaction Status Monitoring
    • OADA Smart Contract API
      • Minting OADA
      • Staking OADA
      • Unstaking sOADA
      • Epoch Stake Auction
        • Bid Calculation Functions
        • Auction Actions
        • Bid Form Component
        • Auction Dashboard
    • Tutorials
      • Environment Setup and Installation
      • Understanding the Project Structure
      • Basic Configuration and Customization
      • Working with Components
      • State Management and Data Flow
      • Wallet Integration and State Management
      • Smart Contract Integration
      • Advanced UI Customization
      • Testing and Quality Assurance
Powered by GitBook
On this page
  • Overview
  • Supported Wallets
  • Key Components:
  • Key functionalities include:
  • Wallet Provider Interface (src/store/wallet.ts)
  • Wallet State Management (src/store/slices/walletSlice.ts)
  • WebSocket Integration (src/websocket.tsx)
  • Wallet Connection
  • UTxO Management
  • Usage Examples
  • Connecting a Wallet
  • Monitoring Wallet Events
  • Error Handling
  • Best Practices
  1. OADA UI
  2. Key Functionalities

Wallet Integration

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:

src/services/wallet/
├── providers/          # Wallet provider implementations
├── types/              # TypeScript type definitions
├── utils/              # Utility functions
├── hooks/              # React hooks for wallet integration
└── index.ts            # Main export file

Key functionalities include:

  • Wallet connection management

  • UTXO (Unspent Transaction Output) handling

  • Transaction signing and submission

  • Network switching

  • Address validation

  • Balance tracking

  • Transaction history

  • Error handling

Wallet Provider Interface (src/store/wallet.ts)

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:

// src/store/wallet.ts
// Registry of supported wallet providers
// Each entry indicates whether a specific wallet implementation is supported
const supportedProviders: { [key: string]: boolean } = {
  nami: true,
  flint: true,
  yoroi: true,
  gerowallet: true,
  eternl: true,
  typhoncip30: true,
  LodeWallet: true,
  exodus: true,
  vespr: true,
  lace: true,
  nufi: true,
};

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;
};
// src/store/slices/walletSlice.ts
// Interface defining the wallet state structure
interface WalletState {
  wallet: UITypes.Wallets.Wallet | null;
  partialWallet: St.PartialWallet;
  showWalletSelect: boolean;
  lastTxId?: string;
  feeAddress: string | null;
  rewardAccounts: RewardAccount[];
}

The initial state for the wallet slice:

// src/store/slices/walletSlice.ts
// Initial state for the wallet slice
const initialState: WalletState = {
  wallet: null,
  partialWallet: {
    utxos: [],
  },
  showWalletSelect: false,
  lastTxId: undefined,
  feeAddress: null,
  rewardAccounts: [],
};

Utility functions for value conversion:

// src/store/slices/walletSlice.ts
// Converts a server-side value format to Lucid's asset format
export const serverValueToLucid = (value: Value): Lucid.Assets => {
  const o: Lucid.Assets = {
    lovelace: value.lovelace,
  };
  for (const [k, v] of Object.entries(value.assets)) {
    const keyWithoutSep = k.replace(".", "");
    o[keyWithoutSep] = BigInt(v);
  }
  return o;
};
// src/store/slices/walletSlice.ts
// Converts a server-side UTxO to Lucid's UTxO format
const serverUtxoToLucid = (serverUtxo: Utxo): Lucid.UTxO => {
  return {
    txHash: serverUtxo.utxoRef.txHash,
    outputIndex: serverUtxo.utxoRef.outputIndex,
    assets: serverValueToLucid(serverUtxo.value),
    address: serverUtxo.address,
  };
};

UTxO management functions:

// src/store/slices/walletSlice.ts
// Type guard for non-null values
const isNotNull = <A>(a: A | null): a is A => {
  return a !== null;
};
// src/store/slices/walletSlice.ts
// Structure representing wallet UTxOs including collateral
type WalletUtxos = {
  utxos: LucidUtxo[];
  collateralUtxos: LucidUtxo[];
};
// src/store/slices/walletSlice.ts
// Retrieves UTxOs for a given wallet, including collateral UTxOs
export const getUtxos = async (storeWallet: Wallet): Promise<WalletUtxos> => {
  const cip30Api =
    walletApiByProviderByAddress?.[storeWallet.provider]?.[storeWallet.address];
  const getCollateral =
    cip30Api?.getCollateral === undefined
      ? cip30Api?.experimental?.getCollateral === undefined
        ? async () => []
        : cip30Api.experimental.getCollateral
      : cip30Api.getCollateral;

  const getCollateralResult = await getCollateral();
  const collateral =
    getCollateralResult === null || getCollateralResult === undefined
      ? []
      : getCollateralResult;

  const collateralUtxos = filter<LucidUtxo>(isNotNull)(
    collateral.map(L.cborToUtxo)
  );

  console.log(`COLLAT:`);
  console.log(collateralUtxos);
  const utxos = await lucid.wallet.getUtxos();
  const utxoRefToUtxoMap = utxos.reduce((hasSeens, utxo) => {
    const utxoRef = `${utxo.txHash}#${utxo.outputIndex}`;
    const hasSeen = hasSeens[utxoRef] !== undefined;
    if (!hasSeen) {
      hasSeens[utxoRef] = utxo;
    }
    return hasSeens;
  }, {} as { [utxoId: string]: LucidUtxo });
  const uniqueUtxos = Object.values(utxoRefToUtxoMap);

  return {
    utxos: uniqueUtxos,
    collateralUtxos,
  };
};

The Redux slice for wallet state management:

// src/store/slices/walletSlice.ts
// Wallet management slice for Redux store
export const walletSlice = createSlice({
  name: "wallet",
  initialState,
  reducers: {
    setWalletFeeAddress: (state, action: PayloadAction<string | null>) => {
      state.feeAddress = action.payload;
    },
    toggleShowWalletSelect: (state) => {
      state.showWalletSelect = !state.showWalletSelect;
    },
    setRewardAccounts: (state, action: PayloadAction<RewardAccount[]>) => {
      state.rewardAccounts = action.payload;
      console.log("setRewardAccounts fulfilled");
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(updateWalletUtxosThunk.fulfilled, (state, action) => {
        if (state.wallet === null || action.payload === null) {
          state.partialWallet = { utxos: [] };
          return;
        }
        const utxos = action.payload;

        const utxoRefs = new Set(utxos.map(utxoToUtxoRefString));
        if (
          !(
            state.partialWallet.utxos.every((u) =>
              utxoRefs.has(utxoToUtxoRefString(u))
            ) && state.partialWallet.utxos.length === utxoRefs.size
          )
        ) {
          console.log("updateWalletUtxosThunk changed utxos");
          state.partialWallet = { utxos };
        }
        console.log("updateWalletUtxosThunk fulfilled");
      })
      .addCase(setWalletByProvider.fulfilled, (state, action) => {
        state.wallet = action.payload;
        state.showWalletSelect = false;
        console.log("setWalletByProvider fulfilled");
      })
      .addCase(disconnectWalletThunk.fulfilled, (state, _action) => {
        if (state.wallet !== null) {
          state.wallet = null;
          state.partialWallet.utxos = [];
        }
      });
  },
});

export const {
  setWalletFeeAddress,
  toggleShowWalletSelect,
  setRewardAccounts,
} = walletSlice.actions;
export default walletSlice.reducer;

WebSocket Integration (src/websocket.tsx)

The WebSocket Integration provides real-time communication with the backend. Let's break down the key components:

// src/websocket.tsx
// WebSocket context type for sharing WebSocket instance
type WebsocketContextType = WebSocket;

// React context for WebSocket instance
export const WebsocketContext = createContext<WebsocketContextType | null>(
  null
);
// src/websocket.tsx
// WebSocket Provider Component
// Manages WebSocket connection and provides it to child components
const WebsocketProvider: FC<{ children: ReactNode }> = ({ children }) => {
  const wallet = useAppSelector(selectWallet);
  const url = `${wsUrl}`;
  const ws = new WebSocket(url);

  const dispatch = useAppDispatch();
  const [reconnectToggle, setReconnectToggle] = useState<boolean>(false);

  console.log("WebsocketProvider");
  console.log(url);

  ws.addEventListener("open", (event) => {
    console.log("WebsocketOpen");
    console.log(event);
    if (wallet !== null) {
      sendWalletConnectWsNotif(ws, wallet.address);
    }
    const timer = setInterval(function () {
      console.log("WebsocketPing");
      if (ws.readyState === WebSocket.OPEN) {
        ws.send("ping");
      } else {
        clearInterval(timer);
        console.log(`WebsocketPingCleared: ${timer}`);
      }
    }, 30000);
  });

  ws.addEventListener("close", (event) => {
    console.log("WebsocketClose");
    console.log(event);
    setTimeout(function () {
      setReconnectToggle(!reconnectToggle);
    }, 5000);
  });

  ws.addEventListener("error", (event) => {
    const data = event;
    console.log("WebsocketError: ");
    console.log(data);
    setTimeout(function () {
      setReconnectToggle(!reconnectToggle);
    }, 5000);
  });

  ws.addEventListener("message", (event) => {
    const data = event.data;
    console.log("WebsocketMessage: ");
    console.log(data);
    if (data === "pong") {
      return;
    }
    const o = JSON.parse(data);
    if (!isJsonRpcNotif("RewardDistsView", isRewardAccounts)(o)) {
      console.error(`WebsocketMessage: not a valid json rpc message: ${data}`);
      return;
    }
    if (o.params !== undefined) {
      dispatch(setRewardAccounts(o.params));
    }
  });

  return (
    <WebsocketContext.Provider value={ws}>{children}</WebsocketContext.Provider>
  );
};

export default WebsocketProvider;

Wallet Connection

The wallet connection functions handle the process of connecting and disconnecting wallets:

// src/store/slices/walletSlice.ts
// Initialize wallet connection with a specific provider
const setWalletByProvider = createAsyncThunk(
  "wallet/setWalletByProvider",
  async ({ name, ws }: SetWalletByProviderParams, thunkAPI) => {
    const walletApi = await lucidWalletApiProvider.getWalletApi(name);
    const address = await walletApi.getUsedAddresses();
    const wallet: UITypes.Wallets.Wallet = {
      provider: name,
      address: address[0],
      api: walletApi,
    };
    sendWalletConnectWsNotif(ws, wallet.address);
    return wallet;
  }
);
// src/store/slices/walletSlice.ts
// Disconnect the currently connected wallet
const disconnectWalletThunk = createAsyncThunk(
  "wallet/disconnectWallet",
  async ({ ws }: DisconnectWalletParams, thunkAPI) => {
    const state = thunkAPI.getState() as RootState;
    const wallet = selectWallet(state);
    if (wallet !== null) {
      await wallet.api.disconnect();
    }
  }
);

UTxO Management

The UTxO management functions handle the retrieval and updating of unspent transaction outputs:

// src/store/slices/walletSlice.ts
// Update wallet UTxOs in the store
export const updateWalletUtxosThunk = createAsyncThunk(
  "wallet/updateWalletUtxos",
  async (_, thunkAPI) => {
    const state = thunkAPI.getState() as RootState;
    const wallet = selectWallet(state);
    if (wallet === null) {
      return null;
    }
    const { utxos } = await getUtxos(wallet);
    return utxos;
  }
);

Usage Examples

Connecting a Wallet

This example demonstrates how to implement wallet connection in a React component:

// src/components/WalletConnect.tsx
// Example of connecting a wallet in a React component
const WalletConnect: FC = () => {
  const dispatch = useAppDispatch();
  const ws = useContext(WebsocketContext);

  const connectWallet = async () => {
    try {
      await dispatch(setWalletByProvider({ name: "nami", ws }));

      const wallet = useAppSelector(selectWallet);
      const utxos = useAppSelector(selectPartialWalletUtxos);

      console.log("Connected wallet:", wallet);
      console.log("Wallet UTxOs:", utxos);
    } catch (error) {
      console.error("Failed to connect wallet:", error);
    }
  };

  return <button onClick={connectWallet}>Connect Wallet</button>;
};

Monitoring Wallet Events

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);
  }
};

Best Practices

  1. Error Handling

    • Always wrap wallet operations in try-catch blocks

    • Provide clear error messages to users

    • Implement proper error recovery mechanisms

    • Log errors for debugging

  2. State Management

    • Use Redux for global wallet state

    • Implement proper loading states

    • Handle disconnection gracefully

    • Cache wallet data appropriately

  3. Security

    • Validate all user inputs

    • Verify transaction parameters

    • Implement proper timeout handling

    • Use secure storage for sensitive data

  4. Performance

    • Optimize UTXO selection

    • Cache wallet data

    • Implement proper cleanup

    • Handle network changes efficiently

  5. User Experience

    • Provide clear feedback

    • Show loading states

    • Handle edge cases gracefully

    • Implement proper validation

PreviousKey FunctionalitiesNextDashboard

Last updated 1 month ago