Enter app

Protocol Adapter

The single seam between the interface and the chain.

No component in the interface talks to a chain, an RPC or an indexer. Everything goes through one interface, which is what makes this build the final frontend rather than a mockup.

ts
src/lib/protocol/types.ts
interface ProtocolAdapter {
  getProtocolStatus(): Promise<ProtocolStatusReport>;
  getVaultStats(): Promise<VaultStats>;
  getVaultHoldings(): Promise<readonly VaultHolding[]>;
  getTargetBasket(): Promise<TargetBasket>;
  getSeries(id: string): Promise<Series>;
  getActivity(query?: ActivityQuery): Promise<ActivityPage>;
  getUserBalances(address: string | null): Promise<UserBalances>;

  quoteSwap(req): Promise<SwapQuote>;
  quoteWrap(req): Promise<WrapQuote>;
  quoteUnwrap(req): Promise<UnwrapQuote>;
  quoteRedeem(req): Promise<RedeemQuote>;
  quoteBorrow(req): Promise<BorrowQuote>;

  swap(req): Promise<TxResult>;
  wrap(req): Promise<TxResult>;
  unwrap(req): Promise<TxResult>;
  redeem(req): Promise<TxResult>;
  borrow(req): Promise<TxResult>;
}

Two implementations

AdapterReadsWrites
PreviewProtocolAdapterConfiguration and deterministic quote arithmetic onlyAlways refused with a typed rejection
LiveProtocolAdapterMarked integration boundaries per dependencyGuarded — refuses unless contracts exist on Robinhood Chain

The data envelope

Every value is wrapped in a `DataValue<T>` that is either available with a value or unavailable with a reason. There is no third state, so a component cannot accidentally render a placeholder as a figure.

ts
type DataValue<T> =
  | { available: true; value: T }
  | { available: false; reason: UnavailableReason };

type UnavailableReason =
  | "NOT_DEPLOYED"
  | "AWAITING_INDEXER"
  | "AWAITING_MARKET"
  | "NO_ACTIVITY_YET"
  | "PHASE_II"
  | "WALLET_DISCONNECTED"
  | "TBD";