@lifi/perps-sdk-provider-ondo 9.0.0 → 11.0.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.
Files changed (67) hide show
  1. package/README.md +29 -0
  2. package/dist/cjs/OndoProvider.d.ts.map +1 -1
  3. package/dist/cjs/OndoProvider.js +41 -40
  4. package/dist/cjs/OndoProvider.js.map +1 -1
  5. package/dist/cjs/types/wire.d.ts +35 -0
  6. package/dist/cjs/types/wire.d.ts.map +1 -1
  7. package/dist/cjs/utils/depositAddress.d.ts.map +1 -1
  8. package/dist/cjs/utils/depositAddress.js +3 -0
  9. package/dist/cjs/utils/depositAddress.js.map +1 -1
  10. package/dist/cjs/utils/index.d.ts +1 -1
  11. package/dist/cjs/utils/index.d.ts.map +1 -1
  12. package/dist/cjs/utils/index.js +3 -1
  13. package/dist/cjs/utils/index.js.map +1 -1
  14. package/dist/cjs/utils/mapActivity.d.ts +5 -3
  15. package/dist/cjs/utils/mapActivity.d.ts.map +1 -1
  16. package/dist/cjs/utils/mapActivity.js +72 -13
  17. package/dist/cjs/utils/mapActivity.js.map +1 -1
  18. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  19. package/dist/cjs/utils/mapFill.js +4 -1
  20. package/dist/cjs/utils/mapFill.js.map +1 -1
  21. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  22. package/dist/cjs/utils/mapOrder.js +4 -2
  23. package/dist/cjs/utils/mapOrder.js.map +1 -1
  24. package/dist/esm/OndoProvider.d.ts.map +1 -1
  25. package/dist/esm/OndoProvider.js +60 -42
  26. package/dist/esm/OndoProvider.js.map +1 -1
  27. package/dist/esm/types/wire.d.ts +53 -0
  28. package/dist/esm/types/wire.d.ts.map +1 -1
  29. package/dist/esm/utils/depositAddress.d.ts +3 -2
  30. package/dist/esm/utils/depositAddress.d.ts.map +1 -1
  31. package/dist/esm/utils/depositAddress.js +6 -2
  32. package/dist/esm/utils/depositAddress.js.map +1 -1
  33. package/dist/esm/utils/index.d.ts +1 -1
  34. package/dist/esm/utils/index.d.ts.map +1 -1
  35. package/dist/esm/utils/index.js +1 -1
  36. package/dist/esm/utils/index.js.map +1 -1
  37. package/dist/esm/utils/mapActivity.d.ts +30 -6
  38. package/dist/esm/utils/mapActivity.d.ts.map +1 -1
  39. package/dist/esm/utils/mapActivity.js +98 -14
  40. package/dist/esm/utils/mapActivity.js.map +1 -1
  41. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  42. package/dist/esm/utils/mapFill.js +5 -1
  43. package/dist/esm/utils/mapFill.js.map +1 -1
  44. package/dist/esm/utils/mapOrder.d.ts +5 -1
  45. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  46. package/dist/esm/utils/mapOrder.js +10 -3
  47. package/dist/esm/utils/mapOrder.js.map +1 -1
  48. package/dist/types/OndoProvider.d.ts.map +1 -1
  49. package/dist/types/types/wire.d.ts +53 -0
  50. package/dist/types/types/wire.d.ts.map +1 -1
  51. package/dist/types/utils/depositAddress.d.ts +3 -2
  52. package/dist/types/utils/depositAddress.d.ts.map +1 -1
  53. package/dist/types/utils/index.d.ts +1 -1
  54. package/dist/types/utils/index.d.ts.map +1 -1
  55. package/dist/types/utils/mapActivity.d.ts +30 -6
  56. package/dist/types/utils/mapActivity.d.ts.map +1 -1
  57. package/dist/types/utils/mapFill.d.ts.map +1 -1
  58. package/dist/types/utils/mapOrder.d.ts +5 -1
  59. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  60. package/package.json +3 -3
  61. package/src/OndoProvider.ts +103 -76
  62. package/src/types/wire.ts +63 -0
  63. package/src/utils/depositAddress.ts +6 -2
  64. package/src/utils/index.ts +6 -1
  65. package/src/utils/mapActivity.ts +112 -15
  66. package/src/utils/mapFill.ts +5 -1
  67. package/src/utils/mapOrder.ts +12 -3
@@ -17,6 +17,7 @@ import {
17
17
  type ProviderGetPositionsParams,
18
18
  type ProviderGetQuoteParams,
19
19
  type ProviderGetRunningTwapsParams,
20
+ paginateActivity,
20
21
  resolveQuote,
21
22
  resolveRetryPolicy,
22
23
  type SDKRequestOptions,
@@ -34,7 +35,10 @@ import type {
34
35
  ActionStep,
35
36
  ActivitiesResponse,
36
37
  ActivityItem,
38
+ Fill,
37
39
  FillsResponse,
40
+ FundingActivity,
41
+ LiquidationActivity,
38
42
  MarketDisplay,
39
43
  OndoAccountConfig,
40
44
  Order,
@@ -47,6 +51,7 @@ import type {
47
51
  SignedActionStep,
48
52
  SigningMethod,
49
53
  TwapOrder,
54
+ WithdrawalActivity,
50
55
  } from '@lifi/perps-types'
51
56
  import { ActionType, ActivityType, PerpsErrorCode } from '@lifi/perps-types'
52
57
  import type { Address } from 'viem'
@@ -73,11 +78,12 @@ import type {
73
78
  OndoOrder,
74
79
  OndoPosition,
75
80
  OndoTwapOrder,
81
+ OndoWalletDeposit,
82
+ OndoWalletWithdrawal,
76
83
  } from './types/wire.js'
77
84
  import {
78
85
  decodeActivityCursor,
79
86
  encodeActivityCursor,
80
- type OndoActivityCursor,
81
87
  } from './utils/activityCursor.js'
82
88
  import {
83
89
  type ApiParams,
@@ -92,11 +98,13 @@ import {
92
98
  formatOrderPrice,
93
99
  formatOrderSize,
94
100
  listOndoDepositAddress,
101
+ mapDepositActivity,
95
102
  mapFill,
96
103
  mapFundingActivity,
97
104
  mapLiquidationActivity,
98
105
  mapOpenPositions,
99
106
  mapOrderDetail,
107
+ mapWithdrawalActivity,
100
108
  positionMarginConstraints,
101
109
  } from './utils/index.js'
102
110
  import { mapRunningTwap } from './utils/mapTwap.js'
@@ -173,6 +181,13 @@ export const ondoProvider = (
173
181
 
174
182
  const requireMarketDisplay = (marketId: string): MarketDisplay =>
175
183
  toMarketDisplay(marketRegistry().require(marketId))
184
+ // The droppable-row counterpart of `requireMarketDisplay`, for the history
185
+ // surfaces where an id the backend market list does not hold must cost only
186
+ // its own row. The registry warns once per unresolved id.
187
+ const marketDisplay = (marketId: string): MarketDisplay | undefined => {
188
+ const market = marketRegistry().get(marketId)
189
+ return market === undefined ? undefined : toMarketDisplay(market)
190
+ }
176
191
  const requirePerpsMarketDisplay = (marketId: string): PerpsMarketDisplay =>
177
192
  toPerpsMarketDisplay(marketRegistry().require(marketId))
178
193
 
@@ -258,7 +273,7 @@ export const ondoProvider = (
258
273
  client.get<OndoBalanceSummary>('/v1/perps/balance', {
259
274
  authToken: token.token,
260
275
  }),
261
- client.get<OndoPosition[]>('/v1/perps/positions', {
276
+ client.get<OndoPosition[] | null>('/v1/perps/positions', {
262
277
  authToken: token.token,
263
278
  }),
264
279
  client.get<OndoAccountReferral | null>('/v1/account/referral', {
@@ -286,7 +301,7 @@ export const ondoProvider = (
286
301
  }
287
302
 
288
303
  const positions: Position[] = mapOpenPositions(
289
- rawPositions,
304
+ rawPositions ?? [],
290
305
  requirePerpsMarketDisplay
291
306
  )
292
307
 
@@ -385,14 +400,14 @@ export const ondoProvider = (
385
400
  async (token) => {
386
401
  const client = apiClient(opts)
387
402
  const [rawPositions] = await Promise.all([
388
- client.get<OndoPosition[]>('/v1/perps/positions', {
403
+ client.get<OndoPosition[] | null>('/v1/perps/positions', {
389
404
  authToken: token.token,
390
405
  }),
391
406
  marketRegistry().sync(),
392
407
  ])
393
408
 
394
409
  let positions: Position[] = mapOpenPositions(
395
- rawPositions,
410
+ rawPositions ?? [],
396
411
  requirePerpsMarketDisplay
397
412
  )
398
413
  if (params.marketId !== undefined) {
@@ -443,13 +458,9 @@ export const ondoProvider = (
443
458
  marketRegistry().sync(),
444
459
  ])
445
460
 
446
- const registry = marketRegistry()
447
461
  const { openOrders, triggerOrders } = classifyAndMapOrders(
448
462
  page.result,
449
- (marketId) => {
450
- const market = registry.get(marketId)
451
- return market === undefined ? undefined : toMarketDisplay(market)
452
- }
463
+ marketDisplay
453
464
  )
454
465
 
455
466
  const nextCursor = page.pageInfo?.nextCursor
@@ -547,9 +558,10 @@ export const ondoProvider = (
547
558
  marketRegistry().sync(),
548
559
  ])
549
560
 
550
- const items = page.result.map((fill) =>
551
- mapFill(fill, requireMarketDisplay(fill.market))
552
- )
561
+ const items = page.result.flatMap((fill): Fill[] => {
562
+ const market = marketDisplay(fill.market)
563
+ return market === undefined ? [] : [mapFill(fill, market)]
564
+ })
553
565
 
554
566
  const nextCursor = page.pageInfo?.nextCursor
555
567
  return {
@@ -580,6 +592,9 @@ export const ondoProvider = (
580
592
  const inputCursor = decodeActivityCursor(params.cursor)
581
593
  const client = apiClient(opts)
582
594
 
595
+ // Ondo publishes no account-to-account transfer history. Its
596
+ // transfer surface moves value between the main and margin wallets
597
+ // of one account, which `TransferActivity` excludes.
583
598
  const wantsType = (t: ActivityType): boolean =>
584
599
  params.type === undefined || params.type.includes(t)
585
600
  const shouldFetch = (
@@ -595,6 +610,13 @@ export const ondoProvider = (
595
610
  const v = inputCursor[key]
596
611
  return typeof v === 'string' && v.length > 0
597
612
  }
613
+ // `/v1/wallet/deposits` and `/v1/wallet/withdrawals` accept no
614
+ // cursor and return the whole history in one response, so each is
615
+ // fetched on the first page only. Its tail rides the cursor's
616
+ // `overflow` list, which is what keeps a later page from either
617
+ // re-fetching the list or losing the rows past the limit.
618
+ const shouldFetchOnce = (t: ActivityType): boolean =>
619
+ wantsType(t) && inputCursor === undefined
598
620
  const cursorParams = (
599
621
  key: 'fundings' | 'liquidations'
600
622
  ): ApiParams => {
@@ -606,77 +628,82 @@ export const ondoProvider = (
606
628
  pageInfo: undefined,
607
629
  })
608
630
 
609
- const [fundings, liquidations] = await Promise.all([
610
- shouldFetch(ActivityType.FUNDING, 'fundings')
611
- ? client.getPage<OndoFundingFeeTransfer>(
612
- '/v1/perps/funding_fees',
613
- {
614
- params: cursorParams('fundings'),
615
- authToken: token.token,
616
- }
617
- )
618
- : Promise.resolve(emptyPage<OndoFundingFeeTransfer>()),
619
- shouldFetch(ActivityType.LIQUIDATION, 'liquidations')
620
- ? client.getPage<OndoLiquidationEvent>(
621
- '/v1/perps/liquidation_history',
622
- {
623
- params: cursorParams('liquidations'),
624
- authToken: token.token,
625
- }
626
- )
627
- : Promise.resolve(emptyPage<OndoLiquidationEvent>()),
628
- marketRegistry().sync(),
629
- ])
631
+ const [fundings, liquidations, deposits, withdrawals] =
632
+ await Promise.all([
633
+ shouldFetch(ActivityType.FUNDING, 'fundings')
634
+ ? client.getPage<OndoFundingFeeTransfer>(
635
+ '/v1/perps/funding_fees',
636
+ {
637
+ params: cursorParams('fundings'),
638
+ authToken: token.token,
639
+ }
640
+ )
641
+ : Promise.resolve(emptyPage<OndoFundingFeeTransfer>()),
642
+ shouldFetch(ActivityType.LIQUIDATION, 'liquidations')
643
+ ? client.getPage<OndoLiquidationEvent>(
644
+ '/v1/perps/liquidation_history',
645
+ {
646
+ params: cursorParams('liquidations'),
647
+ authToken: token.token,
648
+ }
649
+ )
650
+ : Promise.resolve(emptyPage<OndoLiquidationEvent>()),
651
+ shouldFetchOnce(ActivityType.DEPOSIT)
652
+ ? client.get<OndoWalletDeposit[] | null>(
653
+ '/v1/wallet/deposits',
654
+ {
655
+ authToken: token.token,
656
+ }
657
+ )
658
+ : Promise.resolve(null),
659
+ shouldFetchOnce(ActivityType.WITHDRAWAL)
660
+ ? client.get<OndoWalletWithdrawal[] | null>(
661
+ '/v1/wallet/withdrawals',
662
+ { authToken: token.token }
663
+ )
664
+ : Promise.resolve(null),
665
+ // Only the funding and liquidation rows name a market. A ledger
666
+ // request alone must not pull the market list.
667
+ wantsType(ActivityType.FUNDING) ||
668
+ wantsType(ActivityType.LIQUIDATION)
669
+ ? marketRegistry().sync()
670
+ : Promise.resolve(),
671
+ ])
630
672
 
631
673
  const items: ActivityItem[] = [
632
- ...fundings.result.map((f) =>
633
- mapFundingActivity(f, requireMarketDisplay(f.market))
634
- ),
635
- ...liquidations.result.map((l) =>
636
- mapLiquidationActivity(l, requireMarketDisplay)
637
- ),
674
+ ...fundings.result.flatMap((f): FundingActivity[] => {
675
+ const market = marketDisplay(f.market)
676
+ return market === undefined ? [] : [mapFundingActivity(f, market)]
677
+ }),
678
+ // A liquidation event that names no position is dropped: the
679
+ // public contract guarantees a non-empty `liquidatedPositions`.
680
+ ...liquidations.result
681
+ .map((l) => mapLiquidationActivity(l, marketDisplay))
682
+ .filter((a): a is LiquidationActivity => a !== null),
683
+ ...(deposits ?? []).map(mapDepositActivity),
684
+ // A withdrawal Ondo reports as failed or cancelled moved no value,
685
+ // and `WithdrawalActivity` carries no status to say so.
686
+ ...(withdrawals ?? [])
687
+ .map(mapWithdrawalActivity)
688
+ .filter((a): a is WithdrawalActivity => a !== null),
638
689
  ]
639
690
 
640
- const merged = [...(inputCursor?.overflow ?? []), ...items]
641
-
642
- const filtered = merged.filter((it) => {
643
- const ts = new Date(it.timestamp).getTime()
644
- if (params.startTime !== undefined && ts < params.startTime) {
645
- return false
646
- }
647
- if (params.endTime !== undefined && ts > params.endTime) {
648
- return false
649
- }
650
- return true
651
- })
652
-
653
- filtered.sort(
654
- (a, b) =>
655
- new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
691
+ const page = paginateActivity(
692
+ items,
693
+ inputCursor?.overflow ?? [],
694
+ params,
695
+ (overflowTail) =>
696
+ encodeActivityCursor({
697
+ fundings: fundings.pageInfo?.nextCursor,
698
+ liquidations: liquidations.pageInfo?.nextCursor,
699
+ overflow: overflowTail,
700
+ })
656
701
  )
657
702
 
658
- const limit = params.limit ?? filtered.length
659
- const emitted = filtered.slice(0, limit)
660
- const overflow = filtered.slice(limit)
661
-
662
- const nextCursorEnvelope: OndoActivityCursor = {
663
- fundings: fundings.pageInfo?.nextCursor,
664
- liquidations: liquidations.pageInfo?.nextCursor,
665
- overflow,
666
- }
667
- const responseCursor = encodeActivityCursor(nextCursorEnvelope)
668
- const hasMore = responseCursor !== undefined
669
-
670
703
  return {
671
704
  provider: ONDO_PROVIDER_KEY,
672
- items: emitted,
673
- pagination: {
674
- limit,
675
- hasMore,
676
- ...(responseCursor === undefined
677
- ? {}
678
- : { cursor: responseCursor }),
679
- },
705
+ items: page.items,
706
+ pagination: page.pagination,
680
707
  }
681
708
  }
682
709
  )
package/src/types/wire.ts CHANGED
@@ -274,3 +274,66 @@ export interface OndoAccountInfo {
274
274
  privacyUnixSecs: number
275
275
  marketingConsent: string
276
276
  }
277
+
278
+ /** @public */
279
+ export type OndoDepositStatus = 'pending' | 'confirmed'
280
+
281
+ /**
282
+ * Mirrors Ondo's `WalletDeposit` (the `GET /v1/wallet/deposits` result).
283
+ * Carries no id of its own — Ondo addresses a single deposit by `txid`.
284
+ * @public
285
+ */
286
+ export interface OndoWalletDeposit {
287
+ coin: string
288
+ size: string
289
+ status: OndoDepositStatus
290
+ txid: string
291
+ fromAddress: string
292
+ time: string
293
+ chainId: string
294
+ usdValue: string
295
+ currentConfirmations?: number
296
+ requiredConfirmations?: number
297
+ accountId?: string
298
+ /** Disambiguates two deposits carried by one transaction. */
299
+ logIndex?: string
300
+ }
301
+
302
+ /** @public */
303
+ export type OndoWithdrawalStatus =
304
+ | 'complete'
305
+ | 'failure'
306
+ | 'pending'
307
+ | 'cancelled'
308
+ | 'unknown'
309
+
310
+ /**
311
+ * Mirrors Ondo's `WalletWithdrawal` (the `GET /v1/wallet/withdrawals` result).
312
+ * @public
313
+ */
314
+ export interface OndoWalletWithdrawal {
315
+ coin: string
316
+ size: string
317
+ status: OndoWithdrawalStatus
318
+ address: string
319
+ withdrawal_id: string
320
+ txid: string
321
+ customer_withdrawal_id: string
322
+ time: string
323
+ chainId: string
324
+ accountId?: string
325
+ usdValue?: string
326
+ /** Fee in USD, not in the withdrawn asset. */
327
+ usdFee?: string
328
+ from?: OndoAccountWalletKey
329
+ }
330
+
331
+ /**
332
+ * One side of an Ondo value movement: an account id plus which of that
333
+ * account's two wallets held the value.
334
+ * @public
335
+ */
336
+ export interface OndoAccountWalletKey {
337
+ id: string
338
+ wallet: 'main' | 'margin'
339
+ }
@@ -30,6 +30,9 @@ const malformed = (message: string): OndoApiError =>
30
30
  new OndoApiError(`Ondo deposit-address response is malformed: ${message}`)
31
31
 
32
32
  const entriesFromResult = (result: unknown): unknown[] => {
33
+ if (result === null) {
34
+ return []
35
+ }
33
36
  if (Array.isArray(result)) {
34
37
  return result
35
38
  }
@@ -45,8 +48,9 @@ const entriesFromResult = (result: unknown): unknown[] => {
45
48
 
46
49
  /**
47
50
  * Extract the canonical Ethereum USDC address from Ondo's list response.
48
- * An empty list is a valid, unsatisfied setup state; malformed records are
49
- * errors so an API/schema failure cannot be mistaken for an absent address.
51
+ * A `null` result and an empty list are both valid, unsatisfied setup states;
52
+ * malformed records are errors so an API/schema failure cannot be mistaken for
53
+ * an absent address.
50
54
  */
51
55
  export const parseOndoDepositAddress = (result: unknown): Address | null => {
52
56
  const entries = entriesFromResult(result)
@@ -25,7 +25,12 @@ export {
25
25
  parseOndoDepositAddress,
26
26
  } from './depositAddress.js'
27
27
  export { estimateLiquidationPrice } from './liquidation.js'
28
- export { mapFundingActivity, mapLiquidationActivity } from './mapActivity.js'
28
+ export {
29
+ mapDepositActivity,
30
+ mapFundingActivity,
31
+ mapLiquidationActivity,
32
+ mapWithdrawalActivity,
33
+ } from './mapActivity.js'
29
34
  export { mapFill } from './mapFill.js'
30
35
  export {
31
36
  classifyAndMapOrders,
@@ -1,13 +1,17 @@
1
1
  import type {
2
+ DepositActivity,
2
3
  FundingActivity,
3
4
  LiquidationActivity,
4
5
  MarketDisplay,
6
+ WithdrawalActivity,
5
7
  } from '@lifi/perps-types'
6
8
  import { ActivityType } from '@lifi/perps-types'
7
9
  import { ONDO_PROVIDER_KEY } from '../constants.js'
8
10
  import type {
9
11
  OndoFundingFeeTransfer,
10
12
  OndoLiquidationEvent,
13
+ OndoWalletDeposit,
14
+ OndoWalletWithdrawal,
11
15
  } from '../types/wire.js'
12
16
 
13
17
  /**
@@ -37,25 +41,118 @@ export const mapFundingActivity = (
37
41
  }
38
42
 
39
43
  /**
40
- * Map an Ondo liquidation event to a {@link LiquidationActivity}. Ondo does
41
- * not report the account value at liquidation time, and its margin accounts
42
- * are cross-only, so `accountValue` is `'0'` and `leverageType` is `'cross'`.
44
+ * Map an Ondo liquidation event to a {@link LiquidationActivity}, or `null`
45
+ * when the event names no triggering position. Ondo margin accounts are
46
+ * cross-only, so `leverageType` is always `'cross'`. Ondo reports no account
47
+ * value at liquidation time, so `accountValue` stays absent. One Ondo event
48
+ * carries the whole cross-margin cascade in `triggeringPositions`, so every
49
+ * resolvable liquidated market and size reaches `liquidatedPositions`.
43
50
  *
51
+ * @param resolveMarket - Market identity for an Ondo market id, or `undefined`
52
+ * when the backend market list does not hold it. A triggering position the
53
+ * resolver cannot identify is dropped.
44
54
  * @public
45
55
  */
46
56
  export const mapLiquidationActivity = (
47
57
  event: OndoLiquidationEvent,
48
- resolveMarket: (market: string) => MarketDisplay
49
- ): LiquidationActivity => ({
50
- id: event.id,
58
+ resolveMarket: (market: string) => MarketDisplay | undefined
59
+ ): LiquidationActivity | null => {
60
+ const resolvedPositions = (event.triggeringPositions ?? []).flatMap((p) => {
61
+ const market = resolveMarket(p.market)
62
+ return market === undefined ? [] : [{ market, size: p.netQuantity }]
63
+ })
64
+ const [firstPosition, ...restPositions] = resolvedPositions
65
+ if (firstPosition === undefined) {
66
+ return null
67
+ }
68
+ return {
69
+ id: event.id,
70
+ provider: ONDO_PROVIDER_KEY,
71
+ timestamp: new Date(event.time).toISOString(),
72
+ type: ActivityType.LIQUIDATION,
73
+ ...(event.filledQuoteSize === undefined
74
+ ? {}
75
+ : { liquidatedNotionalPosition: event.filledQuoteSize }),
76
+ leverageType: 'cross',
77
+ liquidatedPositions: [firstPosition, ...restPositions],
78
+ }
79
+ }
80
+
81
+ // Ondo reports no transaction id on a deposit it has not yet matched to a
82
+ // chain transaction, so two such deposits would share the bare `deposit:`
83
+ // key. Fall back to the fields the venue does report.
84
+ const depositId = (deposit: OndoWalletDeposit): string => {
85
+ if (deposit.txid === '') {
86
+ return `deposit:${deposit.time}:${deposit.coin}:${deposit.size}`
87
+ }
88
+ return deposit.logIndex === undefined
89
+ ? `deposit:${deposit.txid}`
90
+ : `deposit:${deposit.txid}:${deposit.logIndex}`
91
+ }
92
+
93
+ /**
94
+ * Map an Ondo wallet deposit to a {@link DepositActivity}. Ondo carries no
95
+ * deposit id on the wire and addresses a single deposit by transaction id, so
96
+ * the id is `deposit:<txid>`, suffixed with `logIndex` when one transaction
97
+ * carried several deposits. `coin` is already a display symbol, so it is the
98
+ * normalized asset identity.
99
+ *
100
+ * @public
101
+ */
102
+ export const mapDepositActivity = (
103
+ deposit: OndoWalletDeposit
104
+ ): DepositActivity => ({
105
+ id: depositId(deposit),
51
106
  provider: ONDO_PROVIDER_KEY,
52
- timestamp: new Date(event.time).toISOString(),
53
- type: ActivityType.LIQUIDATION,
54
- liquidatedNotionalPosition: event.filledQuoteSize ?? '0',
55
- accountValue: '0',
56
- leverageType: 'cross',
57
- liquidatedPositions: (event.triggeringPositions ?? []).map((p) => ({
58
- market: resolveMarket(p.market),
59
- size: p.netQuantity,
60
- })),
107
+ timestamp: new Date(deposit.time).toISOString(),
108
+ type: ActivityType.DEPOSIT,
109
+ asset: deposit.coin,
110
+ amount: deposit.size,
111
+ ...(deposit.txid === ''
112
+ ? {}
113
+ : { explorerLink: `https://scan.li.fi/tx/${deposit.txid}` }),
61
114
  })
115
+
116
+ const SETTLING_WITHDRAWAL_STATUSES = new Set<string>([
117
+ 'complete',
118
+ 'pending',
119
+ 'unknown',
120
+ ])
121
+
122
+ /** Ondo denominates a withdrawal fee in USD, whatever asset it withdraws. */
123
+ const ONDO_WITHDRAWAL_FEE_SYMBOL = 'USD'
124
+
125
+ /**
126
+ * Map an Ondo wallet withdrawal to a {@link WithdrawalActivity}, or `null`
127
+ * when the venue reports a status under which no value left the account. Ondo
128
+ * charges the fee in USD, so `fee.asset` is `USD` and not the withdrawn asset.
129
+ *
130
+ * @public
131
+ */
132
+ export const mapWithdrawalActivity = (
133
+ withdrawal: OndoWalletWithdrawal
134
+ ): WithdrawalActivity | null => {
135
+ if (!SETTLING_WITHDRAWAL_STATUSES.has(withdrawal.status)) {
136
+ return null
137
+ }
138
+ return {
139
+ id: withdrawal.withdrawal_id,
140
+ provider: ONDO_PROVIDER_KEY,
141
+ timestamp: new Date(withdrawal.time).toISOString(),
142
+ type: ActivityType.WITHDRAWAL,
143
+ asset: withdrawal.coin,
144
+ amount: withdrawal.size,
145
+ // Loose equality also drops a `null` the live API may send for "no fee".
146
+ ...(withdrawal.usdFee == null
147
+ ? {}
148
+ : {
149
+ fee: {
150
+ amount: withdrawal.usdFee,
151
+ asset: ONDO_WITHDRAWAL_FEE_SYMBOL,
152
+ },
153
+ }),
154
+ ...(withdrawal.txid === ''
155
+ ? {}
156
+ : { explorerLink: `https://scan.li.fi/tx/${withdrawal.txid}` }),
157
+ }
158
+ }
@@ -35,7 +35,11 @@ export const mapFill = (fill: OndoFill, market: MarketDisplay): Fill => ({
35
35
  price: fill.price,
36
36
  status: FillStatus.FILLED,
37
37
  liquidity: fill.isMaker ? LiquidityRole.MAKER : LiquidityRole.TAKER,
38
- fee: new Big(fill.fee).minus(fill.feeRebate ?? '0').toFixed(),
38
+ // Ondo charges the fill fee in the market's quote asset.
39
+ fee: {
40
+ amount: new Big(fill.fee).minus(fill.feeRebate ?? '0').toFixed(),
41
+ asset: market.quoteAsset.displaySymbol,
42
+ },
39
43
  realizedPnl: fill.pnl,
40
44
  classification:
41
45
  fill.direction !== undefined
@@ -22,6 +22,10 @@ const toIso = (time: string): string => new Date(time).toISOString()
22
22
  const mapSide = (side: OndoOrder['side']): OrderSide =>
23
23
  side === 'buy' ? OrderSide.BUY : OrderSide.SELL
24
24
 
25
+ /** Resting quantity: submitted size minus filled size, in base-asset units. */
26
+ const deriveRemainingSize = (size: string, filledSize: string): string =>
27
+ new Big(size).minus(filledSize).toFixed()
28
+
25
29
  /** @public */
26
30
  export const mapOrderType = (type: OndoOrderType): OrderType => {
27
31
  switch (type) {
@@ -88,7 +92,11 @@ export const isTriggerOrder = (order: OndoOrder): boolean =>
88
92
  order.stopOrderType !== undefined ||
89
93
  order.triggerPrice !== undefined
90
94
 
91
- /** Map an active non-trigger Ondo order to an {@link OpenOrder}. @public */
95
+ /**
96
+ * Map an active non-trigger Ondo order to an {@link OpenOrder}. Ondo reports
97
+ * the submitted and filled quantities only, so `remainingSize` is derived.
98
+ * @public
99
+ */
92
100
  export const mapOrder = (
93
101
  order: OndoOrder,
94
102
  market: MarketDisplay
@@ -97,7 +105,8 @@ export const mapOrder = (
97
105
  market,
98
106
  side: mapSide(order.side),
99
107
  type: mapOrderType(order.type),
100
- size: order.size,
108
+ originalSize: new Big(order.size).toFixed(),
109
+ remainingSize: deriveRemainingSize(order.size, order.filledSize),
101
110
  price: order.price,
102
111
  filledSize: order.filledSize,
103
112
  reduceOnly: order.reduceOnly ?? false,
@@ -177,7 +186,7 @@ export const mapOrderDetail = (
177
186
  type: mapOrderType(order.type),
178
187
  price: order.price,
179
188
  originalSize: order.size,
180
- remainingSize: new Big(order.size).minus(filled).toFixed(),
189
+ remainingSize: deriveRemainingSize(order.size, order.filledSize),
181
190
  filledSize: order.filledSize,
182
191
  timeInForce:
183
192
  order.timeInForce === undefined