@lifi/perps-sdk-provider-lighter 24.0.2 → 25.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 (89) hide show
  1. package/README.md +55 -0
  2. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  3. package/dist/cjs/LighterProvider.js +64 -49
  4. package/dist/cjs/LighterProvider.js.map +1 -1
  5. package/dist/cjs/accountConfig.d.ts.map +1 -1
  6. package/dist/cjs/accountConfig.js +1 -7
  7. package/dist/cjs/accountConfig.js.map +1 -1
  8. package/dist/cjs/signers/LighterSigner.d.ts.map +1 -1
  9. package/dist/cjs/signers/LighterSigner.js +2 -1
  10. package/dist/cjs/signers/LighterSigner.js.map +1 -1
  11. package/dist/cjs/types/submit.d.ts.map +1 -1
  12. package/dist/cjs/utils/activityCursor.d.ts.map +1 -1
  13. package/dist/cjs/utils/activityCursor.js +29 -6
  14. package/dist/cjs/utils/activityCursor.js.map +1 -1
  15. package/dist/cjs/utils/index.d.ts +1 -1
  16. package/dist/cjs/utils/index.d.ts.map +1 -1
  17. package/dist/cjs/utils/index.js +2 -7
  18. package/dist/cjs/utils/index.js.map +1 -1
  19. package/dist/cjs/utils/mapFill.d.ts.map +1 -1
  20. package/dist/cjs/utils/mapFill.js +0 -1
  21. package/dist/cjs/utils/mapFill.js.map +1 -1
  22. package/dist/cjs/utils/mapOrder.d.ts +4 -11
  23. package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
  24. package/dist/cjs/utils/mapOrder.js +109 -120
  25. package/dist/cjs/utils/mapOrder.js.map +1 -1
  26. package/dist/cjs/websocket/LighterWsProvider.js +1 -1
  27. package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
  28. package/dist/esm/LighterProvider.d.ts.map +1 -1
  29. package/dist/esm/LighterProvider.js +71 -58
  30. package/dist/esm/LighterProvider.js.map +1 -1
  31. package/dist/esm/accountConfig.d.ts.map +1 -1
  32. package/dist/esm/accountConfig.js +5 -16
  33. package/dist/esm/accountConfig.js.map +1 -1
  34. package/dist/esm/signers/LighterSigner.d.ts.map +1 -1
  35. package/dist/esm/signers/LighterSigner.js +2 -1
  36. package/dist/esm/signers/LighterSigner.js.map +1 -1
  37. package/dist/esm/types/submit.d.ts +16 -6
  38. package/dist/esm/types/submit.d.ts.map +1 -1
  39. package/dist/esm/utils/activityCursor.d.ts +2 -11
  40. package/dist/esm/utils/activityCursor.d.ts.map +1 -1
  41. package/dist/esm/utils/activityCursor.js +35 -7
  42. package/dist/esm/utils/activityCursor.js.map +1 -1
  43. package/dist/esm/utils/index.d.ts +1 -1
  44. package/dist/esm/utils/index.d.ts.map +1 -1
  45. package/dist/esm/utils/index.js +1 -1
  46. package/dist/esm/utils/index.js.map +1 -1
  47. package/dist/esm/utils/mapFill.d.ts.map +1 -1
  48. package/dist/esm/utils/mapFill.js +1 -2
  49. package/dist/esm/utils/mapFill.js.map +1 -1
  50. package/dist/esm/utils/mapOrder.d.ts +6 -68
  51. package/dist/esm/utils/mapOrder.d.ts.map +1 -1
  52. package/dist/esm/utils/mapOrder.js +108 -180
  53. package/dist/esm/utils/mapOrder.js.map +1 -1
  54. package/dist/esm/websocket/LighterWsProvider.js +2 -2
  55. package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
  56. package/dist/types/LighterProvider.d.ts.map +1 -1
  57. package/dist/types/accountConfig.d.ts.map +1 -1
  58. package/dist/types/signers/LighterSigner.d.ts.map +1 -1
  59. package/dist/types/types/submit.d.ts +16 -6
  60. package/dist/types/types/submit.d.ts.map +1 -1
  61. package/dist/types/utils/activityCursor.d.ts +2 -11
  62. package/dist/types/utils/activityCursor.d.ts.map +1 -1
  63. package/dist/types/utils/index.d.ts +1 -1
  64. package/dist/types/utils/index.d.ts.map +1 -1
  65. package/dist/types/utils/mapFill.d.ts.map +1 -1
  66. package/dist/types/utils/mapOrder.d.ts +6 -68
  67. package/dist/types/utils/mapOrder.d.ts.map +1 -1
  68. package/dist/wasm/lighter-signer.wasm +0 -0
  69. package/package.json +3 -3
  70. package/src/LighterProvider.ts +79 -91
  71. package/src/accountConfig.ts +5 -18
  72. package/src/signers/LighterSigner.ts +2 -0
  73. package/src/types/submit.ts +16 -6
  74. package/src/utils/activityCursor.ts +50 -18
  75. package/src/utils/index.ts +1 -9
  76. package/src/utils/mapFill.ts +1 -7
  77. package/src/utils/mapOrder.ts +127 -207
  78. package/src/websocket/LighterWsProvider.ts +2 -2
  79. package/dist/cjs/utils/mapTwap.d.ts +0 -4
  80. package/dist/cjs/utils/mapTwap.d.ts.map +0 -1
  81. package/dist/cjs/utils/mapTwap.js +0 -31
  82. package/dist/cjs/utils/mapTwap.js.map +0 -1
  83. package/dist/esm/utils/mapTwap.d.ts +0 -5
  84. package/dist/esm/utils/mapTwap.d.ts.map +0 -1
  85. package/dist/esm/utils/mapTwap.js +0 -25
  86. package/dist/esm/utils/mapTwap.js.map +0 -1
  87. package/dist/types/utils/mapTwap.d.ts +0 -5
  88. package/dist/types/utils/mapTwap.d.ts.map +0 -1
  89. package/src/utils/mapTwap.ts +0 -31
