UNPKG

@velora-dex/sdk

Version:
616 lines (536 loc) 16.7 kB
import type { NonEmptyArray, Prettify } from 'ts-essentials'; import { Bridge, DeltaAuctionOrder, ExternalDeltaOrder, OnChainOrderType, OrderKind, ProductiveDeltaOrder, SwapSideUnion, TWAPBuyDeltaOrder, TWAPDeltaOrder, DeltaOrderUnion, UnifiedDeltaOrderData, } from './types'; import type { DeltaAuction, DeltaOrderStatus, DeltaTokenSide, DeltaTransaction, } from '../types'; /** * Delta order helpers. * * The on-chain order structs (`auction.order`) and the `onChainOrderType` union * are shared across all order families, so the order-struct guards, auction * discriminant guards, and order-level getters operate purely on those shapes. * * The *auction* envelope is the integrator-facing v2 shape: * - `status` is the integrator-facing `DeltaOrderStatus` * (PENDING/ACTIVE/COMPLETED/…), * - amounts live on `input`/`output` (`DeltaTokenSide`) and `transactions` * (`DeltaTransaction`), * - `side` is carried explicitly on the auction. */ ///// CHECKS — order structs ////// /** * @description Checks whether an order is a TWAP Sell or TWAP Buy order. */ function isTWAPOrder( order: DeltaOrderUnion ): order is TWAPDeltaOrder | TWAPBuyDeltaOrder { return 'numSlices' in order && typeof order.numSlices === 'number'; } /** * @description Checks whether an order is a TWAP Sell order. */ function isTWAPSellOrder(order: DeltaOrderUnion): order is TWAPDeltaOrder { return ( isTWAPOrder(order) && 'totalSrcAmount' in order && typeof order.totalSrcAmount === 'string' ); } /** * @description Checks whether an order is a TWAP Buy order. */ function isTWAPBuyOrder(order: DeltaOrderUnion): order is TWAPBuyDeltaOrder { return ( isTWAPOrder(order) && 'totalDestAmount' in order && typeof order.totalDestAmount === 'string' ); } /** * @description Checks whether an order is an External order. */ function isExternalOrder(order: DeltaOrderUnion): order is ExternalDeltaOrder { return 'handler' in order; } /** * @description Checks whether an order is a regular Delta auction order. */ function isDeltaOrder(order: DeltaOrderUnion): order is DeltaAuctionOrder { return ( !isExternalOrder(order) && 'kind' in order && typeof order.kind === 'number' ); } /** * @description Checks whether an order is a Productive Delta order * (strategy-routed order without an explicit OrderKind). */ function isProductiveOrder( order: DeltaOrderUnion ): order is ProductiveDeltaOrder { return 'strategy' in order && typeof order.strategy === 'string'; } ///// CHECKS — auction discriminants ////// /** * @description Checks whether an auction is a TWAP auction. */ function isTWAPAuction<T extends OnChainOrderType>(auction: { onChainOrderType: T; }): auction is { onChainOrderType: ('TWAPOrder' | 'TWAPBuyOrder') & T } { return isTWAPSellAuction(auction) || isTWAPBuyAuction(auction); } /** * @description Checks whether an auction is a TWAP Sell auction. */ function isTWAPSellAuction<T extends OnChainOrderType>(auction: { onChainOrderType: T; }): auction is { onChainOrderType: 'TWAPOrder' & T } { return auction.onChainOrderType === 'TWAPOrder'; } /** * @description Checks whether an auction is a TWAP Buy auction. */ function isTWAPBuyAuction<T extends OnChainOrderType>(auction: { onChainOrderType: T; }): auction is { onChainOrderType: 'TWAPBuyOrder' & T } { return auction.onChainOrderType === 'TWAPBuyOrder'; } /** * @description Checks whether an auction is a Delta auction. */ function isDeltaAuction<T extends OnChainOrderType>(auction: { onChainOrderType: T; }): auction is { onChainOrderType: 'Order' & T } { return auction.onChainOrderType === 'Order'; } /** * @description Checks whether an auction is an External auction. */ function isExternalAuction<T extends OnChainOrderType>(auction: { onChainOrderType: T; }): auction is { onChainOrderType: 'ExternalOrder' & T } { return auction.onChainOrderType === 'ExternalOrder'; } /** * @description Checks whether an auction is a Productive auction. */ function isProductiveAuction<T extends OnChainOrderType>(auction: { onChainOrderType: T; }): auction is { onChainOrderType: 'ProductiveOrder' & T } { return auction.onChainOrderType === 'ProductiveOrder'; } /** * @description Checks whether an auction is a Fillable auction. * `FillableOrder` is the `onChainOrderType` the server reports for a * `partiallyFillable` Standard order; it carries the same order struct as * `Order`. Consumers that don't distinguish the two should treat * `isDeltaAuction(a) || isFillableAuction(a)` as "is a standard order". */ function isFillableAuction<T extends Pick<DeltaAuction, 'onChainOrderType'>>( auction: T ): auction is T & { onChainOrderType: 'FillableOrder' } { return auction.onChainOrderType === 'FillableOrder'; } ///// CHECKS — status / execution (v2 envelope) ////// /** * @description Checks whether an auction is fully executed (settled on every chain). */ function isCompletedAuction<T extends Pick<DeltaAuction, 'status'>>( auction: T ): auction is T & { status: 'COMPLETED' } { return auction.status === 'COMPLETED'; } const failedAuctionStatuses = [ 'FAILED', 'EXPIRED', 'CANCELLED', 'REFUNDED', 'REFUNDING', ] as const; const failedAuctionStatusesSet = new Set<DeltaOrderStatus>( failedAuctionStatuses ); /** * @description Checks whether an auction is in a terminal failure state * (failed, expired, cancelled, or refunded). */ function isFailedAuction<T extends Pick<DeltaAuction, 'status'>>( auction: T ): auction is T & { status: (typeof failedAuctionStatuses)[number] } { return failedAuctionStatusesSet.has(auction.status); } /** * @description Checks whether an auction status is cancelled. */ function isCanceledAuction<T extends Pick<DeltaAuction, 'status'>>( auction: T ): auction is T & { status: 'CANCELLED' } { return auction.status === 'CANCELLED'; } /** * @description Checks whether an auction status is expired. */ function isExpiredAuction<T extends Pick<DeltaAuction, 'status'>>( auction: T ): auction is T & { status: 'EXPIRED' } { return auction.status === 'EXPIRED'; } const pendingAuctionStatuses = [ 'PENDING', 'AWAITING_SIGNATURE', 'ACTIVE', 'BRIDGING', ] as const; const pendingAuctionStatusesSet = new Set<DeltaOrderStatus>( pendingAuctionStatuses ); /** * @description Checks whether an auction is still in flight (not yet settled * and not failed): awaiting signature, pending, actively executing, or bridging. */ function isPendingAuction<T extends Pick<DeltaAuction, 'status'>>( auction: T ): auction is T & { status: (typeof pendingAuctionStatuses)[number] } { return pendingAuctionStatusesSet.has(auction.status); } /** * @description Checks whether an auction has been partially executed: * it has at least one transaction and an overall filled percent strictly * between 0 and 100. */ function isPartiallyExecutedAuction< T extends Pick<DeltaAuction, 'order' | 'transactions'> >( auction: T ): auction is T & { transactions: NonEmptyArray<DeltaTransaction> } { if (auction.transactions.length === 0) return false; const filledPercent = getFilledPercent(auction); return filledPercent > 0 && filledPercent < 100; } /** * @description Checks whether an order includes valid cross-chain bridge details. */ function isOrderCrosschain<T extends { bridge?: Bridge } | object>( order: T ): order is Prettify<Extract<T, { bridge?: Bridge }> & { bridge: Bridge }> { return ( 'bridge' in order && !!order.bridge && order.bridge.destinationChainId !== 0 ); } const checks = { // order-struct guards isTWAPOrder, isTWAPSellOrder, isTWAPBuyOrder, isExternalOrder, isDeltaOrder, isProductiveOrder, isOrderCrosschain, // auction discriminant guards isTWAPAuction, isTWAPSellAuction, isTWAPBuyAuction, isDeltaAuction, isExternalAuction, isProductiveAuction, isFillableAuction, // status / execution guards (v2 envelope) isCompletedAuction, isFailedAuction, isCanceledAuction, isExpiredAuction, isPendingAuction, isPartiallyExecutedAuction, }; ///// GETTERS — order structs ////// const OrderKindToSwapSide = { [OrderKind.Sell]: 'SELL', [OrderKind.Buy]: 'BUY', } as const; /** * @description Returns swap side from a Delta or External order kind. */ function getSwapSideFromDeltaOrder( order: DeltaAuctionOrder | ExternalDeltaOrder ): SwapSideUnion { return OrderKindToSwapSide[order.kind]; } const TwapTypeToSwapSide = { TWAPOrder: 'SELL', TWAPBuyOrder: 'BUY', } as const; /** * @description Returns swap side from TWAP on-chain order type. */ function getSwapSideFromTwapOrderType( onChainOrderType: 'TWAPOrder' | 'TWAPBuyOrder' ): SwapSideUnion { return TwapTypeToSwapSide[onChainOrderType]; } /** * @description Returns source and destination token addresses for an order. */ function getOrderTokenAddresses(order: DeltaAuction['order']) { const srcToken = order.srcToken; const destToken = isOrderCrosschain(order) ? order.bridge.outputToken : order.destToken; return { srcToken, destToken, }; } /** * @description Returns the expected source amount for a TWAP order. */ function getExpectedTwapSrcAmount( order: | Pick<TWAPDeltaOrder, 'totalSrcAmount'> | Pick<TWAPBuyDeltaOrder, 'maxSrcAmount'> ) { if ('totalSrcAmount' in order) { // SELL return order.totalSrcAmount; } return order.maxSrcAmount; // BUY } /** * @description Returns the expected destination amount for a TWAP order. */ function getExpectedTwapDestAmount( order: | Pick<TWAPDeltaOrder, 'destAmountPerSlice' | 'numSlices' | 'bridge'> | Pick<TWAPBuyDeltaOrder, 'totalDestAmount' | 'bridge'> ) { const destAmount = 'destAmountPerSlice' in order ? BigInt(order.destAmountPerSlice) * BigInt(order.numSlices) // SELL : BigInt(order.totalDestAmount); // BUY if (isOrderCrosschain(order)) { return scaleByFactor(destAmount, order.bridge.scalingFactor).toString(); } return destAmount.toString(); } /** * @description Returns expected source and destination amounts for a TWAP order. */ function getExpectedTwapOrderAmounts( order: TWAPDeltaOrder | TWAPBuyDeltaOrder ) { const srcAmount = getExpectedTwapSrcAmount(order); const destAmount = getExpectedTwapDestAmount(order); return { srcAmount, destAmount }; } function scaleByFactor(amount: bigint, scalingFactor = 0): bigint { if (!amount) return 0n; if (scalingFactor === 0) return amount; const base = 10n; return scalingFactor < 0 ? amount / base ** BigInt(-scalingFactor) : amount * base ** BigInt(scalingFactor); } ///// GETTERS — auction envelope (v2) ////// /** * @description Reads an amount off a v2 token side. A SELL input / BUY output * carries an explicit `amount`; the opposite side carries * `expectedAmount`/`executedAmount`. `prefer` chooses which to read on the * expected/executed variant. */ function getTokenSideAmount( side: DeltaTokenSide, prefer: 'expected' | 'executed' ): string { if ('amount' in side) return side.amount; const value = prefer === 'executed' ? side.executedAmount : side.expectedAmount; return value ?? '0'; } /** * @description Returns the source chain id for the auction (the input side's chain). */ function getAuctionSrcChainId(auction: Pick<DeltaAuction, 'input'>): number { return auction.input.chainId; } /** * @description Returns the destination chain id for the auction (the output side's chain). * Equals the source chain id for same-chain orders. */ function getAuctionDestChainId(auction: Pick<DeltaAuction, 'output'>): number { return auction.output.chainId; } /** * @description Returns the swap side for any auction. The auction carries `side` * directly, so no order introspection is needed. */ function getAuctionSwapSide( auction: Pick<DeltaAuction, 'side'> ): SwapSideUnion { return auction.side; } /** * @description Returns source and destination token addresses for the auction, * read from the input/output sides (already resolved to the dest-chain token * for cross-chain orders). */ function getAuctionTokenAddresses( auction: Pick<DeltaAuction, 'input' | 'output'> ) { return { srcToken: auction.input.token, destToken: auction.output.token, }; } /** * @description Aggregates transaction amounts into total spent (src) and * received (dest) values. */ function getTransactionAmounts(transactions: DeltaTransaction[]) { const { srcAmount, destAmount } = transactions.reduce( (acc, { spentAmount, receivedAmount }) => ({ srcAmount: acc.srcAmount + BigInt(spentAmount ?? 0), destAmount: acc.destAmount + BigInt(receivedAmount ?? 0), }), { srcAmount: 0n, destAmount: 0n } ); return { srcAmount: srcAmount.toString(), destAmount: destAmount.toString(), }; } /** * @description Calculates the overall filled percent (0–100) from the * per-transaction `filledPercent` values. For cross-chain orders, * only transactions with a `destinationTx` are counted towards the filled percent. */ function getFilledPercent({ order, transactions, }: Pick<DeltaAuction, 'order' | 'transactions'>): number { if (transactions.length === 0) return 0; const completedTransactions = !isOrderCrosschain(order) ? transactions : transactions.filter((transaction) => !!transaction.destinationTx); const total = completedTransactions.reduce( (acc, { filledPercent }) => acc + filledPercent, 0 ); return total; } /** * @description Returns the executed amount of a token side when present, * otherwise the provided fallback (typically summed from transactions). */ function getExecutedAmount(side: DeltaTokenSide, fallback: string): string { if ('executedAmount' in side && side.executedAmount != null) { return side.executedAmount; } return fallback; } /** * @description Returns expected and minimal amounts and, once the auction is completed, * executed amounts. Executed amounts prefer the `executedAmount` baked onto the * token sides and fall back to summing transactions. */ function getAuctionAmounts( auction: Pick< DeltaAuction, 'status' | 'order' | 'input' | 'output' | 'transactions' > ) { const expected = { srcAmount: getTokenSideAmount(auction.input, 'expected'), destAmount: getTokenSideAmount(auction.output, 'expected'), }; const order = auction.order; let minimal; if (isTWAPOrder(order)) { minimal = expected; // TWAP doesn't carry explicit min amounts } else if (isOrderCrosschain(order)) { minimal = { srcAmount: order.srcAmount, destAmount: scaleByFactor( BigInt(order.destAmount), order.bridge.scalingFactor ).toString(), }; } else { minimal = { srcAmount: order.srcAmount, destAmount: order.destAmount, }; } if (!isCompletedAuction(auction)) { return { expected, minimal }; } const txAmounts = getTransactionAmounts(auction.transactions); const executed = { srcAmount: getExecutedAmount(auction.input, txAmounts.srcAmount), destAmount: getExecutedAmount(auction.output, txAmounts.destAmount), }; return { expected, executed, minimal }; } /** * @description Returns unified order data with normalized amounts, tokens, * chain ids, and side, built from the auction envelope. */ function getUnifiedDeltaOrderData( auction: DeltaAuction ): UnifiedDeltaOrderData { const { srcToken, destToken } = getAuctionTokenAddresses(auction); const { expected, executed, minimal } = getAuctionAmounts(auction); const srcChainId = getAuctionSrcChainId(auction); const destChainId = getAuctionDestChainId(auction); const swapSide = getAuctionSwapSide(auction); const filledPercent = getFilledPercent(auction); return { srcChainId, destChainId, srcAmount: executed?.srcAmount || expected.srcAmount, destAmount: executed?.destAmount || expected.destAmount, amounts: { minimal, expected, final: executed, }, srcToken, destToken, swapSide, filledPercent, }; } const getters = { getUnifiedDeltaOrderData, // auction-level getters — v2 envelope shape. getAuctionTokenAddresses, getAuctionSrcChainId, getAuctionDestChainId, getAuctionSwapSide, getTransactionAmounts, getAuctionAmounts, getFilledPercent, // order-level getters — order structs shared across families. getOrderTokenAddresses, getSwapSideFromDeltaOrder, getSwapSideFromTwapOrderType, getExpectedTwapSrcAmount, getExpectedTwapDestAmount, getExpectedTwapOrderAmounts, }; export const OrderHelpers = { checks, getters, };