@lifi/perps-sdk-provider-lighter 24.0.2 → 26.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.
- package/README.md +55 -0
- package/dist/cjs/LighterProvider.d.ts.map +1 -1
- package/dist/cjs/LighterProvider.js +71 -56
- package/dist/cjs/LighterProvider.js.map +1 -1
- package/dist/cjs/accountConfig.d.ts.map +1 -1
- package/dist/cjs/accountConfig.js +1 -7
- package/dist/cjs/accountConfig.js.map +1 -1
- package/dist/cjs/constants.d.ts +2 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +3 -0
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/signers/LighterSigner.d.ts.map +1 -1
- package/dist/cjs/signers/LighterSigner.js +2 -1
- package/dist/cjs/signers/LighterSigner.js.map +1 -1
- package/dist/cjs/types/submit.d.ts.map +1 -1
- package/dist/cjs/utils/activityCursor.d.ts.map +1 -1
- package/dist/cjs/utils/activityCursor.js +29 -6
- package/dist/cjs/utils/activityCursor.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +1 -1
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +2 -7
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/mapFill.d.ts.map +1 -1
- package/dist/cjs/utils/mapFill.js +4 -2
- package/dist/cjs/utils/mapFill.js.map +1 -1
- package/dist/cjs/utils/mapOrder.d.ts +4 -11
- package/dist/cjs/utils/mapOrder.d.ts.map +1 -1
- package/dist/cjs/utils/mapOrder.js +112 -120
- package/dist/cjs/utils/mapOrder.js.map +1 -1
- package/dist/cjs/utils/txHash.d.ts +2 -0
- package/dist/cjs/utils/txHash.d.ts.map +1 -0
- package/dist/cjs/utils/txHash.js +10 -0
- package/dist/cjs/utils/txHash.js.map +1 -0
- package/dist/cjs/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/cjs/websocket/LighterWsProvider.js +11 -9
- package/dist/cjs/websocket/LighterWsProvider.js.map +1 -1
- package/dist/esm/LighterProvider.d.ts.map +1 -1
- package/dist/esm/LighterProvider.js +78 -65
- package/dist/esm/LighterProvider.js.map +1 -1
- package/dist/esm/accountConfig.d.ts.map +1 -1
- package/dist/esm/accountConfig.js +5 -16
- package/dist/esm/accountConfig.js.map +1 -1
- package/dist/esm/constants.d.ts +9 -3
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +6 -3
- package/dist/esm/constants.js.map +1 -1
- package/dist/esm/signers/LighterSigner.d.ts.map +1 -1
- package/dist/esm/signers/LighterSigner.js +2 -1
- package/dist/esm/signers/LighterSigner.js.map +1 -1
- package/dist/esm/types/submit.d.ts +16 -6
- package/dist/esm/types/submit.d.ts.map +1 -1
- package/dist/esm/types/ws.d.ts +1 -1
- package/dist/esm/utils/activityCursor.d.ts +2 -11
- package/dist/esm/utils/activityCursor.d.ts.map +1 -1
- package/dist/esm/utils/activityCursor.js +35 -7
- package/dist/esm/utils/activityCursor.js.map +1 -1
- package/dist/esm/utils/index.d.ts +1 -1
- package/dist/esm/utils/index.d.ts.map +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/mapFill.d.ts.map +1 -1
- package/dist/esm/utils/mapFill.js +5 -3
- package/dist/esm/utils/mapFill.js.map +1 -1
- package/dist/esm/utils/mapOrder.d.ts +6 -68
- package/dist/esm/utils/mapOrder.d.ts.map +1 -1
- package/dist/esm/utils/mapOrder.js +111 -180
- package/dist/esm/utils/mapOrder.js.map +1 -1
- package/dist/esm/utils/txHash.d.ts +8 -0
- package/dist/esm/utils/txHash.d.ts.map +1 -0
- package/dist/esm/utils/txHash.js +18 -0
- package/dist/esm/utils/txHash.js.map +1 -0
- package/dist/esm/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/esm/websocket/LighterWsProvider.js +20 -16
- package/dist/esm/websocket/LighterWsProvider.js.map +1 -1
- package/dist/types/LighterProvider.d.ts.map +1 -1
- package/dist/types/accountConfig.d.ts.map +1 -1
- package/dist/types/constants.d.ts +9 -3
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/signers/LighterSigner.d.ts.map +1 -1
- package/dist/types/types/submit.d.ts +16 -6
- package/dist/types/types/submit.d.ts.map +1 -1
- package/dist/types/types/ws.d.ts +1 -1
- package/dist/types/utils/activityCursor.d.ts +2 -11
- package/dist/types/utils/activityCursor.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/mapFill.d.ts.map +1 -1
- package/dist/types/utils/mapOrder.d.ts +6 -68
- package/dist/types/utils/mapOrder.d.ts.map +1 -1
- package/dist/types/utils/txHash.d.ts +8 -0
- package/dist/types/utils/txHash.d.ts.map +1 -0
- package/dist/types/websocket/LighterWsProvider.d.ts.map +1 -1
- package/dist/wasm/lighter-signer.wasm +0 -0
- package/package.json +3 -3
- package/src/LighterProvider.ts +87 -98
- package/src/accountConfig.ts +5 -18
- package/src/constants.ts +11 -3
- package/src/signers/LighterSigner.ts +2 -0
- package/src/types/submit.ts +16 -6
- package/src/types/ws.ts +1 -1
- package/src/utils/activityCursor.ts +50 -18
- package/src/utils/index.ts +1 -9
- package/src/utils/mapFill.ts +5 -8
- package/src/utils/mapOrder.ts +130 -207
- package/src/utils/txHash.ts +20 -0
- package/src/websocket/LighterWsProvider.ts +22 -17
- package/dist/cjs/utils/mapTwap.d.ts +0 -4
- package/dist/cjs/utils/mapTwap.d.ts.map +0 -1
- package/dist/cjs/utils/mapTwap.js +0 -31
- package/dist/cjs/utils/mapTwap.js.map +0 -1
- package/dist/esm/utils/mapTwap.d.ts +0 -5
- package/dist/esm/utils/mapTwap.d.ts.map +0 -1
- package/dist/esm/utils/mapTwap.js +0 -25
- package/dist/esm/utils/mapTwap.js.map +0 -1
- package/dist/types/utils/mapTwap.d.ts +0 -5
- package/dist/types/utils/mapTwap.d.ts.map +0 -1
- package/src/utils/mapTwap.ts +0 -31
package/src/LighterProvider.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ACTIVE_ORDER_STATUSES,
|
|
2
3
|
type DepositFlow,
|
|
4
|
+
explorerTxUrl,
|
|
3
5
|
explorerTxUrlFromBase,
|
|
4
6
|
getAssetRegistry,
|
|
5
7
|
getMarketRegistry,
|
|
@@ -20,7 +22,6 @@ import {
|
|
|
20
22
|
type ProviderGetPortfolioHistoryParams,
|
|
21
23
|
type ProviderGetPositionsParams,
|
|
22
24
|
type ProviderGetQuoteParams,
|
|
23
|
-
type ProviderGetRunningTwapsParams,
|
|
24
25
|
type ProviderGetWithdrawableBalancesParams,
|
|
25
26
|
type ProviderWithdrawableBalance,
|
|
26
27
|
paginateActivity,
|
|
@@ -55,7 +56,6 @@ import type {
|
|
|
55
56
|
Quote,
|
|
56
57
|
SignedActionStep,
|
|
57
58
|
SigningMethod,
|
|
58
|
-
TwapOrder,
|
|
59
59
|
} from '@lifi/perps-types'
|
|
60
60
|
import {
|
|
61
61
|
ActionType,
|
|
@@ -97,7 +97,6 @@ import type {
|
|
|
97
97
|
LtAccountPnL,
|
|
98
98
|
LtDepositHistoryItem,
|
|
99
99
|
LtDepositHistoryResponse,
|
|
100
|
-
LtDetailedAccountPosition,
|
|
101
100
|
LtLiquidation,
|
|
102
101
|
LtLiquidationsResponse,
|
|
103
102
|
LtOrder,
|
|
@@ -128,7 +127,6 @@ import {
|
|
|
128
127
|
} from './utils/apiClient.js'
|
|
129
128
|
import { isAssetMarginEnabled } from './utils/assetCollateral.js'
|
|
130
129
|
import {
|
|
131
|
-
classifyAndMapOrders,
|
|
132
130
|
estimateLiquidationPrice,
|
|
133
131
|
fetchDetailedAccount,
|
|
134
132
|
formatOrderPrice,
|
|
@@ -138,7 +136,7 @@ import {
|
|
|
138
136
|
lighterWithdrawableBalances,
|
|
139
137
|
mapFill,
|
|
140
138
|
mapOpenPositions,
|
|
141
|
-
|
|
139
|
+
mapOrder,
|
|
142
140
|
mapPortfolioHistory,
|
|
143
141
|
positionMarginConstraints,
|
|
144
142
|
toBigOrNull,
|
|
@@ -146,11 +144,11 @@ import {
|
|
|
146
144
|
toIsoFromSeconds,
|
|
147
145
|
toRequiredBig,
|
|
148
146
|
} from './utils/index.js'
|
|
149
|
-
import { mapRunningTwap } from './utils/mapTwap.js'
|
|
150
147
|
import {
|
|
151
148
|
fetchRegisteredApiKey,
|
|
152
149
|
normalizeLighterPublicKey,
|
|
153
150
|
} from './utils/registeredApiKey.js'
|
|
151
|
+
import { isPlaceholderTxHash } from './utils/txHash.js'
|
|
154
152
|
import { wireList } from './utils/wireList.js'
|
|
155
153
|
|
|
156
154
|
const ZERO_FEE_TIER = { maker: '0', taker: '0' }
|
|
@@ -165,11 +163,6 @@ const projectFeeTier = (
|
|
|
165
163
|
taker: tickToFeeString(limits.current_taker_fee_tick),
|
|
166
164
|
})
|
|
167
165
|
|
|
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
166
|
/**
|
|
174
167
|
* Lighter `sendTx` returns a bare lowercase-hex tx hash (40 bytes → 80 hex
|
|
175
168
|
* chars). `Order.order_id` is a different identifier the matching engine
|
|
@@ -349,6 +342,7 @@ export const createLighterProvider = (
|
|
|
349
342
|
const providerKey = deployment.providerKey
|
|
350
343
|
const restUrl = options.restUrl ?? deployment.restUrl
|
|
351
344
|
const explorerTxBaseUrl = deployment.explorerTxBaseUrl
|
|
345
|
+
const bridgeChainId = deployment.bridgeChainId
|
|
352
346
|
const collateral = deployment.collateral
|
|
353
347
|
const authTokenSource: (() => string | Promise<string>) | undefined =
|
|
354
348
|
typeof options.authToken === 'function'
|
|
@@ -647,11 +641,6 @@ export const createLighterProvider = (
|
|
|
647
641
|
return wireList(response.orders)
|
|
648
642
|
}
|
|
649
643
|
|
|
650
|
-
const deriveOrderBearingMarketIds = (account: LtAccount): number[] =>
|
|
651
|
-
account.positions
|
|
652
|
-
.filter((p) => orderCountFor(p) > 0)
|
|
653
|
-
.map((p) => p.market_id)
|
|
654
|
-
|
|
655
644
|
const fetchAllHistory = async (
|
|
656
645
|
client: LighterApiClient,
|
|
657
646
|
token: string,
|
|
@@ -897,23 +886,26 @@ export const createLighterProvider = (
|
|
|
897
886
|
),
|
|
898
887
|
units: availableMargin.toString(),
|
|
899
888
|
valueUsd: availableMargin.toString(),
|
|
889
|
+
price: '1',
|
|
900
890
|
},
|
|
901
891
|
]
|
|
902
892
|
: []
|
|
903
893
|
// Spot token holdings — non-collateral. The instance's settlement asset
|
|
904
894
|
// is valued 1:1; other tokens have no price source at this boundary, so
|
|
905
|
-
// their USD value
|
|
895
|
+
// their price and USD value are unknown.
|
|
906
896
|
const balances: Balance[] = account.assets
|
|
907
897
|
.filter((asset) => toRequiredBig(asset.balance, 'balance').gt(0))
|
|
908
898
|
.map((a) => {
|
|
909
899
|
const assetId = String(a.asset_id)
|
|
900
|
+
const settlement = a.asset_id === collateral.assetIndex
|
|
910
901
|
return {
|
|
911
902
|
categoryId: spotCategoryId,
|
|
912
903
|
asset:
|
|
913
904
|
assetRegistry.get(assetId) ??
|
|
914
905
|
lighterAsset(assetId, a.symbol, providerKey),
|
|
915
906
|
units: a.balance,
|
|
916
|
-
valueUsd:
|
|
907
|
+
valueUsd: settlement ? a.balance : '0',
|
|
908
|
+
...(settlement ? { price: '1' } : {}),
|
|
917
909
|
}
|
|
918
910
|
})
|
|
919
911
|
|
|
@@ -1063,24 +1055,22 @@ export const createLighterProvider = (
|
|
|
1063
1055
|
}
|
|
1064
1056
|
},
|
|
1065
1057
|
|
|
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
1058
|
async getOrders(
|
|
1073
1059
|
params: ProviderGetOrdersParams,
|
|
1074
1060
|
opts?: SDKRequestOptions
|
|
1075
1061
|
): Promise<OrdersResponse> {
|
|
1062
|
+
const statuses = new Set(params.statuses ?? ACTIVE_ORDER_STATUSES)
|
|
1063
|
+
const empty: OrdersResponse = {
|
|
1064
|
+
provider: providerKey,
|
|
1065
|
+
orders: [],
|
|
1066
|
+
pagination: { limit: params.limit ?? 0, hasMore: false },
|
|
1067
|
+
}
|
|
1068
|
+
if (statuses.size === 0) {
|
|
1069
|
+
return empty
|
|
1070
|
+
}
|
|
1076
1071
|
const token = await resolveAuthToken(opts, params.address)
|
|
1077
1072
|
if (token === undefined) {
|
|
1078
|
-
return
|
|
1079
|
-
provider: providerKey,
|
|
1080
|
-
openOrders: [],
|
|
1081
|
-
triggerOrders: [],
|
|
1082
|
-
pagination: { limit: params.limit ?? 0, hasMore: false },
|
|
1083
|
-
}
|
|
1073
|
+
return empty
|
|
1084
1074
|
}
|
|
1085
1075
|
|
|
1086
1076
|
const client = apiClient(opts)
|
|
@@ -1089,69 +1079,73 @@ export const createLighterProvider = (
|
|
|
1089
1079
|
fetchDetailedAccount(client, params.address),
|
|
1090
1080
|
registry.sync(),
|
|
1091
1081
|
])
|
|
1092
|
-
|
|
1093
1082
|
const marketId =
|
|
1094
1083
|
params.marketId === undefined
|
|
1095
1084
|
? LIGHTER_ALL_MARKETS_WILDCARD
|
|
1096
1085
|
: Number(registry.require(params.marketId).id)
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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 []
|
|
1086
|
+
let active = false
|
|
1087
|
+
let terminal = false
|
|
1088
|
+
for (const status of statuses) {
|
|
1089
|
+
if (isActiveOrderStatus(status)) {
|
|
1090
|
+
active = true
|
|
1091
|
+
} else {
|
|
1092
|
+
terminal = true
|
|
1093
|
+
}
|
|
1123
1094
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1095
|
+
const [activeResponse, inactiveResponse] = await retryOnRevoked(
|
|
1096
|
+
opts,
|
|
1097
|
+
params.address,
|
|
1098
|
+
token,
|
|
1099
|
+
(t) =>
|
|
1100
|
+
Promise.all([
|
|
1101
|
+
active && params.cursor === undefined
|
|
1102
|
+
? fetchActiveOrders(client, t, account.index, marketId)
|
|
1103
|
+
: undefined,
|
|
1104
|
+
terminal
|
|
1105
|
+
? client.getAuthed<LtOrdersResponse>(
|
|
1106
|
+
'/api/v1/accountInactiveOrders',
|
|
1107
|
+
t,
|
|
1108
|
+
{
|
|
1109
|
+
account_index: account.index,
|
|
1110
|
+
market_id: marketId,
|
|
1111
|
+
limit: params.limit ?? LIGHTER_HISTORY_PAGE_SIZE,
|
|
1112
|
+
...(params.cursor === undefined
|
|
1113
|
+
? {}
|
|
1114
|
+
: { cursor: params.cursor }),
|
|
1115
|
+
}
|
|
1116
|
+
)
|
|
1117
|
+
: undefined,
|
|
1118
|
+
])
|
|
1139
1119
|
)
|
|
1140
|
-
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1120
|
+
const orders: Order[] = []
|
|
1121
|
+
for (const response of [activeResponse, inactiveResponse]) {
|
|
1122
|
+
for (const raw of wireList(response?.orders)) {
|
|
1123
|
+
// `get`, not `require`: a market id the backend list no longer
|
|
1124
|
+
// carries drops only its own row instead of rejecting the whole
|
|
1125
|
+
// page. The registry warns once per unresolved id. A delisted
|
|
1126
|
+
// market still resolves, so its rows stay.
|
|
1127
|
+
const market = registry.get(String(raw.market_index))
|
|
1128
|
+
if (market === undefined) {
|
|
1129
|
+
continue
|
|
1130
|
+
}
|
|
1131
|
+
const order = mapOrder(raw, market)
|
|
1132
|
+
if (statuses.has(order.status)) {
|
|
1133
|
+
orders.push(order)
|
|
1151
1134
|
}
|
|
1152
1135
|
}
|
|
1153
1136
|
}
|
|
1154
|
-
|
|
1137
|
+
const cursor = inactiveResponse?.next_cursor || undefined
|
|
1138
|
+
return {
|
|
1139
|
+
provider: providerKey,
|
|
1140
|
+
orders,
|
|
1141
|
+
pagination: {
|
|
1142
|
+
limit: terminal
|
|
1143
|
+
? (params.limit ?? LIGHTER_HISTORY_PAGE_SIZE)
|
|
1144
|
+
: orders.length,
|
|
1145
|
+
hasMore: cursor !== undefined,
|
|
1146
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
1147
|
+
},
|
|
1148
|
+
}
|
|
1155
1149
|
},
|
|
1156
1150
|
|
|
1157
1151
|
/**
|
|
@@ -1189,7 +1183,7 @@ export const createLighterProvider = (
|
|
|
1189
1183
|
`Lighter order ${params.id} not found for ${params.address}`
|
|
1190
1184
|
)
|
|
1191
1185
|
const detail = (order: LtOrder): Order =>
|
|
1192
|
-
|
|
1186
|
+
mapOrder(order, registry.require(String(order.market_index)))
|
|
1193
1187
|
|
|
1194
1188
|
// A tx-hash route would require mapping the caller's executeAction tx
|
|
1195
1189
|
// hash → wasm nonce → matching order, which the LI.FI backend did via
|
|
@@ -1433,9 +1427,6 @@ export const createLighterProvider = (
|
|
|
1433
1427
|
: Promise.resolve(),
|
|
1434
1428
|
])
|
|
1435
1429
|
|
|
1436
|
-
const assetSymbol = (assetId: number): string =>
|
|
1437
|
-
assetRegistry.get(String(assetId))?.displaySymbol ?? String(assetId)
|
|
1438
|
-
|
|
1439
1430
|
const items: ActivityItem[] = [
|
|
1440
1431
|
...history.deposits.deposits.map(
|
|
1441
1432
|
(d): ActivityItem => ({
|
|
@@ -1443,11 +1434,9 @@ export const createLighterProvider = (
|
|
|
1443
1434
|
provider: providerKey,
|
|
1444
1435
|
timestamp: toIsoFromMs(d.timestamp),
|
|
1445
1436
|
type: ActivityType.DEPOSIT,
|
|
1446
|
-
asset:
|
|
1437
|
+
asset: assetRegistry.require(String(d.asset_id)),
|
|
1447
1438
|
amount: d.amount,
|
|
1448
|
-
explorerLink: d.l1_tx_hash
|
|
1449
|
-
? `https://scan.li.fi/tx/${d.l1_tx_hash}`
|
|
1450
|
-
: undefined,
|
|
1439
|
+
explorerLink: explorerTxUrl(bridgeChainId, d.l1_tx_hash),
|
|
1451
1440
|
})
|
|
1452
1441
|
),
|
|
1453
1442
|
// `/withdraw/history` carries no fee field, so `fee` stays absent
|
|
@@ -1458,11 +1447,9 @@ export const createLighterProvider = (
|
|
|
1458
1447
|
provider: providerKey,
|
|
1459
1448
|
timestamp: toIsoFromMs(w.timestamp),
|
|
1460
1449
|
type: ActivityType.WITHDRAWAL,
|
|
1461
|
-
asset:
|
|
1450
|
+
asset: assetRegistry.require(String(w.asset_id)),
|
|
1462
1451
|
amount: w.amount,
|
|
1463
|
-
explorerLink: w.l1_tx_hash
|
|
1464
|
-
? `https://scan.li.fi/tx/${w.l1_tx_hash}`
|
|
1465
|
-
: undefined,
|
|
1452
|
+
explorerLink: explorerTxUrl(bridgeChainId, w.l1_tx_hash),
|
|
1466
1453
|
})
|
|
1467
1454
|
),
|
|
1468
1455
|
// `get`, not `require`: a market id the backend list no longer carries
|
|
@@ -1555,13 +1542,15 @@ export const createLighterProvider = (
|
|
|
1555
1542
|
type: ActivityType.TRANSFER,
|
|
1556
1543
|
direction,
|
|
1557
1544
|
counterpartyAccountIndex,
|
|
1558
|
-
asset:
|
|
1545
|
+
asset: assetRegistry.require(String(t.asset_id)),
|
|
1559
1546
|
amount: t.amount,
|
|
1560
1547
|
// The venue charges the transfer fee in the deployment's
|
|
1561
1548
|
// settlement asset, not in the asset the row moves, so a spot
|
|
1562
1549
|
// token transfer still reports a collateral-denominated fee.
|
|
1563
1550
|
fees: [{ amount: t.fee, asset: collateral.displaySymbol }],
|
|
1564
|
-
explorerLink:
|
|
1551
|
+
explorerLink: isPlaceholderTxHash(t.tx_hash)
|
|
1552
|
+
? undefined
|
|
1553
|
+
: explorerTxUrlFromBase(explorerTxBaseUrl, t.tx_hash),
|
|
1565
1554
|
meta: {
|
|
1566
1555
|
transferType: t.type,
|
|
1567
1556
|
txHash: t.tx_hash,
|
package/src/accountConfig.ts
CHANGED
|
@@ -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`.
|
|
47
|
-
*
|
|
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
|
|
42
|
+
return null
|
|
56
43
|
}
|
|
57
44
|
const enumerated = descriptor.params?.[0]?.values ?? []
|
|
58
45
|
return enumerated.some((option) => option.value === userTierName)
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExplorerChainId } from '@lifi/perps-sdk'
|
|
1
2
|
import type { FeeTier, LighterProviderKey } from '@lifi/perps-types'
|
|
2
3
|
import { LT_ASSET_ID_USDC } from './types/action.js'
|
|
3
4
|
|
|
@@ -114,6 +115,11 @@ export interface LighterDeployment {
|
|
|
114
115
|
* When omitted, transfer links are not emitted for this deployment.
|
|
115
116
|
*/
|
|
116
117
|
explorerTxBaseUrl?: string
|
|
118
|
+
/**
|
|
119
|
+
* L1 the deployment's bridge settles on. Deposit and withdrawal rows report
|
|
120
|
+
* an `l1_tx_hash` on this chain, not on the zkLighter L2.
|
|
121
|
+
*/
|
|
122
|
+
bridgeChainId: ExplorerChainId
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
/**
|
|
@@ -128,6 +134,7 @@ export const LIGHTER_MAINNET_DEPLOYMENT: LighterDeployment = Object.freeze({
|
|
|
128
134
|
signerChainId: LIGHTER_MAINNET_SIGNER_CHAIN_ID,
|
|
129
135
|
collateral: LIGHTER_COLLATERAL_ASSETS[LIGHTER_PROVIDER_KEY],
|
|
130
136
|
explorerTxBaseUrl: DEFAULT_LIGHTER_EXPLORER_TX_BASE_URL,
|
|
137
|
+
bridgeChainId: ExplorerChainId.ETHEREUM,
|
|
131
138
|
})
|
|
132
139
|
|
|
133
140
|
/**
|
|
@@ -155,9 +162,9 @@ export const LIGHTER_RH_WS_URL = 'wss://api.rh.lighter.xyz/stream'
|
|
|
155
162
|
export const LIGHTER_RH_SIGNER_CHAIN_ID = 466324
|
|
156
163
|
|
|
157
164
|
/**
|
|
158
|
-
* Lighter on Robinhood chain, as served by `lighterRhProvider()`. No
|
|
159
|
-
* base URL: the RH zkLighter explorer is unpublished, so transfer
|
|
160
|
-
* left unset rather than pointed at the mainnet explorer.
|
|
165
|
+
* Lighter on Robinhood chain, as served by `lighterRhProvider()`. No L2
|
|
166
|
+
* explorer base URL: the RH zkLighter explorer is unpublished, so transfer
|
|
167
|
+
* links are left unset rather than pointed at the mainnet explorer.
|
|
161
168
|
*
|
|
162
169
|
* @public
|
|
163
170
|
*/
|
|
@@ -168,6 +175,7 @@ export const LIGHTER_RH_DEPLOYMENT: LighterDeployment = Object.freeze({
|
|
|
168
175
|
signerChainId: LIGHTER_RH_SIGNER_CHAIN_ID,
|
|
169
176
|
collateral: LIGHTER_COLLATERAL_ASSETS[LIGHTER_RH_PROVIDER_KEY],
|
|
170
177
|
explorerTxBaseUrl: undefined,
|
|
178
|
+
bridgeChainId: ExplorerChainId.ROBINHOOD,
|
|
171
179
|
})
|
|
172
180
|
|
|
173
181
|
/** @internal */
|
|
@@ -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
|
)
|
package/src/types/submit.ts
CHANGED
|
@@ -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
|
|
5
|
-
* `tx_info` are the signed WASM transaction
|
|
6
|
-
*
|
|
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
|
|
32
|
-
* `tx_types` is a JSON-encoded number array and `tx_infos` a
|
|
33
|
-
* string array; corresponding entries are submitted in array
|
|
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
|
*/
|
package/src/types/ws.ts
CHANGED
|
@@ -65,7 +65,7 @@ export type LtWsMarketStatsAllMessage = LtWsMessage & {
|
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Spot market stats carry no funding/open-interest fields and address
|
|
68
|
-
* markets by their
|
|
68
|
+
* markets by their `market_id`.
|
|
69
69
|
* @public
|
|
70
70
|
*/
|
|
71
71
|
export type LtWsSpotMarketStats = {
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { PerpsError } from '@lifi/perps-sdk'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type ActivityItem,
|
|
4
|
+
ActivityType,
|
|
5
|
+
PerpsErrorCode,
|
|
6
|
+
} from '@lifi/perps-types'
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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
|
|
136
|
-
return
|
|
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 => {
|
package/src/utils/index.ts
CHANGED
|
@@ -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'
|
package/src/utils/mapFill.ts
CHANGED
|
@@ -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,
|
|
@@ -17,6 +12,7 @@ import {
|
|
|
17
12
|
} from '../constants.js'
|
|
18
13
|
import type { LtTrade } from '../types/index.js'
|
|
19
14
|
import { leverageFromImf } from './mapPosition.js'
|
|
15
|
+
import { isPlaceholderTxHash } from './txHash.js'
|
|
20
16
|
|
|
21
17
|
/**
|
|
22
18
|
* Fee charged on a fill, in the market's quote asset. Lighter publishes the
|
|
@@ -131,7 +127,6 @@ export const mapFill = (
|
|
|
131
127
|
type: OrderType.LIMIT,
|
|
132
128
|
size: trade.size,
|
|
133
129
|
price: trade.price,
|
|
134
|
-
status: FillStatus.FILLED,
|
|
135
130
|
liquidity: isMaker ? LiquidityRole.MAKER : LiquidityRole.TAKER,
|
|
136
131
|
// Lighter charges the fill fee in the market's quote asset.
|
|
137
132
|
fee:
|
|
@@ -162,6 +157,8 @@ export const mapFill = (
|
|
|
162
157
|
trade.size
|
|
163
158
|
),
|
|
164
159
|
createdAt: new Date(trade.timestamp).toISOString(),
|
|
165
|
-
explorerLink:
|
|
160
|
+
explorerLink: isPlaceholderTxHash(trade.tx_hash)
|
|
161
|
+
? undefined
|
|
162
|
+
: explorerTxUrl(ExplorerChainId.LIGHTER, trade.tx_hash),
|
|
166
163
|
}
|
|
167
164
|
}
|