@velora-dex/sdk
Version:
616 lines (536 loc) • 16.7 kB
text/typescript
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,
};