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.
tssrc/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
| Adapter | Reads | Writes |
|---|---|---|
| PreviewProtocolAdapter | Configuration and deterministic quote arithmetic only | Always refused with a typed rejection |
| LiveProtocolAdapter | Marked integration boundaries per dependency | Guarded — 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";