@@ -1,72 +1,10 @@
1
- import type { MarketDisplay, OpenOrder, Order, TriggerOrder } from '@lifi/perps-types';
2
- import { OrderType } from '@lifi/perps-types';
1
+ import type { MarketDisplay, Order } from '@lifi/perps-types';
3
2
  import type { LtOrder } from '../types/index.js';
4
- /**
5
- * Map a raw Lighter order status to a short English sentence describing
6
- * *why* the order ended in a terminal non-FILLED state. Non-terminal
7
- * statuses, plain `filled`, and unknown values return `undefined`.
8
- * @public
9
- */
10
- export declare const mapStatusReason: (status: string) => string | undefined;
11
- /**
12
- * True for order types Lighter exposes as TP/SL legs. Mirrors the
13
- * Hyperliquid helper of the same name so the backend can split a raw
14
- * Lighter order list into the same `openOrders` / `triggerOrders`
15
- * buckets the SDK declares on `OrdersResponse`.
16
- * @public
17
- */
18
- export declare const isTriggerType: (type: OrderType) => boolean;
19
- /**
20
- * Decide whether a raw Lighter order belongs in the cross-provider
21
- * `triggerOrders` bucket. Single source of truth for both the REST
22
- * `getOrders` path and the `account_all_orders` WS handler.
23
- *
24
- * Authoritative signals, in order:
25
- * 1. `trigger_status` — Lighter's own enum. `'ready' | 'mark-price' |
26
- * 'parent-order'` all mean "this order has trigger semantics" (TP/SL
27
- * waiting on price, or a TP/SL leg of a parent placeOrder). `'twap'`
28
- * is intentionally excluded — TWAP child orders are scheduled resting
29
- * limits, not TP/SL triggers. `'na'` is "regular order".
30
- * 2. `trigger_price > 0` — catches cases where `trigger_status`
31
- * momentarily lags during state transitions (e.g. right after an OCO
32
- * sibling cancels).
33
- * 3. `type` — final fallback via the mapped `OrderType`.
34
- * @public
35
- */
36
- export declare const isTriggerOrder: (raw: LtOrder) => boolean;
37
- /**
38
- * Map a raw Lighter trigger order (stop/take-profit, market or limit) to
39
- * the generic `TriggerOrder` shape. For market variants the `limitPrice`
40
- * field is omitted; for limit variants `order.price` is the limit and
41
- * `order.trigger_price` is the activation level.
42
- * @public
43
- */
44
- export declare const mapTriggerOrder: (order: LtOrder, market: MarketDisplay) => TriggerOrder;
45
- /**
46
- * Map a raw Lighter order to the generic OpenOrder type.
47
- * @param market - Backend-resolved market identity for `order.market_index`.
48
- * @public
49
- */
50
- export declare const mapOrder: (order: LtOrder, market: MarketDisplay) => OpenOrder;
51
- /**
52
- * Walk a flat list of raw Lighter orders and bucket each into the
53
- * `OpenOrder` / `TriggerOrder` arrays the cross-provider `OrdersResponse`
54
- * declares, plus a `terminated` list of orderIds whose status has reached
55
- * a terminal state (filled / cancelled / rejected / expired) — consumers
56
- * applying WS deltas evict those from the cached buckets. REST callers
57
- * see `terminated: []` because `accountActiveOrders` returns active only.
58
- * An order whose market the resolver does not know is skipped.
59
- * @public
60
- */
61
- export declare const classifyAndMapOrders: (orders: LtOrder[], resolveMarket: (marketIndex: number) => MarketDisplay | undefined) => {
62
- openOrders: OpenOrder[];
63
- triggerOrders: TriggerOrder[];
3
+ /** Map a Lighter order with its venue identity, lifecycle, and execution fields. */
4
+ export declare const mapOrder: (order: LtOrder, market: MarketDisplay) => Order;
5
+ /** Include terminal rows and their ids so consumers can update history and active caches. */
6
+ export declare const mapOrderUpdates: (rawOrders: LtOrder[], resolveMarket: (marketIndex: number) => MarketDisplay | undefined) => {
7
+ orders: Order[];
64
8
  terminated: string[];
65
9
  };
66
- /**
67
- * Map a raw Lighter order to the rich Order type — adds status, time-in-force
68
- * and remaining/filled sizes on top of the OpenOrder fields.
69
- * @public
70
- */
71
- export declare const mapOrderDetail: (order: LtOrder, market: MarketDisplay) => Order;
72
10
  //# sourceMappingURL=mapOrder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapOrder.d.ts","sourceRoot":"","sources":["../../../src/utils/mapOrder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,KAAK,EACL,YAAY,EACb,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAGL,SAAS,EAEV,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAmDhD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,SA+BzD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,KAAG,OAIjB,CAAA;AAE/B;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,OAAO,KAAG,OAY7C,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,OAAO,EACd,QAAQ,aAAa,KACpB,YAkBF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,EAAE,QAAQ,aAAa,KAAG,SAW/D,CAAA;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,OAAO,EAAE,EACjB,eAAe,CAAC,WAAW,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,KAChE;IACD,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,UAAU,EAAE,MAAM,EAAE,CAAA;CAsBrB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,OAAO,EACd,QAAQ,aAAa,KACpB,KAeD,CAAA"}
1
+ {"version":3,"file":"mapOrder.d.ts","sourceRoot":"","sources":["../../../src/utils/mapOrder.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAa,MAAM,mBAAmB,CAAA;AASxE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAiFhD,oFAAoF;AACpF,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,EAAE,QAAQ,aAAa,KAAG,KA8DhE,CAAA;AAED,6FAA6F;AAC7F,eAAO,MAAM,eAAe,GAC1B,WAAW,OAAO,EAAE,EACpB,eAAe,CAAC,WAAW,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,KAChE;IAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAczC,CAAA"}
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/perps-sdk-provider-lighter",
3
- "version": "24.0.2",
3
+ "version": "25.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.3.0"
47
+ "@lifi/perps-types": "^13.0.0"
48
48
  },
49
49
  "peerDependencies": {
50
- "@lifi/perps-sdk": "^13.0.0"
50
+ "@lifi/perps-sdk": "^14.0.0"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public"
@@ -1,4 +1,5 @@
1
1
  import {
2
+ ACTIVE_ORDER_STATUSES,
2
3
  type DepositFlow,
3
4
  explorerTxUrlFromBase,
4
5
  getAssetRegistry,
@@ -20,7 +21,6 @@ import {
20
21
  type ProviderGetPortfolioHistoryParams,
21
22
  type ProviderGetPositionsParams,
22
23
  type ProviderGetQuoteParams,
23
- type ProviderGetRunningTwapsParams,
24
24
  type ProviderGetWithdrawableBalancesParams,
25
25
  type ProviderWithdrawableBalance,
26
26
  paginateActivity,
@@ -55,7 +55,6 @@ import type {
55
55
  Quote,
56
56
  SignedActionStep,
57
57
  SigningMethod,
58
- TwapOrder,
59
58
  } from '@lifi/perps-types'
60
59
  import {
61
60
  ActionType,
@@ -97,7 +96,6 @@ import type {
97
96
  LtAccountPnL,
98
97
  LtDepositHistoryItem,
99
98
  LtDepositHistoryResponse,
100
- LtDetailedAccountPosition,
101
99
  LtLiquidation,
102
100
  LtLiquidationsResponse,
103
101
  LtOrder,
@@ -128,7 +126,6 @@ import {
128
126
  } from './utils/apiClient.js'
129
127
  import { isAssetMarginEnabled } from './utils/assetCollateral.js'
130
128
  import {
131
- classifyAndMapOrders,
132
129
  estimateLiquidationPrice,
133
130
  fetchDetailedAccount,
134
131
  formatOrderPrice,
@@ -138,7 +135,7 @@ import {
138
135
  lighterWithdrawableBalances,
139
136
  mapFill,
140
137
  mapOpenPositions,
141
- mapOrderDetail,
138
+ mapOrder,
142
139
  mapPortfolioHistory,
143
140
  positionMarginConstraints,
144
141
  toBigOrNull,
@@ -146,7 +143,6 @@ import {
146
143
  toIsoFromSeconds,
147
144
  toRequiredBig,
148
145
  } from './utils/index.js'
149
- import { mapRunningTwap } from './utils/mapTwap.js'
150
146
  import {
151
147
  fetchRegisteredApiKey,
152
148
  normalizeLighterPublicKey,
@@ -165,11 +161,6 @@ const projectFeeTier = (
165
161
  taker: tickToFeeString(limits.current_taker_fee_tick),
166
162
  })
167
163
 
168
- const orderCountFor = (p: LtDetailedAccountPosition): number =>
169
- (p.open_order_count ?? 0) +
170
- (p.pending_order_count ?? 0) +
171
- (p.position_tied_order_count ?? 0)
172
-
173
164
  /**
174
165
  * Lighter `sendTx` returns a bare lowercase-hex tx hash (40 bytes → 80 hex
175
166
  * chars). `Order.order_id` is a different identifier the matching engine
@@ -647,11 +638,6 @@ export const createLighterProvider = (
647
638
  return wireList(response.orders)
648
639
  }
649
640
 
650
- const deriveOrderBearingMarketIds = (account: LtAccount): number[] =>
651
- account.positions
652
- .filter((p) => orderCountFor(p) > 0)
653
- .map((p) => p.market_id)
654
-
655
641
  const fetchAllHistory = async (
656
642
  client: LighterApiClient,
657
643
  token: string,
@@ -897,23 +883,26 @@ export const createLighterProvider = (
897
883
  ),
898
884
  units: availableMargin.toString(),
899
885
  valueUsd: availableMargin.toString(),
886
+ price: '1',
900
887
  },
901
888
  ]
902
889
  : []
903
890
  // Spot token holdings — non-collateral. The instance's settlement asset
904
891
  // is valued 1:1; other tokens have no price source at this boundary, so
905
- // their USD value is unknown.
892
+ // their price and USD value are unknown.
906
893
  const balances: Balance[] = account.assets
907
894
  .filter((asset) => toRequiredBig(asset.balance, 'balance').gt(0))
908
895
  .map((a) => {
909
896
  const assetId = String(a.asset_id)
897
+ const settlement = a.asset_id === collateral.assetIndex
910
898
  return {
911
899
  categoryId: spotCategoryId,
912
900
  asset:
913
901
  assetRegistry.get(assetId) ??
914
902
  lighterAsset(assetId, a.symbol, providerKey),
915
903
  units: a.balance,
916
- valueUsd: a.asset_id === collateral.assetIndex ? a.balance : '0',
904
+ valueUsd: settlement ? a.balance : '0',
905
+ ...(settlement ? { price: '1' } : {}),
917
906
  }
918
907
  })
919
908
 
@@ -1063,24 +1052,22 @@ export const createLighterProvider = (
1063
1052
  }
1064
1053
  },
1065
1054
 
1066
- /**
1067
- * Lighter's `accountActiveOrders` endpoint takes no limit/cursor and
1068
- * returns every active order for the account, so the response is always
1069
- * the complete set: `params.limit` is not honoured and `pagination` is
1070
- * reported as `{ limit: <count returned>, hasMore: false }` with no cursor.
1071
- */
1072
1055
  async getOrders(
1073
1056
  params: ProviderGetOrdersParams,
1074
1057
  opts?: SDKRequestOptions
1075
1058
  ): Promise<OrdersResponse> {
1059
+ const statuses = new Set(params.statuses ?? ACTIVE_ORDER_STATUSES)
1060
+ const empty: OrdersResponse = {
1061
+ provider: providerKey,
1062
+ orders: [],
1063
+ pagination: { limit: params.limit ?? 0, hasMore: false },
1064
+ }
1065
+ if (statuses.size === 0) {
1066
+ return empty
1067
+ }
1076
1068
  const token = await resolveAuthToken(opts, params.address)
1077
1069
  if (token === undefined) {
1078
- return {
1079
- provider: providerKey,
1080
- openOrders: [],
1081
- triggerOrders: [],
1082
- pagination: { limit: params.limit ?? 0, hasMore: false },
1083
- }
1070
+ return empty
1084
1071
  }
1085
1072
 
1086
1073
  const client = apiClient(opts)
@@ -1089,69 +1076,73 @@ export const createLighterProvider = (
1089
1076
  fetchDetailedAccount(client, params.address),
1090
1077
  registry.sync(),
1091
1078
  ])
1092
-
1093
1079
  const marketId =
1094
1080
  params.marketId === undefined
1095
1081
  ? LIGHTER_ALL_MARKETS_WILDCARD
1096
1082
  : Number(registry.require(params.marketId).id)
1097
-
1098
- const response = await retryOnRevoked(opts, params.address, token, (t) =>
1099
- fetchActiveOrders(client, t, account.index, marketId)
1100
- )
1101
-
1102
- const { openOrders, triggerOrders } = classifyAndMapOrders(
1103
- response.orders,
1104
- (marketIndex) => registry.require(String(marketIndex))
1105
- )
1106
-
1107
- const total = openOrders.length + triggerOrders.length
1108
- return {
1109
- provider: providerKey,
1110
- openOrders,
1111
- triggerOrders,
1112
- pagination: { limit: total, hasMore: false },
1113
- }
1114
- },
1115
-
1116
- async getRunningTwaps(
1117
- params: ProviderGetRunningTwapsParams,
1118
- opts?: SDKRequestOptions
1119
- ): Promise<TwapOrder[]> {
1120
- const token = await resolveAuthToken(opts, params.address)
1121
- if (token === undefined) {
1122
- return []
1083
+ let active = false
1084
+ let terminal = false
1085
+ for (const status of statuses) {
1086
+ if (isActiveOrderStatus(status)) {
1087
+ active = true
1088
+ } else {
1089
+ terminal = true
1090
+ }
1123
1091
  }
1124
-
1125
- const client = apiClient(opts)
1126
- const registry = getMarketRegistry(requireClient(), providerKey)
1127
- const [account] = await Promise.all([
1128
- fetchDetailedAccount(client, params.address),
1129
- registry.sync(),
1130
- ])
1131
- const marketIds =
1132
- params.marketId === undefined
1133
- ? deriveOrderBearingMarketIds(account)
1134
- : [Number(registry.require(params.marketId).id)]
1135
- const responses = await retryOnRevoked(opts, params.address, token, (t) =>
1136
- Promise.all(
1137
- marketIds.map((id) => fetchActiveOrders(client, t, account.index, id))
1138
- )
1092
+ const [activeResponse, inactiveResponse] = await retryOnRevoked(
1093
+ opts,
1094
+ params.address,
1095
+ token,
1096
+ (t) =>
1097
+ Promise.all([
1098
+ active && params.cursor === undefined
1099
+ ? fetchActiveOrders(client, t, account.index, marketId)
1100
+ : undefined,
1101
+ terminal
1102
+ ? client.getAuthed<LtOrdersResponse>(
1103
+ '/api/v1/accountInactiveOrders',
1104
+ t,
1105
+ {
1106
+ account_index: account.index,
1107
+ market_id: marketId,
1108
+ limit: params.limit ?? LIGHTER_HISTORY_PAGE_SIZE,
1109
+ ...(params.cursor === undefined
1110
+ ? {}
1111
+ : { cursor: params.cursor }),
1112
+ }
1113
+ )
1114
+ : undefined,
1115
+ ])
1139
1116
  )
1140
-
1141
- const twaps: TwapOrder[] = []
1142
- for (const response of responses) {
1143
- for (const order of response.orders) {
1144
- if (order.type === 'twap') {
1145
- twaps.push(
1146
- mapRunningTwap(
1147
- order,
1148
- registry.require(String(order.market_index))
1149
- )
1150
- )
1117
+ const orders: Order[] = []
1118
+ for (const response of [activeResponse, inactiveResponse]) {
1119
+ for (const raw of wireList(response?.orders)) {
1120
+ // `get`, not `require`: a market id the backend list no longer
1121
+ // carries drops only its own row instead of rejecting the whole
1122
+ // page. The registry warns once per unresolved id. A delisted
1123
+ // market still resolves, so its rows stay.
1124
+ const market = registry.get(String(raw.market_index))
1125
+ if (market === undefined) {
1126
+ continue
1127
+ }
1128
+ const order = mapOrder(raw, market)
1129
+ if (statuses.has(order.status)) {
1130
+ orders.push(order)
1151
1131
  }
1152
1132
  }
1153
1133
  }
1154
- return twaps
1134
+ const cursor = inactiveResponse?.next_cursor || undefined
1135
+ return {
1136
+ provider: providerKey,
1137
+ orders,
1138
+ pagination: {
1139
+ limit: terminal
1140
+ ? (params.limit ?? LIGHTER_HISTORY_PAGE_SIZE)
1141
+ : orders.length,
1142
+ hasMore: cursor !== undefined,
1143
+ ...(cursor === undefined ? {} : { cursor }),
1144
+ },
1145
+ }
1155
1146
  },
1156
1147
 
1157
1148
  /**
@@ -1189,7 +1180,7 @@ export const createLighterProvider = (
1189
1180
  `Lighter order ${params.id} not found for ${params.address}`
1190
1181
  )
1191
1182
  const detail = (order: LtOrder): Order =>
1192
- mapOrderDetail(order, registry.require(String(order.market_index)))
1183
+ mapOrder(order, registry.require(String(order.market_index)))
1193
1184
 
1194
1185
  // A tx-hash route would require mapping the caller's executeAction tx
1195
1186
  // hash → wasm nonce → matching order, which the LI.FI backend did via
@@ -1433,9 +1424,6 @@ export const createLighterProvider = (
1433
1424
  : Promise.resolve(),
1434
1425
  ])
1435
1426
 
1436
- const assetSymbol = (assetId: number): string =>
1437
- assetRegistry.get(String(assetId))?.displaySymbol ?? String(assetId)
1438
-
1439
1427
  const items: ActivityItem[] = [
1440
1428
  ...history.deposits.deposits.map(
1441
1429
  (d): ActivityItem => ({
@@ -1443,7 +1431,7 @@ export const createLighterProvider = (
1443
1431
  provider: providerKey,
1444
1432
  timestamp: toIsoFromMs(d.timestamp),
1445
1433
  type: ActivityType.DEPOSIT,
1446
- asset: assetSymbol(d.asset_id),
1434
+ asset: assetRegistry.require(String(d.asset_id)),
1447
1435
  amount: d.amount,
1448
1436
  explorerLink: d.l1_tx_hash
1449
1437
  ? `https://scan.li.fi/tx/${d.l1_tx_hash}`
@@ -1458,7 +1446,7 @@ export const createLighterProvider = (
1458
1446
  provider: providerKey,
1459
1447
  timestamp: toIsoFromMs(w.timestamp),
1460
1448
  type: ActivityType.WITHDRAWAL,
1461
- asset: assetSymbol(w.asset_id),
1449
+ asset: assetRegistry.require(String(w.asset_id)),
1462
1450
  amount: w.amount,
1463
1451
  explorerLink: w.l1_tx_hash
1464
1452
  ? `https://scan.li.fi/tx/${w.l1_tx_hash}`
@@ -1555,7 +1543,7 @@ export const createLighterProvider = (
1555
1543
  type: ActivityType.TRANSFER,
1556
1544
  direction,
1557
1545
  counterpartyAccountIndex,
1558
- asset: assetSymbol(t.asset_id),
1546
+ asset: assetRegistry.require(String(t.asset_id)),
1559
1547
  amount: t.amount,
1560
1548
  // The venue charges the transfer fee in the deployment's
1561
1549
  // settlement asset, not in the asset the row moves, so a spot
@@ -16,21 +16,6 @@ function assertNever(value: never): never {
16
16
  )
17
17
  }
18
18
 
19
- const LIGHTER_ACCOUNT_TYPE_STANDARD = 0
20
- const LIGHTER_ACCOUNT_TYPE_PREMIUM = 1
21
-
22
- /**
23
- * Tier decode for the unauthenticated read, where `/accountLimits` — and with
24
- * it the tier string — is out of reach. Neither official Lighter client
25
- * documents an integer for any tier, so `0` (the `account_type` every sampled
26
- * public account reports) reads as the default tier `standard`, and `1` as
27
- * `premium`. No integer is known for `plus`: that account projects `null` here.
28
- */
29
- const ACCOUNT_TYPE_INT_TO_WIRE: Readonly<Record<number, string>> = {
30
- [LIGHTER_ACCOUNT_TYPE_STANDARD]: 'standard',
31
- [LIGHTER_ACCOUNT_TYPE_PREMIUM]: 'premium',
32
- }
33
-
34
19
  // Wire strings match the backend descriptor's ParamOption values. An unmapped
35
20
  // int projects to null.
36
21
  const ACCOUNT_MODE_INT_TO_WIRE: Readonly<Record<number, string>> = {
@@ -43,8 +28,10 @@ const ACCOUNT_MODE_INT_TO_WIRE: Readonly<Record<number, string>> = {
43
28
  * `userTierName` decides it, but only when the descriptor enumerates the
44
29
  * string: Lighter owns that vocabulary, so an unrecognised value projects
45
30
  * `null` instead of a mis-reported tier. A descriptor whose parameter carries
46
- * no `values` array enumerates nothing, so it also projects `null`. With no
47
- * tier string in hand the integer map decides.
31
+ * no `values` array enumerates nothing, so it also projects `null`. The
32
+ * unauthenticated read reaches no `/accountLimits` and so carries no tier
33
+ * string, which projects `null` as well: `config.accountType` is Lighter's
34
+ * `SubAccountType`, not a tier, so nothing else in the config decides it.
48
35
  */
49
36
  function resolveAccountTier(
50
37
  descriptor: ProviderAction,
@@ -52,7 +39,7 @@ function resolveAccountTier(
52
39
  ): string | null {
53
40
  const { userTierName } = config
54
41
  if (userTierName === undefined) {
55
- return ACCOUNT_TYPE_INT_TO_WIRE[config.accountType] ?? null
42
+ return null
56
43
  }
57
44
  const enumerated = descriptor.params?.[0]?.values ?? []
58
45
  return enumerated.some((option) => option.value === userTierName)
@@ -109,6 +109,7 @@ const NIL_INTEGRATOR_MAKER_FEE = 0
109
109
  const SELF_TRADE_BEHAVIOR_EXPIRE_MAKER = 0
110
110
  const SELF_TRADE_EQUALITY_ACCOUNT_INDEX = 0
111
111
  const SKIP_NONCE_DISABLED = 0
112
+ const NIL_ORDER_VERSION = 0
112
113
  // CancelAll across every market (lighter-go `NilMarketIndex`); a real index
113
114
  // scopes the cancel to a single market.
114
115
  const NIL_MARKET_INDEX = 255
@@ -552,6 +553,7 @@ export class LighterSigner {
552
553
  SELF_TRADE_EQUALITY_ACCOUNT_INDEX,
553
554
  SKIP_NONCE_DISABLED,
554
555
  nonce,
556
+ NIL_ORDER_VERSION,
555
557
  ctx.apiKeyIndex,
556
558
  ctx.accountIndex
557
559
  )
@@ -1,9 +1,14 @@
1
1
  // Lighter submit/execute-path wire shapes (sendTx, fastwithdraw, changeAccountTier).
2
2
 
3
3
  /**
4
- * Request body for Lighter's `/api/v1/sendTx` endpoint. `tx_type` and
5
- * `tx_info` are the signed WASM transaction envelope; `price_protection` is an
6
- * optional venue-side execution guard.
4
+ * Request body for Lighter's `/api/v1/sendTx` endpoint (spec schema
5
+ * `ReqSendTx`). `tx_type` and `tx_info` are the signed WASM transaction
6
+ * envelope. `price_protection` is a venue-side execution guard the spec
7
+ * defaults to `true`, so an absent member leaves the guard on.
8
+ *
9
+ * The generated client carries the same members as `SendTxRequest` in
10
+ * `zklighter-perps/apis/TransactionApi` — the call signature of
11
+ * `TransactionApi.sendTx`, not a `models/` wire model.
7
12
  *
8
13
  * @public
9
14
  */
@@ -28,9 +33,14 @@ export interface LtSendTxResponse {
28
33
  }
29
34
 
30
35
  /**
31
- * Batch request body for Lighter's transaction submission endpoint.
32
- * `tx_types` is a JSON-encoded number array and `tx_infos` a JSON-encoded
33
- * string array; corresponding entries are submitted in array order.
36
+ * Batch request body for Lighter's `/api/v1/sendTxBatch` endpoint (spec schema
37
+ * `ReqSendTxBatch`). `tx_types` is a JSON-encoded number array and `tx_infos` a
38
+ * JSON-encoded string array; corresponding entries are submitted in array
39
+ * order.
40
+ *
41
+ * The generated client carries the same members as `SendTxBatchRequest` in
42
+ * `zklighter-perps/apis/TransactionApi` — the call signature of
43
+ * `TransactionApi.sendTxBatch`, not a `models/` wire model.
34
44
  *
35
45
  * @public
36
46
  */
@@ -1,18 +1,13 @@
1
1
  import { PerpsError } from '@lifi/perps-sdk'
2
- import { type ActivityItem, PerpsErrorCode } from '@lifi/perps-types'
2
+ import {
3
+ type ActivityItem,
4
+ ActivityType,
5
+ PerpsErrorCode,
6
+ } from '@lifi/perps-types'
3
7
 
4
8
  /**
5
- * Per-endpoint cursor envelope for Lighter activity pagination.
6
- *
7
- * Each key holds the upstream `cursor` / `next_cursor` returned by that
8
- * endpoint on the previous call. When a key is **present** with a non-empty
9
- * string the corresponding endpoint will be re-fetched at that cursor on the
10
- * next call; when a key is **absent** the endpoint is treated as exhausted
11
- * (or never paged because the type filter excluded it) and skipped.
12
- *
13
- * The envelope is round-tripped through `cursor` as base64url JSON so callers
14
- * don't need to know the per-endpoint shape. The encoding mirrors the LI.FI
15
- * backend's wire format so cursors created by the backend remain usable.
9
+ * Endpoint-keyed activity cursor; absent endpoints are exhausted or excluded.
10
+ * Nonempty overflow requires a version-2 base64url JSON envelope.
16
11
  * @public
17
12
  */
18
13
  export interface LighterActivityCursor {
@@ -66,7 +61,7 @@ export const encodeActivityCursor = (
66
61
  if (Object.keys(compact).length === 0) {
67
62
  return undefined
68
63
  }
69
- return toBase64Url(JSON.stringify(compact))
64
+ return toBase64Url(JSON.stringify({ version: 2, ...compact }))
70
65
  }
71
66
 
72
67
  /**
@@ -120,20 +115,57 @@ export const decodeActivityCursor = (
120
115
  'Invalid Lighter activity cursor: overflow must be an array'
121
116
  )
122
117
  }
123
- env.overflow = overflow as ActivityItem[]
118
+ if (overflow.length > 0 && cursorRecord.version !== 2) {
119
+ throw new PerpsError(
120
+ PerpsErrorCode.ValidationError,
121
+ 'Invalid Lighter activity cursor: legacy overflow format; restart pagination'
122
+ )
123
+ }
124
+ env.overflow = overflow.map(toOverflowItem)
124
125
  }
125
126
  return env
126
127
  }
127
128
 
129
+ const ACTIVITY_TYPES: readonly string[] = Object.values(ActivityType)
130
+
131
+ /**
132
+ * A cursor travels through consumer storage, so an overflow row may arrive
133
+ * corrupted or from an incompatible build. Reject it here rather than let a
134
+ * malformed row reach a consumer as a typed activity.
135
+ */
136
+ const toOverflowItem = (row: unknown, index: number): ActivityItem => {
137
+ const invalid = (detail: string): PerpsError =>
138
+ new PerpsError(
139
+ PerpsErrorCode.ValidationError,
140
+ `Invalid Lighter activity cursor: overflow[${index}] ${detail}`
141
+ )
142
+ if (row === null || typeof row !== 'object' || Array.isArray(row)) {
143
+ throw invalid('must be an object')
144
+ }
145
+ const item = row as Record<string, unknown>
146
+ for (const field of ['id', 'provider', 'timestamp'] as const) {
147
+ if (typeof item[field] !== 'string') {
148
+ throw invalid(`${field} must be a string`)
149
+ }
150
+ }
151
+ if (typeof item.type !== 'string' || !ACTIVITY_TYPES.includes(item.type)) {
152
+ throw invalid('carries an unknown activity type')
153
+ }
154
+ return row as ActivityItem
155
+ }
156
+
128
157
  const toBase64Url = (s: string): string => {
129
- const utf8 = unescape(encodeURIComponent(s))
130
- return btoa(utf8).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
158
+ let binary = ''
159
+ for (const byte of new TextEncoder().encode(s)) {
160
+ binary += String.fromCharCode(byte)
161
+ }
162
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
131
163
  }
132
164
 
133
165
  const fromBase64Url = (s: string): string => {
134
166
  const padded = s.replace(/-/g, '+').replace(/_/g, '/') + padFor(s)
135
- const decoded = atob(padded)
136
- return decodeURIComponent(escape(decoded))
167
+ const bytes = Uint8Array.from(atob(padded), (char) => char.charCodeAt(0))
168
+ return new TextDecoder().decode(bytes)
137
169
  }
138
170
 
139
171
  const padFor = (s: string): string => {
@@ -16,15 +16,7 @@ export { estimateLiquidationPrice } from './liquidation.js'
16
16
  export { mapFill } from './mapFill.js'
17
17
  export { mapMarketContext } from './mapMarketContext.js'
18
18
  export { mapOpenPositions } from './mapOpenPositions.js'
19
- export {
20
- classifyAndMapOrders,
21
- isTriggerOrder,
22
- isTriggerType,
23
- mapOrder,
24
- mapOrderDetail,
25
- mapStatusReason,
26
- mapTriggerOrder,
27
- } from './mapOrder.js'
19
+ export { mapOrder, mapOrderUpdates } from './mapOrder.js'
28
20
  export { mapPortfolioHistory } from './mapPortfolioHistory.js'
29
21
  export { leverageFromImf, mapPosition } from './mapPosition.js'
30
22
  export { mapMarketOrderLimits } from './marketLimits.js'
@@ -4,12 +4,7 @@ import {
4
4
  explorerTxUrl,
5
5
  } from '@lifi/perps-sdk'
6
6
  import type { Fill, MarketDisplay } from '@lifi/perps-types'
7
- import {
8
- FillStatus,
9
- LiquidityRole,
10
- OrderSide,
11
- OrderType,
12
- } from '@lifi/perps-types'
7
+ import { LiquidityRole, OrderSide, OrderType } from '@lifi/perps-types'
13
8
  import Big from 'big.js'
14
9
  import {
15
10
  LIGHTER_FEE_TICK_SCALE,
@@ -131,7 +126,6 @@ export const mapFill = (
131
126
  type: OrderType.LIMIT,
132
127
  size: trade.size,
133
128
  price: trade.price,
134
- status: FillStatus.FILLED,
135
129
  liquidity: isMaker ? LiquidityRole.MAKER : LiquidityRole.TAKER,
136
130
  // Lighter charges the fill fee in the market's quote asset.
137
131
  fee: