@velora-dex/sdk 9.5.1 → 9.5.2-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,648 @@
1
+ import type { NonEmptyArray, Prettify } from 'ts-essentials';
2
+ import {
3
+ Bridge,
4
+ DeltaAuction,
5
+ DeltaAuctionOrder,
6
+ DeltaAuctionStatus,
7
+ DeltaAuctionTransaction,
8
+ DeltaAuctionTWAP,
9
+ DeltaAuctionTWAPBuy,
10
+ DeltaAuctionUnion,
11
+ DeltaOrderUnion,
12
+ ExternalDeltaOrder,
13
+ OnChainOrderType,
14
+ OrderKind,
15
+ SwapSideUnion,
16
+ TWAPBuyDeltaOrder,
17
+ TWAPDeltaOrder,
18
+ UnifiedDeltaOrderData,
19
+ } from './types';
20
+
21
+ ///// CHECKS //////
22
+
23
+ /**
24
+ * @description Checks whether an order is a TWAP Sell or TWAP Buy order.
25
+ */
26
+ function isTWAPOrder(
27
+ order: DeltaOrderUnion
28
+ ): order is TWAPDeltaOrder | TWAPBuyDeltaOrder {
29
+ return 'numSlices' in order && typeof order.numSlices === 'number';
30
+ }
31
+
32
+ /**
33
+ * @description Checks whether an order is a TWAP Sell order.
34
+ */
35
+ function isTWAPSellOrder(order: DeltaOrderUnion): order is TWAPDeltaOrder {
36
+ return (
37
+ isTWAPOrder(order) &&
38
+ 'totalSrcAmount' in order &&
39
+ typeof order.totalSrcAmount === 'string'
40
+ );
41
+ }
42
+ /**
43
+ * @description Checks whether an order is a TWAP Buy order.
44
+ */
45
+ function isTWAPBuyOrder(order: DeltaOrderUnion): order is TWAPBuyDeltaOrder {
46
+ return (
47
+ isTWAPOrder(order) &&
48
+ 'totalDestAmount' in order &&
49
+ typeof order.totalDestAmount === 'string'
50
+ );
51
+ }
52
+
53
+ /**
54
+ * @description Checks whether an order is an External order.
55
+ */
56
+ function isExternalOrder(order: DeltaOrderUnion): order is ExternalDeltaOrder {
57
+ return 'handler' in order;
58
+ }
59
+
60
+ /**
61
+ * @description Checks whether an order is a regular Delta auction order.
62
+ */
63
+ function isDeltaOrder(order: DeltaOrderUnion): order is DeltaAuctionOrder {
64
+ return (
65
+ !isExternalOrder(order) && 'kind' in order && typeof order.kind === 'number'
66
+ );
67
+ }
68
+
69
+ /**
70
+ * @description Checks whether an auction is a TWAP auction.
71
+ */
72
+ function isTWAPAuction<T extends OnChainOrderType>(auction: {
73
+ onChainOrderType: T;
74
+ }): auction is { onChainOrderType: ('TWAPOrder' | 'TWAPBuyOrder') & T } {
75
+ return isTWAPSellAuction(auction) || isTWAPBuyAuction(auction);
76
+ }
77
+
78
+ /**
79
+ * @description Checks whether an auction is a TWAP Sell auction.
80
+ */
81
+ function isTWAPSellAuction<T extends OnChainOrderType>(auction: {
82
+ onChainOrderType: T;
83
+ }): auction is { onChainOrderType: 'TWAPOrder' & T } {
84
+ return auction.onChainOrderType === 'TWAPOrder';
85
+ }
86
+
87
+ /**
88
+ * @description Checks whether an auction is a TWAP Buy auction.
89
+ */
90
+ function isTWAPBuyAuction<T extends OnChainOrderType>(auction: {
91
+ onChainOrderType: T;
92
+ }): auction is { onChainOrderType: 'TWAPBuyOrder' & T } {
93
+ return auction.onChainOrderType === 'TWAPBuyOrder';
94
+ }
95
+
96
+ /**
97
+ * @description Checks whether an auction is a Delta auction.
98
+ */
99
+ function isDeltaAuction<T extends OnChainOrderType>(auction: {
100
+ onChainOrderType: T;
101
+ }): auction is { onChainOrderType: 'Order' & T } {
102
+ return auction.onChainOrderType === 'Order';
103
+ }
104
+
105
+ /**
106
+ * @description Checks whether an auction is an External auction.
107
+ */
108
+ function isExternalAuction<T extends OnChainOrderType>(auction: {
109
+ onChainOrderType: T;
110
+ }): auction is { onChainOrderType: 'ExternalOrder' & T } {
111
+ return auction.onChainOrderType === 'ExternalOrder';
112
+ }
113
+
114
+ const checks = {
115
+ isTWAPOrder,
116
+ isTWAPSellOrder,
117
+ isTWAPBuyOrder,
118
+ isExternalOrder,
119
+ isDeltaOrder,
120
+ isTWAPAuction,
121
+ isTWAPSellAuction,
122
+ isTWAPBuyAuction,
123
+ isDeltaAuction,
124
+ isExternalAuction,
125
+ isOrderCrosschain,
126
+ isExecutedAuction,
127
+ isPartiallyExecutedAuction,
128
+ isFailedAuction,
129
+ isCanceledAuction,
130
+ isExpiredAuction,
131
+ isPendingAuction,
132
+ };
133
+
134
+ ///// GETTERS //////
135
+
136
+ /**
137
+ * @description Returns the expected source amount for a TWAP order.
138
+ */
139
+ function getExpectedTwapSrcAmount(
140
+ order:
141
+ | Pick<TWAPDeltaOrder, 'totalSrcAmount'>
142
+ | Pick<TWAPBuyDeltaOrder, 'maxSrcAmount'>
143
+ ) {
144
+ if ('totalSrcAmount' in order) {
145
+ // SELL
146
+ return order.totalSrcAmount;
147
+ }
148
+
149
+ return order.maxSrcAmount; // BUY
150
+ }
151
+
152
+ /**
153
+ * @description Returns the expected destination amount for a TWAP order.
154
+ */
155
+ function getExpectedTwapDestAmount(
156
+ order:
157
+ | Pick<TWAPDeltaOrder, 'destAmountPerSlice' | 'numSlices' | 'bridge'>
158
+ | Pick<TWAPBuyDeltaOrder, 'totalDestAmount' | 'bridge'>
159
+ ) {
160
+ const destAmount =
161
+ 'destAmountPerSlice' in order
162
+ ? BigInt(order.destAmountPerSlice) * BigInt(order.numSlices) // SELL
163
+ : BigInt(order.totalDestAmount); // BUY
164
+
165
+ if (isOrderCrosschain(order)) {
166
+ return scaleByFactor(destAmount, order.bridge.scalingFactor).toString();
167
+ }
168
+
169
+ return destAmount.toString();
170
+ }
171
+
172
+ /**
173
+ * @description Returns expected source and destination amounts for a TWAP order.
174
+ */
175
+ function getExpectedTwapOrderAmounts(
176
+ order: TWAPDeltaOrder | TWAPBuyDeltaOrder
177
+ ) {
178
+ const srcAmount = getExpectedTwapSrcAmount(order);
179
+ const destAmount = getExpectedTwapDestAmount(order);
180
+ return { srcAmount, destAmount };
181
+ }
182
+
183
+ /**
184
+ * @description Returns expected and, when available, final amounts for a TWAP auction.
185
+ */
186
+ function getTwapAuctionAmounts(
187
+ twapAuction:
188
+ | Pick<DeltaAuctionTWAP, 'status' | 'transactions' | 'order'>
189
+ | Pick<DeltaAuctionTWAPBuy, 'status' | 'transactions' | 'order'>
190
+ ) {
191
+ const isExecuted = isExecutedAuction(twapAuction);
192
+
193
+ const expected = getExpectedTwapOrderAmounts(twapAuction.order);
194
+ if (isExecuted) {
195
+ const final = getTransactionAmounts(twapAuction.transactions);
196
+ return {
197
+ final,
198
+ expected,
199
+ };
200
+ }
201
+ return {
202
+ expected,
203
+ };
204
+ }
205
+
206
+ const getters = {
207
+ getUnifiedDeltaOrderData,
208
+ getExpectedTwapSrcAmount,
209
+ getExpectedTwapDestAmount,
210
+ getExpectedTwapOrderAmounts,
211
+ getTwapAuctionAmounts,
212
+ getAuctionDestChainId,
213
+ getSwapSideFromDeltaOrder,
214
+ getSwapSideFromTwapOrderType,
215
+ getAuctionSwapSide,
216
+ getOrderTokenAddresses,
217
+ getTransactionAmounts,
218
+ getAuctionAmounts,
219
+ getFilledPercent,
220
+ };
221
+
222
+ export const OrderHelpers = {
223
+ checks,
224
+ getters,
225
+ };
226
+
227
+ // -------------------- Auction Unified Data --------------------
228
+
229
+ /**
230
+ * @description Returns the destination chain id for the auction.
231
+ */
232
+ function getAuctionDestChainId({
233
+ order,
234
+ chainId,
235
+ }: Pick<DeltaAuction, 'order' | 'chainId'>) {
236
+ return isOrderCrosschain(order) ? order.bridge.destinationChainId : chainId;
237
+ }
238
+
239
+ const OrderKindToSwapSide = {
240
+ [OrderKind.Sell]: 'SELL',
241
+ [OrderKind.Buy]: 'BUY',
242
+ } as const;
243
+
244
+ /**
245
+ * @description Returns swap side from a Delta or External order kind.
246
+ */
247
+ function getSwapSideFromDeltaOrder(
248
+ order: DeltaAuctionOrder | ExternalDeltaOrder
249
+ ): SwapSideUnion {
250
+ return OrderKindToSwapSide[order.kind];
251
+ }
252
+
253
+ const TwapTypeToSwapSide = {
254
+ TWAPOrder: 'SELL',
255
+ TWAPBuyOrder: 'BUY',
256
+ } as const;
257
+
258
+ /**
259
+ * @description Returns swap side from TWAP on-chain order type.
260
+ */
261
+ function getSwapSideFromTwapOrderType(
262
+ onChainOrderType: 'TWAPOrder' | 'TWAPBuyOrder'
263
+ ): SwapSideUnion {
264
+ return TwapTypeToSwapSide[onChainOrderType];
265
+ }
266
+
267
+ /**
268
+ * @description Returns swap side for any auction type.
269
+ */
270
+ function getAuctionSwapSide(auction: DeltaAuction): SwapSideUnion {
271
+ if (isTWAPAuction(auction)) {
272
+ // TWAP orders have onChainOrderType instead of kind
273
+ return getSwapSideFromTwapOrderType(auction.onChainOrderType);
274
+ }
275
+ return getSwapSideFromDeltaOrder(auction.order);
276
+ }
277
+
278
+ /**
279
+ * @description Returns unified order data with normalized amounts, tokens, and side.
280
+ */
281
+ function getUnifiedDeltaOrderData(
282
+ auction: DeltaAuction
283
+ ): UnifiedDeltaOrderData {
284
+ const { order, chainId } = auction;
285
+
286
+ const { srcToken, destToken } = getOrderTokenAddresses(order);
287
+ const { expected, final } = getAuctionAmounts(auction);
288
+
289
+ const srcChainId = chainId;
290
+ const destChainId = getAuctionDestChainId({ order, chainId });
291
+
292
+ const swapSide = getAuctionSwapSide(auction);
293
+
294
+ const filledPercent = getFilledPercent(auction);
295
+
296
+ return {
297
+ srcChainId,
298
+ destChainId,
299
+ srcAmount: final?.srcAmount || expected.srcAmount,
300
+ destAmount: final?.destAmount || expected.destAmount,
301
+ amounts: {
302
+ expected,
303
+ final,
304
+ },
305
+ srcToken,
306
+ destToken,
307
+ swapSide,
308
+ filledPercent,
309
+ };
310
+ }
311
+
312
+ /**
313
+ * @description Returns source and destination token addresses for an order.
314
+ */
315
+ function getOrderTokenAddresses(order: DeltaAuction['order']) {
316
+ const srcToken = order.srcToken;
317
+ const destToken = isOrderCrosschain(order)
318
+ ? order.bridge.outputToken
319
+ : order.destToken;
320
+ return {
321
+ srcToken,
322
+ destToken,
323
+ };
324
+ }
325
+
326
+ /**
327
+ * @description Aggregates transaction amounts into total source and destination values.
328
+ */
329
+ function getTransactionAmounts(transactions: DeltaAuctionTransaction[]) {
330
+ const { srcAmount, destAmount } = transactions.reduce(
331
+ (acc, { spentAmount, receivedAmount, bridgeMetadata }) => {
332
+ return {
333
+ srcAmount: acc.srcAmount + BigInt(spentAmount),
334
+ destAmount:
335
+ acc.destAmount +
336
+ BigInt(bridgeMetadata ? bridgeMetadata.outputAmount : receivedAmount),
337
+ };
338
+ },
339
+ {
340
+ srcAmount: 0n,
341
+ destAmount: 0n,
342
+ }
343
+ );
344
+
345
+ return {
346
+ srcAmount: srcAmount.toString(),
347
+ destAmount: destAmount.toString(),
348
+ };
349
+ }
350
+
351
+ /**
352
+ * @description Returns expected and, when available, final amounts for an auction.
353
+ */
354
+ function getAuctionAmounts(auction: DeltaAuction) {
355
+ const isTwap = checks.isTWAPAuction(auction);
356
+ if (isTwap) {
357
+ return getTwapAuctionAmounts(auction);
358
+ }
359
+
360
+ let expected = {
361
+ srcAmount: auction.order.srcAmount,
362
+ destAmount: auction.order.expectedAmount || auction.order.destAmount,
363
+ };
364
+
365
+ const order = auction.order;
366
+
367
+ if (isOrderCrosschain(order)) {
368
+ expected = {
369
+ srcAmount: expected.srcAmount,
370
+ destAmount: scaleByFactor(
371
+ BigInt(expected.destAmount),
372
+ order.bridge.scalingFactor
373
+ ).toString(),
374
+ };
375
+ }
376
+
377
+ const isExecuted = isExecutedAuction(auction);
378
+ if (isExecuted) {
379
+ const final = getTransactionAmounts(auction.transactions);
380
+ return {
381
+ final,
382
+ expected,
383
+ };
384
+ }
385
+ return {
386
+ expected,
387
+ };
388
+ }
389
+
390
+ /**
391
+ * @description Checks whether an order includes valid cross-chain bridge details.
392
+ */
393
+ function isOrderCrosschain<T extends { bridge?: Bridge } | object>(
394
+ order: T
395
+ // Extract<ExternalOrder, { bridge?: Bridge }> == never
396
+ ): order is Prettify<Extract<T, { bridge?: Bridge }> & { bridge: Bridge }> {
397
+ return (
398
+ 'bridge' in order && !!order.bridge && order.bridge.destinationChainId !== 0
399
+ );
400
+ }
401
+
402
+ function scaleByFactor(amount: bigint, scalingFactor: number): bigint {
403
+ if (!amount) return 0n;
404
+
405
+ if (scalingFactor === undefined) return amount;
406
+
407
+ const base = 10n;
408
+
409
+ return scalingFactor < 0
410
+ ? amount / base ** BigInt(-scalingFactor)
411
+ : amount * base ** BigInt(scalingFactor);
412
+ }
413
+
414
+ type ExecutedDeltaAuctionProps = {
415
+ status: 'EXECUTED';
416
+ transactions: NonEmptyArray<DeltaAuctionTransaction>;
417
+ };
418
+
419
+ /**
420
+ * @description Checks whether an auction is fully executed.
421
+ */
422
+ function isExecutedAuction<
423
+ T extends Pick<DeltaAuction, 'order' | 'status' | 'transactions'>
424
+ >(auction: T): auction is T & ExecutedDeltaAuctionProps {
425
+ if (auction.status !== 'EXECUTED') return false;
426
+
427
+ if (isOrderCrosschain(auction.order)) {
428
+ const filledPercent = getFilledPercent(auction);
429
+ return filledPercent === 100;
430
+ }
431
+
432
+ return true;
433
+ }
434
+
435
+ const failedAuctionStatuses = [
436
+ 'FAILED',
437
+ 'EXPIRED',
438
+ 'CANCELLED',
439
+ 'REFUNDED',
440
+ ] as const;
441
+
442
+ const failedAuctionStatusesSet = new Set<DeltaAuctionStatus>(
443
+ failedAuctionStatuses
444
+ );
445
+
446
+ type FailedDeltaAuctionProps =
447
+ | {
448
+ status: (typeof failedAuctionStatuses)[number];
449
+ }
450
+ | {
451
+ status: 'EXECUTED'; // srcChain tx succeeded
452
+ bridgeStatus: 'expired' | 'refunded'; // destChain tx failed or relayer didn't deliver
453
+ };
454
+
455
+ /**
456
+ * @description Checks whether an auction is failed on source or destination chain.
457
+ */
458
+ function isFailedAuction<
459
+ T extends Pick<DeltaAuction, 'status' | 'order' | 'bridgeStatus'>
460
+ >(auction: T): auction is T & FailedDeltaAuctionProps {
461
+ // already failed on srcChain, whether Order is crosschain or not
462
+ if (failedAuctionStatusesSet.has(auction.status)) return true;
463
+
464
+ // crosschain Order is executed on srcChain, but failed on destChain
465
+ if (auction.status === 'EXECUTED' && isOrderCrosschain(auction.order)) {
466
+ return (
467
+ auction.bridgeStatus === 'expired' || auction.bridgeStatus === 'refunded'
468
+ );
469
+ }
470
+
471
+ return false;
472
+ }
473
+
474
+ /**
475
+ * @description Checks whether an auction status is cancelled.
476
+ */
477
+ function isCanceledAuction<T extends Pick<DeltaAuction, 'status'>>(
478
+ auction: T
479
+ ): auction is T & {
480
+ status: 'CANCELLED';
481
+ } {
482
+ return auction.status === 'CANCELLED';
483
+ }
484
+
485
+ /**
486
+ * @description Checks whether an auction status is expired.
487
+ */
488
+ function isExpiredAuction<T extends Pick<DeltaAuction, 'status'>>(
489
+ auction: T
490
+ ): auction is T & {
491
+ status: 'EXPIRED';
492
+ } {
493
+ return auction.status === 'EXPIRED';
494
+ }
495
+
496
+ const pendingAuctionStatuses = [
497
+ 'NOT_STARTED',
498
+ 'AWAITING_PRE_SIGNATURE',
499
+ 'RUNNING',
500
+ 'EXECUTING',
501
+ ] as const;
502
+
503
+ const pendingAuctionStatusesSet = new Set<DeltaAuctionStatus>(
504
+ pendingAuctionStatuses
505
+ );
506
+ /**
507
+ * @description Checks whether an auction status is in pending execution states.
508
+ */
509
+ function isPendingAuction<T extends Pick<DeltaAuction, 'status'>>(
510
+ auction: T
511
+ ): auction is T & {
512
+ status: (typeof pendingAuctionStatuses)[number];
513
+ } {
514
+ return pendingAuctionStatusesSet.has(auction.status);
515
+ }
516
+
517
+ /**
518
+ * @description Auction can be cancelled in the middle of execution,
519
+ * or crosschain-TWAP slices may not all be bridged,
520
+ * or order can be suspended if it runs out of user balance/allowance.
521
+ * Orders in the middle of normal execution can also be considered partially executed if they have any transactions.
522
+ */
523
+ function isPartiallyExecutedAuction<
524
+ T extends Pick<DeltaAuction, 'order' | 'transactions'>
525
+ >(
526
+ auction: T
527
+ ): auction is T & { transactions: NonEmptyArray<DeltaAuctionTransaction> } {
528
+ if (auction.transactions.length === 0) return false;
529
+
530
+ const filledPercent = getFilledPercent(auction);
531
+
532
+ return filledPercent > 0 && filledPercent < 100;
533
+ }
534
+
535
+ /**
536
+ * @description Calculates filled percentage from auction transaction filled bps values.
537
+ */
538
+ function getFilledPercent(
539
+ auction: Pick<DeltaAuction, 'order' | 'transactions'>
540
+ ): number {
541
+ const transaction = !isOrderCrosschain(auction.order)
542
+ ? auction.transactions
543
+ : auction.transactions.filter(
544
+ (transaction) => transaction.bridgeStatus === 'filled'
545
+ );
546
+
547
+ const filledPercentBps = transaction.reduce((acc, { filledPercent }) => {
548
+ return acc + filledPercent;
549
+ }, 0);
550
+
551
+ const filledPercent = filledPercentBps / 100;
552
+ return filledPercent;
553
+ }
554
+
555
+ ///// TESTS //////
556
+ // @TODO remove
557
+
558
+ const auction = {} as DeltaAuctionUnion;
559
+ const minAuction = {
560
+ onChainOrderType: 'TWAPOrder' as OnChainOrderType,
561
+ gas: 6,
562
+ };
563
+ if (isTWAPAuction(minAuction)) {
564
+ console.log('🚀 ~ auction:', minAuction);
565
+ /**
566
+ * {
567
+ onChainOrderType: OnChainOrderType;
568
+ gas: number;
569
+ } & {
570
+ onChainOrderType: "TWAPOrder" | "TWAPBuyOrder";
571
+ }
572
+ */
573
+ }
574
+ if (isTWAPAuction(auction)) {
575
+ console.log('🚀 ~ auction:', auction);
576
+ /**
577
+ (DeltaAuctionBase & {
578
+ onChainOrderType: "TWAPOrder";
579
+ order: TWAPDeltaOrder;
580
+ }) | (DeltaAuctionBase & {
581
+ onChainOrderType: "TWAPBuyOrder";
582
+ order: TWAPBuyDeltaOrder;
583
+ })
584
+ */
585
+ }
586
+ if (isTWAPSellAuction(auction)) {
587
+ console.log('🚀 ~ auction:', auction);
588
+ /**
589
+ * DeltaAuctionBase & {
590
+ onChainOrderType: "TWAPOrder";
591
+ order: TWAPDeltaOrder;
592
+ }
593
+ */
594
+ }
595
+ if (isTWAPBuyAuction(auction)) {
596
+ console.log('🚀 ~ auction:', auction);
597
+ /**
598
+ * DeltaAuctionBase & {
599
+ onChainOrderType: "TWAPBuyOrder";
600
+ order: TWAPBuyDeltaOrder;
601
+ }
602
+ */
603
+ }
604
+ if (isDeltaAuction(auction)) {
605
+ console.log('🚀 ~ auction:', auction);
606
+ /**
607
+ * DeltaAuctionBase & {
608
+ onChainOrderType: "Order";
609
+ order: DeltaAuctionOrder;
610
+ }
611
+ */
612
+ }
613
+
614
+ if (isExternalAuction(auction)) {
615
+ console.log('🚀 ~ auction:', auction);
616
+ /**
617
+ * DeltaAuctionBase & {
618
+ onChainOrderType: "ExternalOrder";
619
+ order: ExternalDeltaOrder;
620
+ }
621
+ */
622
+ }
623
+ // ------------------------------------------------------------ //
624
+
625
+ const orderAny = {} as DeltaOrderUnion;
626
+ if (isOrderCrosschain(orderAny)) {
627
+ console.log('🚀 ~ order:', orderAny);
628
+ /**
629
+ DeltaAuctionOrder | TWAPDeltaOrder | TWAPBuyDeltaOrder
630
+ */
631
+ }
632
+ const orderExternal = {} as ExternalDeltaOrder;
633
+ if (isOrderCrosschain(orderExternal)) {
634
+ console.log('🚀 ~ order:', orderExternal);
635
+ /**
636
+ never
637
+ */
638
+ }
639
+ const orderLike = {} as { bridge?: Bridge; a: 2 };
640
+ if (isOrderCrosschain(orderLike)) {
641
+ console.log('🚀 ~ order:', orderLike);
642
+ /**
643
+ * {
644
+ bridge: Bridge;
645
+ a: 2;
646
+ }
647
+ */
648
+ }