SPB Git

Repository Public

types.ts 6.2 KB 124 lines typescript

1// SP Universal Swap Router — shared contract. Every provider adapter, the aggregator, the graph, the optimizer,2// the API and the UI speak these types. Amounts are bigint base units end to end; nothing here is ever a float3// except fiat estimates, which are display/ranking aids and never feed a transaction.4import { getAsset, getNetwork, type AssetDef, type NetworkDef } from '@spw/core';56/** An asset is identified by network + contract/mint ('native' for the chain coin). A symbol is never an identifier. */7export interface AssetRef { network: string; asset: string }8export const assetKey = (a: AssetRef): string => `${a.network}:${a.asset === 'native' ? 'native' : getNetwork(a.network).family === 'evm' ? a.asset.toLowerCase() : a.asset}`;9export const sameAsset = (a: AssetRef, b: AssetRef) => assetKey(a) === assetKey(b);10/** Resolve against the wallet's verified registry; throws for anything not allowlisted. */11export function resolveAsset(a: AssetRef): { net: NetworkDef; def: AssetDef } { return { net: getNetwork(a.network), def: getAsset(a.network, a.asset) }; }1213export type RoutingMode = 'best_price' | 'fastest' | 'balanced';1415export interface SwapQuoteRequest {16  from: AssetRef; to: AssetRef;17  /** exact input, base units of `from` */18  amountIn: bigint;19  /** the wallet's address on the source network (pays) and on the destination network (receives) */20  sender: string; recipient: string;21  /** maximum tolerated slippage, basis points (50 = 0.5 %) */22  slippageBps: number;23}2425export type StepKind = 'swap' | 'bridge' | 'native_swap' | 'wrap' | 'unwrap';26export interface RouteStep {27  kind: StepKind; provider: string;28  from: AssetRef; to: AssetRef;29  /** the venue as the provider names it (pool, DEX, bridge), for display only */30  via: string[];31  /** contracts / programs / vault addresses this step would interact with, on `network` */32  contracts: { network: string; address: string; role: 'router' | 'spender' | 'vault' | 'bridge' | 'program' }[];33}3435export type FeeKind = 'network' | 'protocol' | 'provider' | 'bridge' | 'affiliate';36export interface FeeItem {37  kind: FeeKind; asset: AssetRef; amount: bigint;38  /** true when `amountOut` is already net of this fee (so it must not be subtracted again) */39  includedInOutput: boolean;40  /** true when the figure is an estimate (e.g. gas) rather than a fixed charge */41  estimated: boolean;42}4344/** How far the wallet can take this quote today. Reported honestly per quote. */45export type Executability =46  /** the isolated signer can rebuild/verify every value-moving field itself */47  | { class: 'verified' }48  /** provider-built calldata/instructions the signer can only constrain structurally (allowlisted contract, exact allowance) */49  | { class: 'opaque'; reason: string }50  /** price discovery only: the wallet cannot execute this route */51  | { class: 'quote_only'; reason: string };5253export type RiskLevel = 'low' | 'medium' | 'high';54export interface RiskFlag { code: string; level: RiskLevel; detail?: string }55export interface RiskAssessment { level: RiskLevel; score: number; flags: RiskFlag[] }5657export interface SwapQuote {58  /** unique per quote response */59  id: string;60  provider: string;61  request: SwapQuoteRequest;62  steps: RouteStep[];63  /** expected output and the minimum the route guarantees after slippage, base units of `to` */64  amountOut: bigint; minAmountOut: bigint;65  fees: FeeItem[];66  /** provider's own estimate; null when it gives none (never invented) */67  estimatedSeconds: number | null;68  priceImpactBps: number | null;69  /** unix ms after which this quote must not be used to build a transaction */70  expiresAt: number;71  /** indicative = fast price discovery; firm = fetched for execution with the real addresses */72  firmness: 'indicative' | 'firm';73  executability: Executability;74  /** can funds come back to the sender if the destination leg fails */75  refundable: boolean | null;76  /** not refundable, but never lost either: if the destination leg stalls, the funds stay claimable there (e.g. a CCTP burn awaiting its mint) */77  recoverable?: boolean;78  /** single transaction that either fully succeeds or fully reverts */79  atomic: boolean;80  /** opaque provider payload needed to build the transaction later; never shown, never trusted */81  raw?: unknown;82}8384export interface ProviderCapabilities {85  /** network ids (from the wallet registry) this provider can act on */86  networks: string[];87  crossChain: boolean;88  /** true when the provider needs an API key that is not configured: it is skipped and reported as such */89  needsKey: boolean; configured: boolean;90  /** steps this provider can produce */91  kinds: StepKind[];92  docs: string;93}9495export interface ProviderContext {96  /** abort when the caller's deadline budget is exhausted */97  signal: AbortSignal;98  /** schema-validating HTTP helper with keep-alive; adapters must not call fetch directly */99  http: HttpClient;100  now: () => number;101}102export interface HttpClient {103  getJson(url: string, init?: { headers?: Record<string, string>; signal?: AbortSignal }): Promise<unknown>;104  postJson(url: string, body: unknown, init?: { headers?: Record<string, string>; signal?: AbortSignal }): Promise<unknown>;105}106107/** What a provider adapter must implement. Unsupported behaviour is omitted, never faked. */108export interface SwapProvider {109  id: string; name: string;110  /** provider-specific runtime limits (rate, timeout) that override the defaults, e.g. a keyless tier's quota */111  policy?: { timeoutMs?: number; ratePerSec?: number; burst?: number;112    /** tight request quota: only asked for direct cross-chain requests, never for the legs of a composed route or same-chain pairs */113    scarce?: boolean };114  capabilities(): ProviderCapabilities;115  /** cheap, local pre-filter (no network): can this provider possibly serve the pair? */116  supportsPair(from: AssetRef, to: AssetRef): boolean;117  /** real quotes from the provider's API, strictly schema-validated. Empty array = no route. Throws on provider failure. */118  quote(req: SwapQuoteRequest, ctx: ProviderContext): Promise<SwapQuote[]>;119}120121export class ProviderError extends Error {122  constructor(public provider: string, public code: 'timeout' | 'http' | 'schema' | 'rate_limited' | 'unavailable' | 'bad_response', message: string) { super(message); }123}124
Files main
Menu

Theme