@lifi/perps-sdk-provider-lighter 13.0.0 → 13.0.2

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 (42) hide show
  1. package/dist/cjs/LighterProvider.d.ts.map +1 -1
  2. package/dist/cjs/LighterProvider.js +6 -8
  3. package/dist/cjs/LighterProvider.js.map +1 -1
  4. package/dist/cjs/signers/signActions.d.ts +6 -1
  5. package/dist/cjs/signers/signActions.d.ts.map +1 -1
  6. package/dist/cjs/signers/signActions.js +49 -28
  7. package/dist/cjs/signers/signActions.js.map +1 -1
  8. package/dist/cjs/signers/wasmLoader.d.ts +0 -3
  9. package/dist/cjs/signers/wasmLoader.d.ts.map +1 -1
  10. package/dist/cjs/signers/wasmLoader.js +3 -2
  11. package/dist/cjs/signers/wasmLoader.js.map +1 -1
  12. package/dist/cjs/utils/registeredApiKey.d.ts +8 -0
  13. package/dist/cjs/utils/registeredApiKey.d.ts.map +1 -0
  14. package/dist/cjs/utils/registeredApiKey.js +11 -0
  15. package/dist/cjs/utils/registeredApiKey.js.map +1 -0
  16. package/dist/esm/LighterProvider.d.ts.map +1 -1
  17. package/dist/esm/LighterProvider.js +4 -7
  18. package/dist/esm/LighterProvider.js.map +1 -1
  19. package/dist/esm/signers/signActions.d.ts +22 -1
  20. package/dist/esm/signers/signActions.d.ts.map +1 -1
  21. package/dist/esm/signers/signActions.js +90 -47
  22. package/dist/esm/signers/signActions.js.map +1 -1
  23. package/dist/esm/signers/wasmLoader.d.ts +0 -3
  24. package/dist/esm/signers/wasmLoader.d.ts.map +1 -1
  25. package/dist/esm/signers/wasmLoader.js +7 -2
  26. package/dist/esm/signers/wasmLoader.js.map +1 -1
  27. package/dist/esm/utils/registeredApiKey.d.ts +21 -0
  28. package/dist/esm/utils/registeredApiKey.d.ts.map +1 -0
  29. package/dist/esm/utils/registeredApiKey.js +13 -0
  30. package/dist/esm/utils/registeredApiKey.js.map +1 -0
  31. package/dist/types/LighterProvider.d.ts.map +1 -1
  32. package/dist/types/signers/signActions.d.ts +22 -1
  33. package/dist/types/signers/signActions.d.ts.map +1 -1
  34. package/dist/types/signers/wasmLoader.d.ts +0 -3
  35. package/dist/types/signers/wasmLoader.d.ts.map +1 -1
  36. package/dist/types/utils/registeredApiKey.d.ts +21 -0
  37. package/dist/types/utils/registeredApiKey.d.ts.map +1 -0
  38. package/package.json +1 -1
  39. package/src/LighterProvider.ts +10 -17
  40. package/src/signers/signActions.ts +169 -58
  41. package/src/signers/wasmLoader.ts +7 -3
  42. package/src/utils/registeredApiKey.ts +34 -0
@@ -1 +1 @@
1
- {"version":3,"file":"signActions.d.ts","sourceRoot":"","sources":["../../../src/signers/signActions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAA8B,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAOnC,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,aAAa,CAAA;IACrB,QAAQ,EAAE,eAAe,CAAA;IACzB;;qCAEiC;IACjC,SAAS,EAAE,gBAAgB,CAAA;IAC3B;;;;;OAKG;IACH,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACvD;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,sBAAsB,EAC5B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,kBAAkB,EAAE,EAC3B,GAAG,EAAE,kBAAkB,GAAG,SAAS,GAClC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAkBrC;AAkUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,eAAe,EAAE,EACxB,GAAG,EAAE,kBAAkB,GAAG,SAAS,GAClC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CA6ElC;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,sBAAsB,EAC5B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,EAAE,OAAO,EAChB,GAAG,CAAC,EAAE,kBAAkB,GACvB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAsB7B"}
1
+ {"version":3,"file":"signActions.d.ts","sourceRoot":"","sources":["../../../src/signers/signActions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACrE,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAA8B,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAOnC,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAMxE,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC1E,OAAO,KAAK,EAEV,aAAa,EAEd,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,aAAa,CAAA;IACrB,QAAQ,EAAE,eAAe,CAAA;IACzB;;qCAEiC;IACjC,SAAS,EAAE,gBAAgB,CAAA;IAC3B;;;OAGG;IACH,eAAe,EAAE,sBAAsB,CAAA;IACvC;;;;;OAKG;IACH,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACvD;AAyBD;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,qBAAqB,CACnB,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,QAAO,sBAqD/C,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,sBAAsB,EAC5B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,kBAAkB,EAAE,EAC3B,GAAG,EAAE,kBAAkB,GAAG,SAAS,GAClC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAkCrC;AAgUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,eAAe,EAAE,EACxB,GAAG,EAAE,kBAAkB,GAAG,SAAS,GAClC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CA6ElC;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,sBAAsB,EAC5B,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,UAAU,EAAE,EACnB,OAAO,EAAE,OAAO,EAChB,GAAG,CAAC,EAAE,kBAAkB,GACvB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAsB7B"}
@@ -14,9 +14,6 @@ export interface LighterWasmExports {
14
14
  CreateClient: (url: string, privateKey: string, chainId: number, apiKeyIndex: number, accountIndex: number) => {
15
15
  error?: string;
16
16
  };
17
- CheckClient: (apiKeyIndex: number, accountIndex: number) => {
18
- error?: string;
19
- };
20
17
  /**
21
18
  * Returns `{authToken}` on success — note the field name is `authToken`,
22
19
  * NOT `token`. Reading the wrong field silently fails (Go sets nothing
@@ -1 +1 @@
1
- {"version":3,"file":"wasmLoader.d.ts","sourceRoot":"","sources":["../../../src/signers/wasmLoader.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,YAAY,EAAE,CACZ,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,KACjB;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACvB,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9E;;;;;OAKG;IACH,eAAe,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,KACjB;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,KACjB,UAAU,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,eAAe,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACnD,2EAA2E;IAC3E,eAAe,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACnD;;;OAGG;IACH,mBAAmB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACvD;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7E,iFAAiF;IACjF,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IAChD;;;OAGG;IACH,kBAAkB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACtD;;;;;OAKG;IACH,eAAe,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACnD;;;OAGG;IACH,gBAAgB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACpD;;;;;;;OAOG;IACH,qBAAqB,EAAE,CACrB,GAAG,IAAI,EAAE,OAAO,EAAE,KACf,UAAU,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C;;;;OAIG;IACH,uBAAuB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IAC3D;;;;OAIG;IACH,4BAA4B,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;CACjE;AAED,gBAAgB;AAChB,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAwHD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAYnE;AAmDD;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
1
+ {"version":3,"file":"wasmLoader.d.ts","sourceRoot":"","sources":["../../../src/signers/wasmLoader.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM;QACpB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,YAAY,EAAE,CACZ,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,KACjB;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACvB;;;;;OAKG;IACH,eAAe,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,KACjB;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,KACjB,UAAU,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,eAAe,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACnD,2EAA2E;IAC3E,eAAe,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACnD;;;OAGG;IACH,mBAAmB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACvD;;;;;;;;OAQG;IACH,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7E,iFAAiF;IACjF,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IAChD;;;OAGG;IACH,kBAAkB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACtD;;;;;OAKG;IACH,eAAe,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACnD;;;OAGG;IACH,gBAAgB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IACpD;;;;;;;OAOG;IACH,qBAAqB,EAAE,CACrB,GAAG,IAAI,EAAE,OAAO,EAAE,KACf,UAAU,GAAG;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C;;;;OAIG;IACH,uBAAuB,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;IAC3D;;;;OAIG;IACH,4BAA4B,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,UAAU,CAAA;CACjE;AAED,gBAAgB;AAChB,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAuHD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAYnE;AAyDD;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
@@ -0,0 +1,21 @@
1
+ import type { LighterApiClient } from './apiClient.js';
2
+ /**
3
+ * One entry of Lighter's `/api/v1/apikeys` response: the public key the venue
4
+ * holds in an API-key slot.
5
+ *
6
+ * @internal
7
+ */
8
+ export interface LighterRegisteredApiKey {
9
+ api_key_index: number;
10
+ public_key: string;
11
+ }
12
+ /** Compare Lighter public keys irrespective of `0x` prefix / casing. */
13
+ export declare const normalizeLighterPublicKey: (key: string) => string;
14
+ /**
15
+ * Read the public key Lighter holds in one API-key slot. Returns `undefined`
16
+ * when the response carries no entry for `apiKeyIndex`.
17
+ *
18
+ * @internal
19
+ */
20
+ export declare const fetchRegisteredApiKey: (client: LighterApiClient, accountIndex: number, apiKeyIndex: number) => Promise<LighterRegisteredApiKey | undefined>;
21
+ //# sourceMappingURL=registeredApiKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registeredApiKey.d.ts","sourceRoot":"","sources":["../../../src/utils/registeredApiKey.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,GAAI,KAAK,MAAM,KAAG,MACjB,CAAA;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,gBAAgB,EACxB,cAAc,MAAM,EACpB,aAAa,MAAM,KAClB,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAM7C,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifi/perps-sdk-provider-lighter",
3
- "version": "13.0.0",
3
+ "version": "13.0.2",
4
4
  "description": "Lighter provider plugin for @lifi/perps-sdk",
5
5
  "homepage": "https://github.com/lifinance/perps-sdk",
6
6
  "bugs": {
@@ -76,7 +76,10 @@ import { createAuthToken } from './signers/createAuthToken.js'
76
76
  import { LighterKeyStore } from './signers/LighterKeyStore.js'
77
77
  import { LighterReadOnlyTokenManager } from './signers/LighterReadOnlyTokenManager.js'
78
78
  import { LighterSigner } from './signers/LighterSigner.js'
79
- import { lighterSignActions } from './signers/signActions.js'
79
+ import {
80
+ createLighterApiKeyFreshness,
81
+ lighterSignActions,
82
+ } from './signers/signActions.js'
80
83
  import type {
81
84
  LtAccountLimits,
82
85
  LtDepositHistoryResponse,
@@ -119,6 +122,10 @@ import {
119
122
  toRequiredBig,
120
123
  } from './utils/index.js'
121
124
  import { mapRunningTwap } from './utils/mapTwap.js'
125
+ import {
126
+ fetchRegisteredApiKey,
127
+ normalizeLighterPublicKey,
128
+ } from './utils/registeredApiKey.js'
122
129
 
123
130
  const ZERO_FEE_TIER = { maker: '0', taker: '0' }
124
131
 
@@ -170,10 +177,6 @@ interface ReadOnlyCreationBackoff {
170
177
  attempt: number
171
178
  }
172
179
 
173
- /** Compare Lighter public keys irrespective of `0x` prefix / casing. */
174
- const normalizeLighterPublicKey = (key: string): string =>
175
- key.replace(/^0x/i, '').toLowerCase()
176
-
177
180
  /**
178
181
  * Consumer-level overrides for a ready-made Lighter provider plugin. Every
179
182
  * deployment fact — provider key, endpoints, signing chain id, collateral
@@ -285,6 +288,7 @@ export const createLighterProvider = (
285
288
  collateralAssetIndex: collateral.assetIndex,
286
289
  })
287
290
  const keyStore = new LighterKeyStore(storage, providerKey)
291
+ const apiKeyFreshness = createLighterApiKeyFreshness()
288
292
  const readOnlyTokenManager = new LighterReadOnlyTokenManager({
289
293
  storage,
290
294
  providerKey,
@@ -518,18 +522,6 @@ export const createLighterProvider = (
518
522
  }
519
523
  }
520
524
 
521
- const fetchRegisteredApiKey = async (
522
- client: LighterApiClient,
523
- accountIndex: number,
524
- apiKeyIndex: number
525
- ): Promise<{ api_key_index: number; public_key: string } | undefined> => {
526
- const response = await client.get<{
527
- code: number
528
- api_keys: Array<{ api_key_index: number; public_key: string }>
529
- }>('/api/v1/apikeys', { account_index: accountIndex })
530
- return response.api_keys?.find((k) => k.api_key_index === apiKeyIndex)
531
- }
532
-
533
525
  const fetchAccountLimits = (
534
526
  client: LighterApiClient,
535
527
  accountIndex: number,
@@ -1449,6 +1441,7 @@ export const createLighterProvider = (
1449
1441
  signer,
1450
1442
  keyStore,
1451
1443
  apiClient: apiClient(),
1444
+ apiKeyFreshness,
1452
1445
  resolveAccountIndex: async (addr) => {
1453
1446
  const apiKey = await keyStore.get(addr)
1454
1447
  if (apiKey !== null) {
@@ -16,8 +16,17 @@ import {
16
16
  LIGHTER_MUTATION_SUCCESS_CODE,
17
17
  } from '../constants.js'
18
18
  import type { ApiParams, LighterApiClient } from '../utils/apiClient.js'
19
+ import {
20
+ fetchRegisteredApiKey,
21
+ type LighterRegisteredApiKey,
22
+ normalizeLighterPublicKey,
23
+ } from '../utils/registeredApiKey.js'
19
24
  import type { LighterApiKey, LighterKeyStore } from './LighterKeyStore.js'
20
- import type { LighterSigner } from './LighterSigner.js'
25
+ import type {
26
+ LighterSignedBlob,
27
+ LighterSigner,
28
+ LighterSignerContext,
29
+ } from './LighterSigner.js'
21
30
 
22
31
  /**
23
32
  * Per-batch dependencies the Lighter `signActions` implementation needs:
@@ -35,6 +44,11 @@ export interface LighterSignActionsDeps {
35
44
  * `changeAccountTier` / `referral/use` POSTs so their auth token never
36
45
  * transits the LI.FI backend. */
37
46
  apiClient: LighterApiClient
47
+ /**
48
+ * Pre-sign guard that the stored API key still occupies its venue slot.
49
+ * Created once per provider instance so its freshness window spans batches.
50
+ */
51
+ apiKeyFreshness: LighterApiKeyFreshness
38
52
  /**
39
53
  * Resolve the user's Lighter `accountIndex` for an L1 address. Required
40
54
  * by REGISTER_API_KEY so the ChangePubKey blob carries the right
@@ -59,6 +73,89 @@ const TOKEN_AUTH_MUTATION_KINDS = new Set(['changeAccountTier', 'referralUse'])
59
73
  */
60
74
  const TOKEN_AUTH_MUTATION_DEADLINE_SECONDS = 5 * 60
61
75
 
76
+ /**
77
+ * Window in which one `/apikeys` read stands for the next. 30s keeps a burst
78
+ * of batches — several orders submitted back to back, a close-then-reduce
79
+ * sequence — at one read, and still surfaces a key rotated on another device
80
+ * on the user's next action instead of after a venue rejection.
81
+ */
82
+ const API_KEY_FRESHNESS_WINDOW_MS = 30_000
83
+
84
+ /**
85
+ * Pre-sign guard that the stored Lighter API key is still the key registered
86
+ * in its venue slot. Owns the freshness window, so one instance is created per
87
+ * provider instance and shared by every batch that instance signs.
88
+ *
89
+ * @internal
90
+ */
91
+ export interface LighterApiKeyFreshness {
92
+ assertStillRegistered(
93
+ client: LighterApiClient,
94
+ address: Address,
95
+ apiKey: LighterApiKey
96
+ ): Promise<void>
97
+ }
98
+
99
+ /**
100
+ * Build the guard {@link LighterSignActionsDeps.apiKeyFreshness} needs.
101
+ *
102
+ * @internal
103
+ */
104
+ export const createLighterApiKeyFreshness = (): LighterApiKeyFreshness => {
105
+ const checkedAtMs = new Map<string, number>()
106
+ return {
107
+ async assertStillRegistered(client, address, apiKey) {
108
+ const storedKey = normalizeLighterPublicKey(apiKey.apiKeyPublicKey)
109
+ // Keyed on the stored key, so a rotation retires the entry by itself.
110
+ const windowKey = `${apiKey.accountIndex}:${apiKey.apiKeyIndex}:${storedKey}`
111
+ const checkedAt = checkedAtMs.get(windowKey)
112
+ if (
113
+ checkedAt !== undefined &&
114
+ Date.now() - checkedAt < API_KEY_FRESHNESS_WINDOW_MS
115
+ ) {
116
+ return
117
+ }
118
+
119
+ let registered: LighterRegisteredApiKey | undefined
120
+ try {
121
+ registered = await fetchRegisteredApiKey(
122
+ client,
123
+ apiKey.accountIndex,
124
+ apiKey.apiKeyIndex
125
+ )
126
+ } catch (err) {
127
+ // A transport failure or a non-success body proves nothing about the
128
+ // slot, so the venue decides. A failed check never blocks a signature.
129
+ // The code names the failure without the response body, which lists
130
+ // the account's other key slots.
131
+ console.warn(
132
+ '[lighter] could not read the registered API key before signing; ' +
133
+ 'signing anyway and letting the venue decide.',
134
+ err instanceof PerpsError ? err.code : String(err)
135
+ )
136
+ checkedAtMs.set(windowKey, Date.now())
137
+ return
138
+ }
139
+ // A slot carrying no entry leaves nothing to compare against.
140
+ if (
141
+ registered !== undefined &&
142
+ normalizeLighterPublicKey(registered.public_key) !== storedKey
143
+ ) {
144
+ throw new PerpsError(
145
+ PerpsErrorCode.SDKError,
146
+ `The Lighter API key stored for ${address} is no longer registered: ` +
147
+ `API-key slot ${apiKey.apiKeyIndex} now holds a different key. ` +
148
+ 'Run prepareAccount / REGISTER_API_KEY to register a fresh key.'
149
+ )
150
+ }
151
+ // Every read the venue answered counts against the window, so a burst
152
+ // costs one read whatever the verdict. A mismatch throws above, so it
153
+ // never records and the next batch reads again.
154
+ checkedAtMs.set(windowKey, Date.now())
155
+ },
156
+ }
157
+ }
158
+
62
159
  /**
63
160
  * Sign a `WASM_BLOB` batch (Lighter). Ensures the user's API keypair is
64
161
  * registered first — generating one and running the REGISTER_API_KEY
@@ -78,6 +175,22 @@ export async function signWasmBlobActions(
78
175
  steps: WasmBlobActionStep[],
79
176
  ctx: SignActionsContext | undefined
80
177
  ): Promise<WasmBlobSignedActionStep[]> {
178
+ // REGISTER_API_KEY replaces the slot's key, so for such a batch the stored
179
+ // key is expected to differ from the registered one.
180
+ const replacesApiKey = steps.some(
181
+ (step) => step.action === ActionType.REGISTER_API_KEY
182
+ )
183
+ if (!replacesApiKey) {
184
+ const storedKey = await deps.keyStore.get(address)
185
+ if (storedKey !== null) {
186
+ await deps.apiKeyFreshness.assertStillRegistered(
187
+ deps.apiClient,
188
+ address,
189
+ storedKey
190
+ )
191
+ }
192
+ }
193
+
81
194
  const signed: WasmBlobSignedActionStep[] = []
82
195
  for (const step of steps) {
83
196
  if (step.action === ActionType.REGISTER_API_KEY) {
@@ -202,34 +315,41 @@ async function signRegisterApiKey(
202
315
  }
203
316
 
204
317
  /**
205
- * APPROVE_INTEGRATOR flow:
206
- * 1. Load the user's stored API key (no keypair generation).
207
- * 2. Wasm-sign the `L2ApproveIntegrator` blob with that key, obtaining the
208
- * EIP-191 L1 message the wallet must countersign.
209
- * 3. Have the user's L1 Ethereum wallet sign that message.
210
- * 4. Inject the L1 signature into the txInfo JSON as `L1Sig`.
318
+ * Shared L1-countersign flow for the Lighter WASM_BLOB actions that require
319
+ * the end-user's Ethereum wallet:
320
+ * 1. Guard that the caller supplied `ctx.userWallet`.
321
+ * 2. Load the user's stored API key (no keypair generation).
322
+ * 3. Wasm-sign the blob via `signMethod`, obtaining the EIP-191 L1 message
323
+ * the wallet must countersign.
324
+ * 4. Have the user's L1 Ethereum wallet sign that message.
325
+ * 5. Inject the L1 signature into the txInfo JSON as `L1Sig`.
211
326
  *
212
- * Requires the end-user's wallet in `ctx.userWallet`: Lighter rejects the tx
213
- * without the user's `L1Sig` when the integrator account belongs to a
214
- * different L1 address with non-zero fee caps.
327
+ * `actionLabel` names the caller's action in the missing-wallet error
328
+ * message. `signMethod` is the caller's wasm-signing call; each caller keeps
329
+ * its own doc comment stating why its flow needs the L1 signature.
215
330
  */
216
- async function signApproveIntegrator(
331
+ async function signL1CountersignedWasmAction(
217
332
  deps: LighterSignActionsDeps,
218
333
  address: Address,
219
334
  step: WasmBlobActionStep,
220
- ctx: SignActionsContext | undefined
335
+ ctx: SignActionsContext | undefined,
336
+ actionLabel: 'APPROVE_INTEGRATOR' | 'TRANSFER',
337
+ signMethod: (
338
+ wasmSignParams: Record<string, unknown>,
339
+ context: LighterSignerContext
340
+ ) => Promise<LighterSignedBlob & { messageToSign: string }>
221
341
  ): Promise<WasmBlobSignedActionStep> {
222
342
  const walletSigner = ctx?.userWallet
223
343
  if (!walletSigner) {
224
344
  throw new PerpsError(
225
345
  PerpsErrorCode.SDKError,
226
- 'APPROVE_INTEGRATOR requires the end-user wallet — pass `userWallet` to ' +
346
+ `${actionLabel} requires the end-user wallet — pass \`userWallet\` to ` +
227
347
  'createPerpsClient or call setUserWallet(walletClient).'
228
348
  )
229
349
  }
230
350
 
231
351
  const apiKey = await requireApiKey(deps, address)
232
- const signed = await deps.signer.signApproveIntegrator(step.wasmSignParams, {
352
+ const signed = await signMethod(step.wasmSignParams, {
233
353
  apiKeyPrivateKey: apiKey.apiKeyPrivateKey,
234
354
  apiKeyIndex: apiKey.apiKeyIndex,
235
355
  accountIndex: apiKey.accountIndex,
@@ -257,58 +377,49 @@ async function signApproveIntegrator(
257
377
  }
258
378
 
259
379
  /**
260
- * TRANSFER flow:
261
- * 1. Load the user's stored API key (no keypair generation).
262
- * 2. Wasm-sign the `L2Transfer` blob with that key, obtaining the EIP-191
263
- * L1 message the wallet must countersign.
264
- * 3. Have the user's L1 Ethereum wallet sign that message.
265
- * 4. Inject the L1 signature into the txInfo JSON as `L1Sig`.
266
- *
267
- * Requires the end-user's wallet in `ctx.userWallet`: the destination account
268
- * and the amount are only bound to the account owner by the user's `L1Sig`,
269
- * so the stored API key alone must not authorize the move.
380
+ * APPROVE_INTEGRATOR flow. Requires the end-user's wallet in
381
+ * `ctx.userWallet`: Lighter rejects the tx without the user's `L1Sig` when
382
+ * the integrator account belongs to a different L1 address with non-zero fee
383
+ * caps.
270
384
  */
271
- async function signTransfer(
385
+ async function signApproveIntegrator(
272
386
  deps: LighterSignActionsDeps,
273
387
  address: Address,
274
388
  step: WasmBlobActionStep,
275
389
  ctx: SignActionsContext | undefined
276
390
  ): Promise<WasmBlobSignedActionStep> {
277
- const walletSigner = ctx?.userWallet
278
- if (!walletSigner) {
279
- throw new PerpsError(
280
- PerpsErrorCode.SDKError,
281
- 'TRANSFER requires the end-user wallet — pass `userWallet` to ' +
282
- 'createPerpsClient or call setUserWallet(walletClient).'
283
- )
284
- }
285
-
286
- const apiKey = await requireApiKey(deps, address)
287
- const signed = await deps.signer.signTransfer(step.wasmSignParams, {
288
- apiKeyPrivateKey: apiKey.apiKeyPrivateKey,
289
- apiKeyIndex: apiKey.apiKeyIndex,
290
- accountIndex: apiKey.accountIndex,
291
- })
292
-
293
- const l1Signature = await walletSigner.signMessage({
294
- account: walletSigner.account,
295
- message: signed.messageToSign,
296
- })
297
-
298
- const txInfoWithL1Sig = deps.signer.embedL1Signature(
299
- signed.txInfo,
300
- l1Signature
391
+ return await signL1CountersignedWasmAction(
392
+ deps,
393
+ address,
394
+ step,
395
+ ctx,
396
+ 'APPROVE_INTEGRATOR',
397
+ (wasmSignParams, context) =>
398
+ deps.signer.signApproveIntegrator(wasmSignParams, context)
301
399
  )
400
+ }
302
401
 
303
- return {
304
- action: step.action,
305
- wasmSignParams: step.wasmSignParams,
306
- signedTx: {
307
- txType: signed.txType,
308
- txInfo: txInfoWithL1Sig,
309
- txHash: signed.txHash,
310
- },
311
- }
402
+ /**
403
+ * TRANSFER flow. Requires the end-user's wallet in `ctx.userWallet`: the
404
+ * destination account and the amount are only bound to the account owner by
405
+ * the user's `L1Sig`, so the stored API key alone must not authorize the
406
+ * move.
407
+ */
408
+ async function signTransfer(
409
+ deps: LighterSignActionsDeps,
410
+ address: Address,
411
+ step: WasmBlobActionStep,
412
+ ctx: SignActionsContext | undefined
413
+ ): Promise<WasmBlobSignedActionStep> {
414
+ return await signL1CountersignedWasmAction(
415
+ deps,
416
+ address,
417
+ step,
418
+ ctx,
419
+ 'TRANSFER',
420
+ (wasmSignParams, context) =>
421
+ deps.signer.signTransfer(wasmSignParams, context)
422
+ )
312
423
  }
313
424
 
314
425
  /**
@@ -27,7 +27,6 @@ export interface LighterWasmExports {
27
27
  apiKeyIndex: number,
28
28
  accountIndex: number
29
29
  ) => { error?: string }
30
- CheckClient: (apiKeyIndex: number, accountIndex: number) => { error?: string }
31
30
  /**
32
31
  * Returns `{authToken}` on success — note the field name is `authToken`,
33
32
  * NOT `token`. Reading the wrong field silently fails (Go sets nothing
@@ -143,7 +142,6 @@ export interface SignResult {
143
142
  const WASM_FUNCTION_NAMES = [
144
143
  'GenerateAPIKey',
145
144
  'CreateClient',
146
- 'CheckClient',
147
145
  'CreateAuthToken',
148
146
  'SignChangePubKey',
149
147
  'SignCreateOrder',
@@ -304,9 +302,14 @@ async function loadWasmUncached(): Promise<LighterWasmExports> {
304
302
  // Wait microtasks to let Go's init() register JS-bound functions.
305
303
  await yieldToGoRuntime()
306
304
 
305
+ // Go's main.go only calls `js.Global().Set(name, ...)`; it never reads those
306
+ // names back, so dispatch survives the deletion while the captured handle
307
+ // keeps working. Left installed, they let any same-origin script sign with
308
+ // the API key held inside the instance. Never restore them for convenience.
309
+ const globals = globalThis as Record<string, unknown>
307
310
  const exports: Partial<LighterWasmExports> = {}
308
311
  for (const name of WASM_FUNCTION_NAMES) {
309
- const fn = (globalThis as Record<string, unknown>)[name]
312
+ const fn = globals[name]
310
313
  if (typeof fn !== 'function') {
311
314
  throw new Error(
312
315
  `Lighter WASM did not export expected function: ${name}. ` +
@@ -314,6 +317,7 @@ async function loadWasmUncached(): Promise<LighterWasmExports> {
314
317
  )
315
318
  }
316
319
  ;(exports as Record<string, unknown>)[name] = fn
320
+ delete globals[name]
317
321
  }
318
322
 
319
323
  return exports as LighterWasmExports
@@ -0,0 +1,34 @@
1
+ import type { LighterApiClient } from './apiClient.js'
2
+
3
+ /**
4
+ * One entry of Lighter's `/api/v1/apikeys` response: the public key the venue
5
+ * holds in an API-key slot.
6
+ *
7
+ * @internal
8
+ */
9
+ export interface LighterRegisteredApiKey {
10
+ api_key_index: number
11
+ public_key: string
12
+ }
13
+
14
+ /** Compare Lighter public keys irrespective of `0x` prefix / casing. */
15
+ export const normalizeLighterPublicKey = (key: string): string =>
16
+ key.replace(/^0x/i, '').toLowerCase()
17
+
18
+ /**
19
+ * Read the public key Lighter holds in one API-key slot. Returns `undefined`
20
+ * when the response carries no entry for `apiKeyIndex`.
21
+ *
22
+ * @internal
23
+ */
24
+ export const fetchRegisteredApiKey = async (
25
+ client: LighterApiClient,
26
+ accountIndex: number,
27
+ apiKeyIndex: number
28
+ ): Promise<LighterRegisteredApiKey | undefined> => {
29
+ const response = await client.get<{
30
+ code: number
31
+ api_keys: LighterRegisteredApiKey[]
32
+ }>('/api/v1/apikeys', { account_index: accountIndex })
33
+ return response.api_keys?.find((k) => k.api_key_index === apiKeyIndex)
34
+ }