@lifi/perps-sdk-provider-lighter 20.0.0 → 22.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 (135) hide show
  1. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  2. package/dist/cjs/LighterProvider.js +70 -24
  3. package/dist/cjs/LighterProvider.js.map +1 -1
  4. package/dist/cjs/constants.d.ts +1 -0
  5. package/dist/cjs/constants.d.ts.map +1 -1
  6. package/dist/cjs/constants.js +2 -1
  7. package/dist/cjs/constants.js.map +1 -1
  8. package/dist/cjs/index.d.ts +1 -1
  9. package/dist/cjs/index.d.ts.map +1 -1
  10. package/dist/cjs/index.js +2 -1
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  13. package/dist/cjs/signers/LighterReadOnlyTokenManager.js +1 -0
  14. package/dist/cjs/signers/LighterReadOnlyTokenManager.js.map +1 -1
  15. package/dist/cjs/signers/signActions.d.ts.map +1 -1
  16. package/dist/cjs/signers/signActions.js +13 -5
  17. package/dist/cjs/signers/signActions.js.map +1 -1
  18. package/dist/cjs/types/account.d.ts +8 -3
  19. package/dist/cjs/types/account.d.ts.map +1 -1
  20. package/dist/cjs/types/activity.d.ts +7 -6
  21. package/dist/cjs/types/activity.d.ts.map +1 -1
  22. package/dist/cjs/types/fill.d.ts +3 -1
  23. package/dist/cjs/types/fill.d.ts.map +1 -1
  24. package/dist/cjs/types/order.d.ts +6 -1
  25. package/dist/cjs/types/order.d.ts.map +1 -1
  26. package/dist/cjs/utils/apiClient.d.ts +2 -1
  27. package/dist/cjs/utils/apiClient.d.ts.map +1 -1
  28. package/dist/cjs/utils/apiClient.js +28 -12
  29. package/dist/cjs/utils/apiClient.js.map +1 -1
  30. package/dist/cjs/utils/fetchDetailedAccount.d.ts +2 -2
  31. package/dist/cjs/utils/fetchDetailedAccount.d.ts.map +1 -1
  32. package/dist/cjs/utils/fetchDetailedAccount.js +9 -3
  33. package/dist/cjs/utils/fetchDetailedAccount.js.map +1 -1
  34. package/dist/cjs/utils/lighterErrorCode.d.ts +3 -0
  35. package/dist/cjs/utils/lighterErrorCode.d.ts.map +1 -0
  36. package/dist/cjs/utils/lighterErrorCode.js +17 -0
  37. package/dist/cjs/utils/lighterErrorCode.js.map +1 -0
  38. package/dist/cjs/utils/ohlcvInterval.d.ts.map +1 -1
  39. package/dist/cjs/utils/ohlcvInterval.js +0 -1
  40. package/dist/cjs/utils/ohlcvInterval.js.map +1 -1
  41. package/dist/cjs/utils/wireList.d.ts +2 -0
  42. package/dist/cjs/utils/wireList.d.ts.map +1 -0
  43. package/dist/cjs/utils/wireList.js +6 -0
  44. package/dist/cjs/utils/wireList.js.map +1 -0
  45. package/dist/esm/LighterProvider.d.ts.map +1 -1
  46. package/dist/esm/LighterProvider.js +91 -32
  47. package/dist/esm/LighterProvider.js.map +1 -1
  48. package/dist/esm/constants.d.ts +14 -2
  49. package/dist/esm/constants.d.ts.map +1 -1
  50. package/dist/esm/constants.js +14 -2
  51. package/dist/esm/constants.js.map +1 -1
  52. package/dist/esm/index.d.ts +1 -1
  53. package/dist/esm/index.d.ts.map +1 -1
  54. package/dist/esm/index.js +1 -1
  55. package/dist/esm/index.js.map +1 -1
  56. package/dist/esm/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  57. package/dist/esm/signers/LighterReadOnlyTokenManager.js +3 -0
  58. package/dist/esm/signers/LighterReadOnlyTokenManager.js.map +1 -1
  59. package/dist/esm/signers/signActions.d.ts.map +1 -1
  60. package/dist/esm/signers/signActions.js +22 -8
  61. package/dist/esm/signers/signActions.js.map +1 -1
  62. package/dist/esm/types/account.d.ts +15 -3
  63. package/dist/esm/types/account.d.ts.map +1 -1
  64. package/dist/esm/types/account.js +1 -0
  65. package/dist/esm/types/account.js.map +1 -1
  66. package/dist/esm/types/activity.d.ts +8 -6
  67. package/dist/esm/types/activity.d.ts.map +1 -1
  68. package/dist/esm/types/activity.js +1 -0
  69. package/dist/esm/types/activity.js.map +1 -1
  70. package/dist/esm/types/fill.d.ts +5 -1
  71. package/dist/esm/types/fill.d.ts.map +1 -1
  72. package/dist/esm/types/fill.js +1 -0
  73. package/dist/esm/types/fill.js.map +1 -1
  74. package/dist/esm/types/order.d.ts +15 -1
  75. package/dist/esm/types/order.d.ts.map +1 -1
  76. package/dist/esm/types/order.js +1 -0
  77. package/dist/esm/types/order.js.map +1 -1
  78. package/dist/esm/utils/apiClient.d.ts +23 -15
  79. package/dist/esm/utils/apiClient.d.ts.map +1 -1
  80. package/dist/esm/utils/apiClient.js +55 -26
  81. package/dist/esm/utils/apiClient.js.map +1 -1
  82. package/dist/esm/utils/fetchDetailedAccount.d.ts +3 -2
  83. package/dist/esm/utils/fetchDetailedAccount.d.ts.map +1 -1
  84. package/dist/esm/utils/fetchDetailedAccount.js +10 -3
  85. package/dist/esm/utils/fetchDetailedAccount.js.map +1 -1
  86. package/dist/esm/utils/lighterErrorCode.d.ts +7 -0
  87. package/dist/esm/utils/lighterErrorCode.d.ts.map +1 -0
  88. package/dist/esm/utils/lighterErrorCode.js +26 -0
  89. package/dist/esm/utils/lighterErrorCode.js.map +1 -0
  90. package/dist/esm/utils/ohlcvInterval.d.ts.map +1 -1
  91. package/dist/esm/utils/ohlcvInterval.js +1 -2
  92. package/dist/esm/utils/ohlcvInterval.js.map +1 -1
  93. package/dist/esm/utils/wireList.d.ts +10 -0
  94. package/dist/esm/utils/wireList.d.ts.map +1 -0
  95. package/dist/esm/utils/wireList.js +10 -0
  96. package/dist/esm/utils/wireList.js.map +1 -0
  97. package/dist/types/LighterProvider.d.ts.map +1 -1
  98. package/dist/types/constants.d.ts +14 -2
  99. package/dist/types/constants.d.ts.map +1 -1
  100. package/dist/types/index.d.ts +1 -1
  101. package/dist/types/index.d.ts.map +1 -1
  102. package/dist/types/signers/LighterReadOnlyTokenManager.d.ts.map +1 -1
  103. package/dist/types/signers/signActions.d.ts.map +1 -1
  104. package/dist/types/types/account.d.ts +15 -3
  105. package/dist/types/types/account.d.ts.map +1 -1
  106. package/dist/types/types/activity.d.ts +8 -6
  107. package/dist/types/types/activity.d.ts.map +1 -1
  108. package/dist/types/types/fill.d.ts +5 -1
  109. package/dist/types/types/fill.d.ts.map +1 -1
  110. package/dist/types/types/order.d.ts +15 -1
  111. package/dist/types/types/order.d.ts.map +1 -1
  112. package/dist/types/utils/apiClient.d.ts +23 -15
  113. package/dist/types/utils/apiClient.d.ts.map +1 -1
  114. package/dist/types/utils/fetchDetailedAccount.d.ts +3 -2
  115. package/dist/types/utils/fetchDetailedAccount.d.ts.map +1 -1
  116. package/dist/types/utils/lighterErrorCode.d.ts +7 -0
  117. package/dist/types/utils/lighterErrorCode.d.ts.map +1 -0
  118. package/dist/types/utils/ohlcvInterval.d.ts.map +1 -1
  119. package/dist/types/utils/wireList.d.ts +10 -0
  120. package/dist/types/utils/wireList.d.ts.map +1 -0
  121. package/package.json +3 -3
  122. package/src/LighterProvider.ts +153 -62
  123. package/src/constants.ts +15 -2
  124. package/src/index.ts +1 -0
  125. package/src/signers/LighterReadOnlyTokenManager.ts +3 -0
  126. package/src/signers/signActions.ts +28 -11
  127. package/src/types/account.ts +17 -3
  128. package/src/types/activity.ts +9 -6
  129. package/src/types/fill.ts +6 -1
  130. package/src/types/order.ts +17 -1
  131. package/src/utils/apiClient.ts +68 -25
  132. package/src/utils/fetchDetailedAccount.ts +13 -6
  133. package/src/utils/lighterErrorCode.ts +30 -0
  134. package/src/utils/ohlcvInterval.ts +1 -2
  135. package/src/utils/wireList.ts +9 -0
