@lifi/perps-sdk 20.0.0 → 20.1.1
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 +20 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/registry/assetRegistry.d.ts +2 -1
- package/dist/cjs/registry/assetRegistry.d.ts.map +1 -1
- package/dist/cjs/registry/assetRegistry.js +4 -2
- package/dist/cjs/registry/assetRegistry.js.map +1 -1
- package/dist/cjs/registry/marketRegistry.d.ts +2 -1
- package/dist/cjs/registry/marketRegistry.d.ts.map +1 -1
- package/dist/cjs/registry/marketRegistry.js +4 -2
- package/dist/cjs/registry/marketRegistry.js.map +1 -1
- package/dist/cjs/registry/referenceDataRegistry.d.ts +5 -1
- package/dist/cjs/registry/referenceDataRegistry.d.ts.map +1 -1
- package/dist/cjs/registry/referenceDataRegistry.js +65 -5
- package/dist/cjs/registry/referenceDataRegistry.js.map +1 -1
- package/dist/cjs/transport/request.d.ts +1 -0
- package/dist/cjs/transport/request.d.ts.map +1 -1
- package/dist/cjs/transport/request.js +6 -2
- package/dist/cjs/transport/request.js.map +1 -1
- package/dist/cjs/types/withdrawal.d.ts +5 -5
- package/dist/cjs/types/withdrawal.d.ts.map +1 -1
- package/dist/cjs/utils/decimal.d.ts +1 -0
- package/dist/cjs/utils/decimal.d.ts.map +1 -1
- package/dist/cjs/utils/decimal.js +4 -1
- package/dist/cjs/utils/decimal.js.map +1 -1
- package/dist/cjs/utils/orderEntry.d.ts +6 -0
- package/dist/cjs/utils/orderEntry.d.ts.map +1 -0
- package/dist/cjs/utils/orderEntry.js +20 -0
- package/dist/cjs/utils/orderEntry.js.map +1 -0
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/websocket/WsProviderBase.d.ts.map +1 -1
- package/dist/cjs/websocket/WsProviderBase.js +11 -3
- package/dist/cjs/websocket/WsProviderBase.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/registry/assetRegistry.d.ts +2 -1
- package/dist/esm/registry/assetRegistry.d.ts.map +1 -1
- package/dist/esm/registry/assetRegistry.js +4 -2
- package/dist/esm/registry/assetRegistry.js.map +1 -1
- package/dist/esm/registry/marketRegistry.d.ts +2 -1
- package/dist/esm/registry/marketRegistry.d.ts.map +1 -1
- package/dist/esm/registry/marketRegistry.js +4 -2
- package/dist/esm/registry/marketRegistry.js.map +1 -1
- package/dist/esm/registry/referenceDataRegistry.d.ts +6 -6
- package/dist/esm/registry/referenceDataRegistry.d.ts.map +1 -1
- package/dist/esm/registry/referenceDataRegistry.js +68 -10
- package/dist/esm/registry/referenceDataRegistry.js.map +1 -1
- package/dist/esm/transport/request.d.ts +2 -0
- package/dist/esm/transport/request.d.ts.map +1 -1
- package/dist/esm/transport/request.js +6 -2
- package/dist/esm/transport/request.js.map +1 -1
- package/dist/esm/types/withdrawal.d.ts +5 -5
- package/dist/esm/types/withdrawal.d.ts.map +1 -1
- package/dist/esm/utils/decimal.d.ts +5 -0
- package/dist/esm/utils/decimal.d.ts.map +1 -1
- package/dist/esm/utils/decimal.js +7 -0
- package/dist/esm/utils/decimal.js.map +1 -1
- package/dist/esm/utils/orderEntry.d.ts +26 -0
- package/dist/esm/utils/orderEntry.d.ts.map +1 -0
- package/dist/esm/utils/orderEntry.js +34 -0
- package/dist/esm/utils/orderEntry.js.map +1 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/websocket/WsProviderBase.d.ts.map +1 -1
- package/dist/esm/websocket/WsProviderBase.js +11 -3
- package/dist/esm/websocket/WsProviderBase.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/registry/assetRegistry.d.ts +2 -1
- package/dist/types/registry/assetRegistry.d.ts.map +1 -1
- package/dist/types/registry/marketRegistry.d.ts +2 -1
- package/dist/types/registry/marketRegistry.d.ts.map +1 -1
- package/dist/types/registry/referenceDataRegistry.d.ts +6 -6
- package/dist/types/registry/referenceDataRegistry.d.ts.map +1 -1
- package/dist/types/transport/request.d.ts +2 -0
- package/dist/types/transport/request.d.ts.map +1 -1
- package/dist/types/types/withdrawal.d.ts +5 -5
- package/dist/types/types/withdrawal.d.ts.map +1 -1
- package/dist/types/utils/decimal.d.ts +5 -0
- package/dist/types/utils/decimal.d.ts.map +1 -1
- package/dist/types/utils/orderEntry.d.ts +26 -0
- package/dist/types/utils/orderEntry.d.ts.map +1 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types/websocket/WsProviderBase.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +6 -0
- package/src/registry/assetRegistry.ts +7 -5
- package/src/registry/marketRegistry.ts +7 -5
- package/src/registry/referenceDataRegistry.ts +89 -11
- package/src/transport/request.ts +12 -2
- package/src/types/withdrawal.ts +10 -5
- package/src/utils/decimal.ts +8 -0
- package/src/utils/orderEntry.ts +38 -0
- package/src/version.ts +1 -1
- package/src/websocket/WsProviderBase.ts +11 -3
|
@@ -1,14 +1,64 @@
|
|
|
1
1
|
import { PerpsErrorCode } from '@lifi/perps-types'
|
|
2
2
|
import { PerpsError } from '../errors/PerpsError.js'
|
|
3
|
+
import type { RequestOptions } from '../transport/request.js'
|
|
3
4
|
import type { PerpsSDKClient } from '../types/provider.js'
|
|
4
5
|
import { createWarnOnce } from '../utils/warnOnce.js'
|
|
5
6
|
|
|
7
|
+
function freshUntil(
|
|
8
|
+
headers: Headers,
|
|
9
|
+
requestStartedAt: number,
|
|
10
|
+
responseReceivedAt: number
|
|
11
|
+
): number {
|
|
12
|
+
const directives = (headers.get('cache-control') ?? '')
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.split(',')
|
|
15
|
+
.map((directive) => directive.trim())
|
|
16
|
+
if (
|
|
17
|
+
directives.some((directive) =>
|
|
18
|
+
/^(no-store|no-cache)(?:\s*=|$)/.test(directive)
|
|
19
|
+
) ||
|
|
20
|
+
headers
|
|
21
|
+
.get('vary')
|
|
22
|
+
?.split(',')
|
|
23
|
+
.some((name) => name.trim() === '*')
|
|
24
|
+
) {
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
const maxAges = directives.filter((directive) =>
|
|
28
|
+
/^max-age(?:\s*=|$)/.test(directive)
|
|
29
|
+
)
|
|
30
|
+
const maxAge =
|
|
31
|
+
maxAges.length === 1
|
|
32
|
+
? /^max-age\s*=\s*(?:"(\d+)"|(\d+))$/.exec(maxAges[0])
|
|
33
|
+
: null
|
|
34
|
+
if (!maxAge) {
|
|
35
|
+
return 0
|
|
36
|
+
}
|
|
37
|
+
const age = headers.get('age') ?? '0'
|
|
38
|
+
const date = headers.get('date')
|
|
39
|
+
const dateTime = date === null ? responseReceivedAt : Date.parse(date)
|
|
40
|
+
if (!/^\d+$/.test(age) || !Number.isFinite(dateTime)) {
|
|
41
|
+
return 0
|
|
42
|
+
}
|
|
43
|
+
const lifetime = Number(maxAge[1] ?? maxAge[2]) * 1000
|
|
44
|
+
const currentAge = Math.max(
|
|
45
|
+
responseReceivedAt - dateTime,
|
|
46
|
+
Number(age) * 1000 + Math.max(0, responseReceivedAt - requestStartedAt)
|
|
47
|
+
)
|
|
48
|
+
return Number.isFinite(lifetime)
|
|
49
|
+
? responseReceivedAt + lifetime - currentAge
|
|
50
|
+
: 0
|
|
51
|
+
}
|
|
52
|
+
|
|
6
53
|
/** Atomic provider reference indexes; HTTP cache headers govern freshness. @internal */
|
|
7
54
|
export abstract class ReferenceDataRegistry<T> {
|
|
8
55
|
private index = new Map<string, T>()
|
|
9
56
|
private secondaryIndexes = new Map<string, Map<string, T>>()
|
|
10
57
|
private current: readonly T[] = []
|
|
11
58
|
private inflight: Promise<readonly T[]> | undefined
|
|
59
|
+
private inflightIdentity: string | undefined
|
|
60
|
+
private cacheIdentity: string | undefined
|
|
61
|
+
private freshUntil = 0
|
|
12
62
|
private readonly warnOnce = createWarnOnce()
|
|
13
63
|
|
|
14
64
|
protected constructor(
|
|
@@ -21,21 +71,37 @@ export abstract class ReferenceDataRegistry<T> {
|
|
|
21
71
|
) {}
|
|
22
72
|
|
|
23
73
|
/** Fetch the provider's full list through the HTTP layer. */
|
|
24
|
-
protected abstract fetchItems(
|
|
74
|
+
protected abstract fetchItems(
|
|
75
|
+
onResponse: RequestOptions['onResponse']
|
|
76
|
+
): Promise<T[]>
|
|
25
77
|
|
|
26
78
|
/** The item's primary key — what {@link get} is keyed by. */
|
|
27
79
|
protected abstract keyOf(item: T): string
|
|
28
80
|
|
|
29
|
-
/**
|
|
30
|
-
* Fetch the list through the HTTP cache and rebuild the index. Concurrent
|
|
31
|
-
* callers share one in-flight fetch; a settled fetch is never reused, so
|
|
32
|
-
* HTTP `cache-control` alone governs freshness.
|
|
33
|
-
*/
|
|
81
|
+
/** Sync reference indexes, reusing only explicitly fresh Node responses. */
|
|
34
82
|
sync(): Promise<readonly T[]> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
83
|
+
const { config } = this.client
|
|
84
|
+
// Hooks can change credentials or cache policy on every call.
|
|
85
|
+
if (config.requestInterceptor || config.fetch) {
|
|
86
|
+
return this.load()
|
|
87
|
+
}
|
|
88
|
+
const identity = JSON.stringify([
|
|
89
|
+
config.apiUrl,
|
|
90
|
+
config.apiKey,
|
|
91
|
+
config.integrator,
|
|
92
|
+
])
|
|
93
|
+
if (identity === this.cacheIdentity && Date.now() < this.freshUntil) {
|
|
94
|
+
return Promise.resolve(this.current)
|
|
95
|
+
}
|
|
96
|
+
if (!this.inflight || this.inflightIdentity !== identity) {
|
|
97
|
+
const inflight = this.load(identity).finally(() => {
|
|
98
|
+
if (this.inflight === inflight) {
|
|
99
|
+
this.inflight = undefined
|
|
100
|
+
this.inflightIdentity = undefined
|
|
101
|
+
}
|
|
38
102
|
})
|
|
103
|
+
this.inflight = inflight
|
|
104
|
+
this.inflightIdentity = identity
|
|
39
105
|
}
|
|
40
106
|
return this.inflight
|
|
41
107
|
}
|
|
@@ -59,8 +125,18 @@ export abstract class ReferenceDataRegistry<T> {
|
|
|
59
125
|
return this.secondaryIndexes.get(key)?.get(id)
|
|
60
126
|
}
|
|
61
127
|
|
|
62
|
-
private async load(): Promise<readonly T[]> {
|
|
63
|
-
|
|
128
|
+
private async load(identity?: string): Promise<readonly T[]> {
|
|
129
|
+
let expiresAt = 0
|
|
130
|
+
const items = await this.fetchItems((headers, startedAt, receivedAt) => {
|
|
131
|
+
// Browsers retain their native HTTP cache and its resident response age.
|
|
132
|
+
if (
|
|
133
|
+
identity !== undefined &&
|
|
134
|
+
typeof process !== 'undefined' &&
|
|
135
|
+
process.versions?.node
|
|
136
|
+
) {
|
|
137
|
+
expiresAt = freshUntil(headers, startedAt, receivedAt)
|
|
138
|
+
}
|
|
139
|
+
})
|
|
64
140
|
const index = new Map<string, T>()
|
|
65
141
|
for (const item of items) {
|
|
66
142
|
const key = this.keyOf(item)
|
|
@@ -93,6 +169,8 @@ export abstract class ReferenceDataRegistry<T> {
|
|
|
93
169
|
this.index = index
|
|
94
170
|
this.secondaryIndexes = secondaryIndexes
|
|
95
171
|
this.current = items
|
|
172
|
+
this.cacheIdentity = identity
|
|
173
|
+
this.freshUntil = expiresAt
|
|
96
174
|
return items
|
|
97
175
|
}
|
|
98
176
|
}
|
package/src/transport/request.ts
CHANGED
|
@@ -20,6 +20,12 @@ import {
|
|
|
20
20
|
export interface RequestOptions extends RequestInit {
|
|
21
21
|
/** Per-call retry override. Falls back to the resolved client-level policy. */
|
|
22
22
|
retry?: RetryPolicy | false
|
|
23
|
+
/** Successful response metadata, delivered only after JSON decoding. */
|
|
24
|
+
onResponse?: (
|
|
25
|
+
headers: Headers,
|
|
26
|
+
requestStartedAt: number,
|
|
27
|
+
responseReceivedAt: number
|
|
28
|
+
) => void
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
/**
|
|
@@ -34,7 +40,7 @@ export async function request<T>(
|
|
|
34
40
|
options: RequestOptions = {},
|
|
35
41
|
sdkOptions?: SDKRequestOptions
|
|
36
42
|
): Promise<T> {
|
|
37
|
-
const { retry, ...fetchInit } = options
|
|
43
|
+
const { retry, onResponse, ...fetchInit } = options
|
|
38
44
|
const policy =
|
|
39
45
|
retry !== undefined
|
|
40
46
|
? resolveRetryPolicy(LIFI_RETRY_DEFAULTS, retry, LIFI_REQUEST_KEY)
|
|
@@ -64,11 +70,13 @@ export async function request<T>(
|
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
try {
|
|
73
|
+
const requestStartedAt = onResponse ? Date.now() : 0
|
|
67
74
|
const response = await fetchWithRetry(url, finalOptions, {
|
|
68
75
|
policy,
|
|
69
76
|
fetchImpl: config.fetch,
|
|
70
77
|
signal: sdkOptions?.signal ?? finalOptions.signal ?? undefined,
|
|
71
78
|
})
|
|
79
|
+
const responseReceivedAt = onResponse ? Date.now() : 0
|
|
72
80
|
|
|
73
81
|
if (!response.ok) {
|
|
74
82
|
let body: PerpsErrorBody | undefined
|
|
@@ -94,7 +102,9 @@ export async function request<T>(
|
|
|
94
102
|
throw error
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
|
|
105
|
+
const data = (await response.json()) as T
|
|
106
|
+
onResponse?.(response.headers, requestStartedAt, responseReceivedAt)
|
|
107
|
+
return data
|
|
98
108
|
} catch (error) {
|
|
99
109
|
if (error instanceof PerpsError) {
|
|
100
110
|
throw error
|
package/src/types/withdrawal.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ActionType,
|
|
3
|
+
Asset,
|
|
4
|
+
DecimalString,
|
|
5
|
+
WithdrawalRoute,
|
|
6
|
+
} from '@lifi/perps-types'
|
|
2
7
|
import type { Address } from 'viem'
|
|
3
8
|
|
|
4
9
|
export type { WithdrawalRoute }
|
|
@@ -14,13 +19,13 @@ export interface ProviderWithdrawableBalance {
|
|
|
14
19
|
assetId: string
|
|
15
20
|
route: WithdrawalRoute
|
|
16
21
|
/** Withdrawable amount in the asset's own units. Always greater than zero. */
|
|
17
|
-
available:
|
|
22
|
+
available: DecimalString
|
|
18
23
|
/**
|
|
19
24
|
* Flat venue fee on a withdrawal from this row, in the asset's own units.
|
|
20
25
|
* Absent when the venue publishes no fee for the asset. The client rejects
|
|
21
26
|
* a value that is not a non-negative decimal.
|
|
22
27
|
*/
|
|
23
|
-
withdrawalFee?:
|
|
28
|
+
withdrawalFee?: DecimalString
|
|
24
29
|
/**
|
|
25
30
|
* `true` when the venue takes `withdrawalFee` out of the requested amount,
|
|
26
31
|
* `false` when it charges the fee in addition. Set together with
|
|
@@ -40,7 +45,7 @@ export interface WithdrawableBalance {
|
|
|
40
45
|
asset: Asset
|
|
41
46
|
route: WithdrawalRoute
|
|
42
47
|
/** Withdrawable amount in the asset's own units. */
|
|
43
|
-
available:
|
|
48
|
+
available: DecimalString
|
|
44
49
|
/**
|
|
45
50
|
* Flat venue fee on a withdrawal from this row, in the asset's own units.
|
|
46
51
|
* Absent when the venue publishes no fee for the asset. With
|
|
@@ -51,7 +56,7 @@ export interface WithdrawableBalance {
|
|
|
51
56
|
* is zero or negative when the fee is at or above `available`, and the row
|
|
52
57
|
* then funds no withdrawal.
|
|
53
58
|
*/
|
|
54
|
-
withdrawalFee?:
|
|
59
|
+
withdrawalFee?: DecimalString
|
|
55
60
|
/**
|
|
56
61
|
* Present only together with `withdrawalFee`. Absent on a row from a
|
|
57
62
|
* provider plugin that does not report it; absence means unknown, not
|
package/src/utils/decimal.ts
CHANGED
|
@@ -7,6 +7,14 @@ import Big from 'big.js'
|
|
|
7
7
|
export const DivBig = Big()
|
|
8
8
|
DivBig.DP = 40
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* As `DivBig`, but truncating, so a derived amount never rounds up past the
|
|
12
|
+
* exact quotient.
|
|
13
|
+
*/
|
|
14
|
+
export const TruncBig = Big()
|
|
15
|
+
TruncBig.DP = 40
|
|
16
|
+
TruncBig.RM = Big.roundDown
|
|
17
|
+
|
|
10
18
|
/** True when every value is a finite number, so `new DivBig(value)` cannot throw. */
|
|
11
19
|
export function areFinite(...values: number[]): boolean {
|
|
12
20
|
return values.every(Number.isFinite)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type Big from 'big.js'
|
|
2
|
+
import { TruncBig } from './decimal.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base size the given margin buys at `leverage`: size = margin × leverage ÷ price.
|
|
6
|
+
* Truncates, so `size × price` never exceeds `margin × leverage`.
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export function sizeFromMargin(margin: Big, leverage: number, price: Big): Big {
|
|
10
|
+
return new TruncBig(margin).times(leverage).div(price)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Margin that holds the given base size at `leverage`: margin = size × price ÷ leverage.
|
|
15
|
+
* Truncates, so `margin × leverage` never exceeds `size × price`.
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export function marginFromSize(size: Big, leverage: number, price: Big): Big {
|
|
19
|
+
return new TruncBig(size).times(price).div(leverage)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Base size for a notional: size = notional ÷ price.
|
|
24
|
+
* Truncates, so `size × price` never exceeds the notional.
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export function sizeFromNotional(notionalUsd: Big, price: Big): Big {
|
|
28
|
+
return new TruncBig(notionalUsd).div(price)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Margin for a notional at `leverage`: margin = notional ÷ leverage.
|
|
33
|
+
* Truncates, so `margin × leverage` never exceeds the notional.
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
export function marginFromNotional(notionalUsd: Big, leverage: number): Big {
|
|
37
|
+
return new TruncBig(notionalUsd).div(leverage)
|
|
38
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -199,7 +199,9 @@ export abstract class WsProviderBase<TSub = unknown> implements WsProvider {
|
|
|
199
199
|
try {
|
|
200
200
|
await this.sendSubscribe(state)
|
|
201
201
|
} catch (err) {
|
|
202
|
-
this.wireSubs.
|
|
202
|
+
if (this.wireSubs.get(key) === state) {
|
|
203
|
+
this.wireSubs.delete(key)
|
|
204
|
+
}
|
|
203
205
|
throw err
|
|
204
206
|
}
|
|
205
207
|
}
|
|
@@ -304,7 +306,11 @@ export abstract class WsProviderBase<TSub = unknown> implements WsProvider {
|
|
|
304
306
|
try {
|
|
305
307
|
const teardown = await opening
|
|
306
308
|
const current = this.channels.get(key)
|
|
307
|
-
if (current
|
|
309
|
+
if (current !== entry) {
|
|
310
|
+
teardown()
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
if (current.listeners.size === 0) {
|
|
308
314
|
// Every listener released while opening — unwind immediately.
|
|
309
315
|
teardown()
|
|
310
316
|
this.channels.delete(key)
|
|
@@ -315,7 +321,9 @@ export abstract class WsProviderBase<TSub = unknown> implements WsProvider {
|
|
|
315
321
|
} catch (err) {
|
|
316
322
|
// Open failed: drop the channel so all (concurrent) subscribers reject
|
|
317
323
|
// cleanly and a later subscribe re-opens.
|
|
318
|
-
this.channels.
|
|
324
|
+
if (this.channels.get(key) === entry) {
|
|
325
|
+
this.channels.delete(key)
|
|
326
|
+
}
|
|
319
327
|
throw err
|
|
320
328
|
}
|
|
321
329
|
}
|