@@ -1,11 +1,12 @@
1
1
  import type { Address } from 'viem';
2
- import type { LtDetailedAccount } from '../types/index.js';
2
+ import type { LtAccount } from '../types/index.js';
3
3
  import type { LighterApiClient } from './apiClient.js';
4
4
  /**
5
5
  * Look up the Lighter account for an L1 address via `/api/v1/account`,
6
6
  * discriminating Lighter's account-not-found body code from generic failures.
7
+ * The returned account carries real arrays for `positions` and `assets`.
7
8
  *
8
9
  * @public
9
10
  */
10
- export declare const fetchDetailedAccount: (client: LighterApiClient, address: Address) => Promise<LtDetailedAccount>;
11
+ export declare const fetchDetailedAccount: (client: LighterApiClient, address: Address) => Promise<LtAccount>;
11
12
  //# sourceMappingURL=fetchDetailedAccount.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetchDetailedAccount.d.ts","sourceRoot":"","sources":["../../../src/utils/fetchDetailedAccount.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,gBAAgB,EACxB,SAAS,OAAO,KACf,OAAO,CAAC,iBAAiB,CA6B3B,CAAA"}
1
+ {"version":3,"file":"fetchDetailedAccount.d.ts","sourceRoot":"","sources":["../../../src/utils/fetchDetailedAccount.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,mBAAmB,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGtD;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,gBAAgB,EACxB,SAAS,OAAO,KACf,OAAO,CAAC,SAAS,CAkCnB,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { PerpsErrorCode } from '@lifi/perps-types';
2
+ /**
3
+ * Maps a Lighter body error `code` to the `PerpsErrorCode` it names. Returns
4
+ * `undefined` for every other code so the status-driven layer decides.
5
+ */
6
+ export declare const lighterErrorCodeFromBody: (code: number | undefined) => PerpsErrorCode | undefined;
7
+ //# sourceMappingURL=lighterErrorCode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lighterErrorCode.d.ts","sourceRoot":"","sources":["../../../src/utils/lighterErrorCode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAsBlD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,MAAM,MAAM,GAAG,SAAS,KACvB,cAAc,GAAG,SACiD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ohlcvInterval.d.ts","sourceRoot":"","sources":["../../../src/utils/ohlcvInterval.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAsBtD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,aAAa,KAAG,MAUrD,CAAA"}
1
+ {"version":3,"file":"ohlcvInterval.d.ts","sourceRoot":"","sources":["../../../src/utils/ohlcvInterval.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAqBtD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,aAAa,KAAG,MAUrD,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Read a Lighter wire list member. Lighter serializes an empty list as JSON
3
+ * `null` rather than `[]`, so every list the SDK reads off a REST body passes
4
+ * through this at the response boundary and every mapper downstream works on a
5
+ * real array.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare const wireList: <T>(value: T[] | null | undefined) => T[];
10
+ //# sourceMappingURL=wireList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wireList.d.ts","sourceRoot":"","sources":["../../../src/utils/wireList.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,KAAG,CAAC,EAAiB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/perps-sdk-provider-lighter",
3
- "version": "20.0.0",
3
+ "version": "22.0.0",
4
4
  "description": "Lighter provider plugin for @lifi/perps-sdk",
5
5
  "homepage": "https://github.com/lifinance/perps-sdk",
6
6
  "bugs": {
@@ -44,10 +44,10 @@
44
44
  "dependencies": {
45
45
  "big.js": "^7.0.1",
46
46
  "viem": "^2.55.18",
47
- "@lifi/perps-types": "^12.0.1"
47
+ "@lifi/perps-types": "^12.1.0"
48
48
  },
49
49
  "peerDependencies": {
50
- "@lifi/perps-sdk": "^12.0.0"
50
+ "@lifi/perps-sdk": "^12.1.0"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public"
@@ -4,6 +4,7 @@ import {
4
4
  getAssetRegistry,
5
5
  getMarketRegistry,
6
6
  getProviders,
7
+ isActiveOrderStatus,
7
8
  localStorageAdapter,
8
9
  PerpsError,
9
10
  type PerpsProviderPlugin,
@@ -66,6 +67,7 @@ import {
66
67
  DEFAULT_TRADES_LIMIT,
67
68
  LIGHTER_ALL_MARKETS_WILDCARD,
68
69
  LIGHTER_BASE_FEE_TIER,
70
+ LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX,
69
71
  LIGHTER_FEE_TICK_SCALE,
70
72
  LIGHTER_HISTORY_PAGE_SIZE,
71
73
  LIGHTER_MAINNET_DEPLOYMENT,
@@ -86,15 +88,22 @@ import {
86
88
  lighterSignActions,
87
89
  } from './signers/signActions.js'
88
90
  import type {
91
+ LtAccount,
89
92
  LtAccountLimits,
93
+ LtAccountOrdersResponse,
94
+ LtDepositHistoryItem,
90
95
  LtDepositHistoryResponse,
91
- LtDetailedAccount,
92
96
  LtDetailedAccountPosition,
97
+ LtLiquidation,
93
98
  LtLiquidationsResponse,
99
+ LtOrder,
94
100
  LtOrdersResponse,
101
+ LtPositionFunding,
95
102
  LtPositionFundingsResponse,
96
103
  LtTradesResponse,
104
+ LtTransfer,
97
105
  LtTransferHistoryResponse,
106
+ LtWithdrawHistoryItem,
98
107
  LtWithdrawHistoryResponse,
99
108
  } from './types/index.js'
100
109
  import { LT_MARGIN_MODE_CROSS, LT_MARGIN_MODE_ISOLATED } from './types/index.js'
@@ -132,6 +141,7 @@ import {
132
141
  fetchRegisteredApiKey,
133
142
  normalizeLighterPublicKey,
134
143
  } from './utils/registeredApiKey.js'
144
+ import { wireList } from './utils/wireList.js'
135
145
 
136
146
  const ZERO_FEE_TIER = { maker: '0', taker: '0' }
137
147
 
@@ -158,6 +168,13 @@ const orderCountFor = (p: LtDetailedAccountPosition): number =>
158
168
  */
159
169
  const TX_HASH_PATTERN = /^[0-9a-f]{80}$/
160
170
 
171
+ /**
172
+ * Lighter's `client_order_index` is an int64 and `accountOrders` takes it as a
173
+ * decimal string, so a prefixed id's suffix goes to the venue verbatim rather
174
+ * than through a lossy JS number.
175
+ */
176
+ const CLIENT_ORDER_INDEX_PATTERN = /^\d+$/
177
+
161
178
  const INACTIVE_ORDERS_LOOKUP_LIMIT = 100
162
179
 
163
180
  /** Activity surfaces whose rows name a market, so they need the market registry. */
@@ -560,19 +577,35 @@ export const createLighterProvider = (
560
577
  account_index: accountIndex,
561
578
  })
562
579
 
563
- const fetchActiveOrdersForMarket = (
580
+ const fetchActiveOrders = async (
564
581
  client: LighterApiClient,
565
582
  authToken: string,
566
583
  accountIndex: number,
567
584
  marketId: number
568
- ): Promise<LtOrdersResponse> =>
569
- client.getAuthed<LtOrdersResponse>(
585
+ ): Promise<LtOrdersResponse & { orders: LtOrder[] }> => {
586
+ const response = await client.getAuthed<LtOrdersResponse>(
570
587
  '/api/v1/accountActiveOrders',
571
588
  authToken,
572
589
  { account_index: accountIndex, market_id: marketId }
573
590
  )
591
+ return { ...response, orders: wireList(response.orders) }
592
+ }
574
593
 
575
- const deriveOrderBearingMarketIds = (account: LtDetailedAccount): number[] =>
594
+ const fetchOrdersByClientOrderIndex = async (
595
+ client: LighterApiClient,
596
+ authToken: string,
597
+ accountIndex: number,
598
+ clientOrderIndex: string
599
+ ): Promise<LtOrder[]> => {
600
+ const response = await client.getAuthed<LtAccountOrdersResponse>(
601
+ '/api/v1/accountOrders',
602
+ authToken,
603
+ { account_index: accountIndex, client_order_indexes: clientOrderIndex }
604
+ )
605
+ return wireList(response.orders)
606
+ }
607
+
608
+ const deriveOrderBearingMarketIds = (account: LtAccount): number[] =>
576
609
  account.positions
577
610
  .filter((p) => orderCountFor(p) > 0)
578
611
  .map((p) => p.market_id)
@@ -585,11 +618,15 @@ export const createLighterProvider = (
585
618
  requested: ActivityType[] | undefined,
586
619
  inputCursor: LighterActivityCursor | undefined
587
620
  ): Promise<{
588
- deposits: LtDepositHistoryResponse
589
- withdraws: LtWithdrawHistoryResponse
590
- fundings: LtPositionFundingsResponse
591
- liquidations: LtLiquidationsResponse
592
- transfers: LtTransferHistoryResponse
621
+ deposits: LtDepositHistoryResponse & { deposits: LtDepositHistoryItem[] }
622
+ withdraws: LtWithdrawHistoryResponse & {
623
+ withdraws: LtWithdrawHistoryItem[]
624
+ }
625
+ fundings: LtPositionFundingsResponse & {
626
+ position_fundings: LtPositionFunding[]
627
+ }
628
+ liquidations: LtLiquidationsResponse & { liquidations: LtLiquidation[] }
629
+ transfers: LtTransferHistoryResponse & { transfers: LtTransfer[] }
593
630
  }> => {
594
631
  const wantsType = (t: ActivityType): boolean =>
595
632
  requested === undefined || requested.includes(t)
@@ -643,7 +680,6 @@ export const createLighterProvider = (
643
680
  token,
644
681
  {
645
682
  account_index: accountIndex,
646
- market_id: LIGHTER_ALL_MARKETS_WILDCARD,
647
683
  limit: LIGHTER_HISTORY_PAGE_SIZE,
648
684
  ...cursorParam('fundings'),
649
685
  }
@@ -670,7 +706,30 @@ export const createLighterProvider = (
670
706
  : Promise.resolve(empty({ code: 0, transfers: [] })),
671
707
  ])
672
708
 
673
- return { deposits, withdraws, fundings, liquidations, transfers }
709
+ return {
710
+ deposits: {
711
+ ...deposits,
712
+ deposits: wireList<LtDepositHistoryItem>(deposits.deposits),
713
+ },
714
+ withdraws: {
715
+ ...withdraws,
716
+ withdraws: wireList<LtWithdrawHistoryItem>(withdraws.withdraws),
717
+ },
718
+ fundings: {
719
+ ...fundings,
720
+ position_fundings: wireList<LtPositionFunding>(
721
+ fundings.position_fundings
722
+ ),
723
+ },
724
+ liquidations: {
725
+ ...liquidations,
726
+ liquidations: wireList<LtLiquidation>(liquidations.liquidations),
727
+ },
728
+ transfers: {
729
+ ...transfers,
730
+ transfers: wireList<LtTransfer>(transfers.transfers),
731
+ },
732
+ }
674
733
  }
675
734
 
676
735
  const resolveAccountExists = async (
@@ -924,7 +983,7 @@ export const createLighterProvider = (
924
983
  if (params.market.categoryId === LIGHTER_SPOT_CATEGORY_ID) {
925
984
  return undefined
926
985
  }
927
- let account: LtDetailedAccount
986
+ let account: LtAccount
928
987
  try {
929
988
  account = await fetchDetailedAccount(apiClient(opts), params.address)
930
989
  } catch (err) {
@@ -982,21 +1041,17 @@ export const createLighterProvider = (
982
1041
  registry.sync(),
983
1042
  ])
984
1043
 
985
- const marketIds =
1044
+ const marketId =
986
1045
  params.marketId === undefined
987
- ? deriveOrderBearingMarketIds(account)
988
- : [Number(registry.require(params.marketId).id)]
1046
+ ? LIGHTER_ALL_MARKETS_WILDCARD
1047
+ : Number(registry.require(params.marketId).id)
989
1048
 
990
- const responses = await retryOnRevoked(opts, params.address, token, (t) =>
991
- Promise.all(
992
- marketIds.map((id) =>
993
- fetchActiveOrdersForMarket(client, t, account.index, id)
994
- )
995
- )
1049
+ const response = await retryOnRevoked(opts, params.address, token, (t) =>
1050
+ fetchActiveOrders(client, t, account.index, marketId)
996
1051
  )
997
1052
 
998
1053
  const { openOrders, triggerOrders } = classifyAndMapOrders(
999
- responses.flatMap((r) => r.orders),
1054
+ response.orders,
1000
1055
  (marketIndex) => registry.require(String(marketIndex))
1001
1056
  )
1002
1057
 
@@ -1030,9 +1085,7 @@ export const createLighterProvider = (
1030
1085
  : [Number(registry.require(params.marketId).id)]
1031
1086
  const responses = await retryOnRevoked(opts, params.address, token, (t) =>
1032
1087
  Promise.all(
1033
- marketIds.map((id) =>
1034
- fetchActiveOrdersForMarket(client, t, account.index, id)
1035
- )
1088
+ marketIds.map((id) => fetchActiveOrders(client, t, account.index, id))
1036
1089
  )
1037
1090
  )
1038
1091
 
@@ -1052,6 +1105,14 @@ export const createLighterProvider = (
1052
1105
  return twaps
1053
1106
  },
1054
1107
 
1108
+ /**
1109
+ * Resolves either reference Lighter hands out for an order: a bare
1110
+ * `order_index` — the value `Order.orderId` carries, as streamed by
1111
+ * `orderUpdates` / `fills` — or a `client_order_index` prefixed with
1112
+ * {@link LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX}, which a signed placement
1113
+ * step returns and which resolves before the venue has assigned an
1114
+ * `order_index`.
1115
+ */
1055
1116
  async getOrder(
1056
1117
  params: ProviderGetOrderParams,
1057
1118
  opts?: SDKRequestOptions
@@ -1073,41 +1134,75 @@ export const createLighterProvider = (
1073
1134
  registry.sync(),
1074
1135
  ])
1075
1136
 
1076
- // Native `order_index` route only. The cross-provider `Order.orderId`
1077
- // for Lighter is `String(order_index)` — see `mapOrder`. A tx-hash route
1078
- // would require mapping the caller's executeAction tx hash → wasm nonce
1079
- // → matching order, which the LI.FI backend did via its `UserAction`
1080
- // table; the SDK has no equivalent persistence, so we refuse rather
1081
- // than mis-resolve.
1137
+ const notFound = (): PerpsError =>
1138
+ new PerpsError(
1139
+ PerpsErrorCode.OrderNotFound,
1140
+ `Lighter order ${params.id} not found for ${params.address}`
1141
+ )
1142
+ const detail = (order: LtOrder): Order =>
1143
+ mapOrderDetail(order, registry.require(String(order.market_index)))
1144
+
1145
+ // A tx-hash route would require mapping the caller's executeAction tx
1146
+ // hash → wasm nonce → matching order, which the LI.FI backend did via
1147
+ // its `UserAction` table; the SDK has no equivalent persistence, so we
1148
+ // refuse rather than mis-resolve.
1082
1149
  if (TX_HASH_PATTERN.test(params.id)) {
1083
1150
  throw new PerpsError(
1084
1151
  PerpsErrorCode.OrderNotFound,
1085
1152
  `Lighter order id "${params.id}" looks like a tx hash. The SDK ` +
1086
- `resolves orders by Lighter \`order_index\` only — surface the orderId ` +
1087
- `from the orderUpdates / fills WS stream and pass it here.`
1153
+ `resolves orders by Lighter \`order_index\` or by a ` +
1154
+ `\`${LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX}<index>\` id — surface the ` +
1155
+ `orderId from the orderUpdates / fills WS stream, or the ` +
1156
+ `clientOrderIndex from the signed placement step, and pass it here.`
1088
1157
  )
1089
1158
  }
1090
- const predicate: (o: { order_index: number }) => boolean = (o) =>
1091
- String(o.order_index) === params.id
1092
1159
 
1093
- const marketIds = deriveOrderBearingMarketIds(account)
1094
- const activeResponses = await retryOnRevoked(
1095
- opts,
1096
- params.address,
1097
- token,
1098
- (t) =>
1099
- Promise.all(
1100
- marketIds.map((id) =>
1101
- fetchActiveOrdersForMarket(client, t, account.index, id)
1102
- )
1160
+ if (params.id.startsWith(LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX)) {
1161
+ const clientOrderIndex = params.id.slice(
1162
+ LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX.length
1163
+ )
1164
+ if (!CLIENT_ORDER_INDEX_PATTERN.test(clientOrderIndex)) {
1165
+ throw new PerpsError(
1166
+ PerpsErrorCode.SDKError,
1167
+ `Lighter order id "${params.id}" names a client order index, but ` +
1168
+ `"${clientOrderIndex}" is not a decimal integer.`
1103
1169
  )
1104
- )
1105
-
1106
- for (const response of activeResponses) {
1107
- const hit = response.orders.find(predicate as (o: unknown) => boolean)
1108
- if (hit !== undefined) {
1109
- return mapOrderDetail(hit, registry.require(String(hit.market_index)))
1110
1170
  }
1171
+ const orders = await retryOnRevoked(opts, params.address, token, (t) =>
1172
+ fetchOrdersByClientOrderIndex(
1173
+ client,
1174
+ t,
1175
+ account.index,
1176
+ clientOrderIndex
1177
+ )
1178
+ )
1179
+ // `accountOrders` reaches active and inactive rows alike, so a reused
1180
+ // client order index can match more than one row. Prefer the live one.
1181
+ const matches = orders
1182
+ .filter((o) => String(o.client_order_index) === clientOrderIndex)
1183
+ .map(detail)
1184
+ const hit =
1185
+ matches.find((o) => isActiveOrderStatus(o.status)) ?? matches[0]
1186
+ if (hit === undefined) {
1187
+ throw notFound()
1188
+ }
1189
+ return hit
1190
+ }
1191
+
1192
+ const byOrderIndex = (o: LtOrder): boolean =>
1193
+ String(o.order_index) === params.id
1194
+
1195
+ const active = await retryOnRevoked(opts, params.address, token, (t) =>
1196
+ fetchActiveOrders(
1197
+ client,
1198
+ t,
1199
+ account.index,
1200
+ LIGHTER_ALL_MARKETS_WILDCARD
1201
+ )
1202
+ )
1203
+ const activeHit = active.orders.find(byOrderIndex)
1204
+ if (activeHit !== undefined) {
1205
+ return detail(activeHit)
1111
1206
  }
1112
1207
 
1113
1208
  const inactive = await retryOnRevoked(opts, params.address, token, (t) =>
@@ -1117,15 +1212,11 @@ export const createLighterProvider = (
1117
1212
  limit: INACTIVE_ORDERS_LOOKUP_LIMIT,
1118
1213
  })
1119
1214
  )
1120
- const hit = inactive.orders.find(predicate as (o: unknown) => boolean)
1121
- if (hit !== undefined) {
1122
- return mapOrderDetail(hit, registry.require(String(hit.market_index)))
1215
+ const inactiveHit = wireList(inactive.orders).find(byOrderIndex)
1216
+ if (inactiveHit === undefined) {
1217
+ throw notFound()
1123
1218
  }
1124
-
1125
- throw new PerpsError(
1126
- PerpsErrorCode.OrderNotFound,
1127
- `Lighter order ${params.id} not found for ${params.address}`
1128
- )
1219
+ return detail(inactiveHit)
1129
1220
  },
1130
1221
 
1131
1222
  async getFills(
@@ -1170,7 +1261,7 @@ export const createLighterProvider = (
1170
1261
  // drops only its own row instead of rejecting the whole page. The
1171
1262
  // registry warns once per unresolved id. A delisted market still
1172
1263
  // resolves, so its rows stay.
1173
- const items = response.trades.flatMap((t): Fill[] => {
1264
+ const items = wireList(response.trades).flatMap((t): Fill[] => {
1174
1265
  const market = registry.get(String(t.market_id))
1175
1266
  if (market === undefined) {
1176
1267
  return []
@@ -1297,7 +1388,7 @@ export const createLighterProvider = (
1297
1388
  }
1298
1389
  const price = toBigOrNull(l.trade.price)
1299
1390
  const size = toBigOrNull(l.trade.size)
1300
- const marginMode = l.info.positions.find(
1391
+ const marginMode = wireList(l.info.positions).find(
1301
1392
  (p) => p.market_id === l.market_id
1302
1393
  )?.margin_mode
1303
1394
  // Lighter reports the margin mode as an integer, so the venue
package/src/constants.ts CHANGED
@@ -204,13 +204,26 @@ export const LIGHTER_BASE_FEE_TIER: FeeTier = { maker: '0', taker: '0' }
204
204
 
205
205
  /**
206
206
  * Wildcard `market_id` accepted by Lighter's per-market account endpoints to
207
- * indicate "every market". Used on `accountInactiveOrders`, `positionFunding`,
208
- * `liquidations`.
207
+ * indicate "every market". Used on `accountActiveOrders`,
208
+ * `accountInactiveOrders` and `liquidations`. It is also the venue's own
209
+ * documented default for `accountActiveOrders.market_id`.
209
210
  *
210
211
  * @internal
211
212
  */
212
213
  export const LIGHTER_ALL_MARKETS_WILDCARD = 255
213
214
 
215
+ /**
216
+ * Prefix that marks a `getOrder` id as a Lighter `client_order_index` rather
217
+ * than an `order_index`. Compose the id as
218
+ * `` `${LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX}${clientOrderIndex}` `` from the
219
+ * `clientOrderIndex` a signed placement step returns. Both references are
220
+ * decimal integers, so without the prefix `getOrder` could resolve one as the
221
+ * other and return an unrelated order.
222
+ *
223
+ * @public
224
+ */
225
+ export const LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX = 'client_order_index:'
226
+
214
227
  /**
215
228
  * Lighter caps `limit` at 100 on positionFunding/liquidations endpoints.
216
229
  *
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export {
23
23
  DEFAULT_LIGHTER_EXPLORER_TX_BASE_URL,
24
24
  DEFAULT_LIGHTER_REST_URL,
25
25
  DEFAULT_LIGHTER_WS_URL,
26
+ LIGHTER_CLIENT_ORDER_INDEX_ID_PREFIX,
26
27
  LIGHTER_COLLATERAL_ASSETS,
27
28
  LIGHTER_MAINNET_DEPLOYMENT,
28
29
  LIGHTER_MAINNET_SIGNER_CHAIN_ID,
@@ -218,9 +218,12 @@ const isLighterReadOnlyToken = (
218
218
  >
219
219
  // Accept a record that carries no `tokenId`: rejecting one would discard a
220
220
  // usable token and force an unnecessary replacement.
221
+ // A token carrying a line break cannot go in the `Authorization` header, so
222
+ // reject the record and let the manager create a replacement.
221
223
  return (
222
224
  typeof token === 'string' &&
223
225
  token.length > 0 &&
226
+ !/[\r\n]/.test(token) &&
224
227
  typeof expiry === 'number' &&
225
228
  Number.isFinite(expiry) &&
226
229
  (scope === 'single' || scope === 'all') &&
@@ -230,13 +230,30 @@ async function signStandardWasmAction(
230
230
  apiKeyIndex: apiKey.apiKeyIndex,
231
231
  accountIndex: apiKey.accountIndex,
232
232
  })
233
+ const clientOrderIndex = clientOrderIndexOf(step.wasmSignParams)
233
234
  return {
234
235
  action: step.action,
235
236
  wasmSignParams: step.wasmSignParams,
236
237
  signedTx,
238
+ ...(clientOrderIndex === undefined ? {} : { clientOrderIndex }),
237
239
  }
238
240
  }
239
241
 
242
+ /**
243
+ * Project the signed order's `client_order_index` so the caller can resolve it
244
+ * through `getOrder` before the venue assigns an `order_index`. Only the
245
+ * order-placing actions carry one, and the value is read back off the params
246
+ * that were signed rather than generated a second time.
247
+ */
248
+ function clientOrderIndexOf(
249
+ wasmSignParams: Record<string, unknown>
250
+ ): string | undefined {
251
+ const value = wasmSignParams.client_order_index
252
+ return typeof value === 'number' && Number.isInteger(value)
253
+ ? String(value)
254
+ : undefined
255
+ }
256
+
240
257
  /**
241
258
  * REGISTER_API_KEY flow:
242
259
  * 1. Look up the user's Lighter accountIndex (Lighter REST).
@@ -484,11 +501,11 @@ async function executeTokenAuthMutation(
484
501
  }
485
502
  }
486
503
 
487
- const { path, params } = buildTokenAuthMutationRequest(step, authToken)
504
+ const { path, params } = buildTokenAuthMutationRequest(step)
488
505
  const { status, data } = await deps.apiClient.postForm<{
489
506
  code?: number
490
507
  message?: string
491
- }>(path, params)
508
+ }>(path, authToken, params)
492
509
 
493
510
  const code = data?.code
494
511
  const referralAlreadyApplied =
@@ -533,14 +550,14 @@ async function readAppliedReferralCode(
533
550
  }
534
551
 
535
552
  /**
536
- * Map a token-authenticated mutation step to its Lighter endpoint and
537
- * form body, mirroring the backend's request contract. The auth token is the
538
- * `auth` field; the remaining fields come from `wasmSignParams`.
553
+ * Map a token-authenticated mutation step to its Lighter endpoint and form
554
+ * body. The fields come from `wasmSignParams`; the auth token travels in the
555
+ * `Authorization` header, not the body.
539
556
  */
540
- function buildTokenAuthMutationRequest(
541
- step: WasmBlobActionStep,
542
- authToken: string
543
- ): { path: string; params: ApiParams } {
557
+ function buildTokenAuthMutationRequest(step: WasmBlobActionStep): {
558
+ path: string
559
+ params: ApiParams
560
+ } {
544
561
  const kind = step.wasmSignParams.kind
545
562
  if (kind === 'changeAccountTier') {
546
563
  const { account_index, new_tier } = step.wasmSignParams as {
@@ -555,7 +572,7 @@ function buildTokenAuthMutationRequest(
555
572
  }
556
573
  return {
557
574
  path: '/api/v1/changeAccountTier',
558
- params: { auth: authToken, account_index, new_tier },
575
+ params: { account_index, new_tier },
559
576
  }
560
577
  }
561
578
  if (kind === 'referralUse') {
@@ -572,7 +589,7 @@ function buildTokenAuthMutationRequest(
572
589
  }
573
590
  return {
574
591
  path: '/api/v1/referral/use',
575
- params: { auth: authToken, l1_address, referral_code, x },
592
+ params: { l1_address, referral_code, x },
576
593
  }
577
594
  }
578
595
  throw new PerpsError(
@@ -1,5 +1,6 @@
1
1
  // Account / balance shapes returned by Lighter's `/api/v1/account` and
2
2
  // `/api/v1/accountLimits` endpoints.
3
+ // Lighter serializes an empty list as JSON `null`, so list members are nullable.
3
4
 
4
5
  /**
5
6
  * Per-market position row returned by Lighter's account endpoints.
@@ -35,6 +36,8 @@ export type LtAccountPosition = {
35
36
  margin_mode: number
36
37
  allocated_margin: string
37
38
  total_discount: string
39
+ /** Lighter bit flags recording which margin settings the account set itself. */
40
+ margin_set_flag?: number
38
41
  }
39
42
 
40
43
  /**
@@ -126,8 +129,8 @@ export interface LtDetailedAccount {
126
129
  account_index: number
127
130
  name: string
128
131
  description: string
129
- positions: LtDetailedAccountPosition[]
130
- assets: LtAccountAsset[]
132
+ positions: LtDetailedAccountPosition[] | null
133
+ assets: LtAccountAsset[] | null
131
134
  total_asset_value: string
132
135
  cross_asset_value: string
133
136
  /** Initial margin locked by cross positions; isolated positions carry
@@ -136,6 +139,17 @@ export interface LtDetailedAccount {
136
139
  approved_integrators?: LtApprovedIntegrator[]
137
140
  }
138
141
 
142
+ /**
143
+ * A detailed account whose wire lists `fetchDetailedAccount` has normalized to
144
+ * real arrays. Every SDK read of an account works on this shape.
145
+ *
146
+ * @public
147
+ */
148
+ export type LtAccount = LtDetailedAccount & {
149
+ positions: LtDetailedAccountPosition[]
150
+ assets: LtAccountAsset[]
151
+ }
152
+
139
153
  /**
140
154
  * Response envelope for a detailed-account lookup. `code` is Lighter's
141
155
  * endpoint result code and `total` is the number of account rows returned.
@@ -145,7 +159,7 @@ export interface LtDetailedAccount {
145
159
  export interface LtDetailedAccountsResponse {
146
160
  code: number
147
161
  total: number
148
- accounts: LtDetailedAccount[]
162
+ accounts: LtDetailedAccount[] | null
149
163
  }
150
164
 
151
165
  /**