zklighter-perps 1.0.302 → 1.0.304

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.
@@ -259,6 +259,8 @@ models/Status.ts
259
259
  models/StockBalanceSheet.ts
260
260
  models/StockCashFlowStatement.ts
261
261
  models/StockFinancial.ts
262
+ models/StockFinancialDividendEvent.ts
263
+ models/StockFinancialDividends.ts
262
264
  models/StockFinancialEarnings.ts
263
265
  models/StockFinancialEarningsEvent.ts
264
266
  models/StockFinancialHistoricalEarning.ts
package/README.md CHANGED
@@ -1,86 +1,50 @@
1
- # zklighter-perps-ts
1
+ # zklighter-perps
2
2
 
3
- Auto-generated TypeScript SDK for the Lighter Perps API, published to npm as
3
+ TypeScript client for the Lighter Perps REST API, published to npm as
4
4
  [`zklighter-perps`](https://www.npmjs.com/package/zklighter-perps).
5
5
 
6
- The SDK source (`index.ts`, `runtime.ts`, `apis/`, `models/`) is **generated** by
7
- the [OpenAPI Generator](https://openapi-generator.tech) from the backend's
8
- OpenAPI spec. Do not edit the generated files by hand they are overwritten on
9
- every regeneration. The package ships raw `.ts` (`"main": "index.ts"`), so
10
- consumers compile it themselves.
6
+ The client is generated from the backend's OpenAPI spec with
7
+ [OpenAPI Generator](https://openapi-generator.tech) (`typescript-fetch`). It
8
+ ships raw `.ts` (`"main": "index.ts"`), so your bundler or `tsc` compiles it
9
+ along with your own code.
11
10
 
12
- ## Generation pipeline (CircleCI)
11
+ ## Install
13
12
 
14
- Defined in [`.circleci/config.yml`](.circleci/config.yml). The `update_ts_sdk`
15
- workflow (manually triggered) runs three jobs in sequence:
16
-
17
- 1. **`update_openapi`** — clones `zklighter-perps`, runs `goctl-swagger` to emit
18
- the raw `openapi.json`, and persists it. This job no longer transforms the
19
- spec; it only generates and persists it.
20
- 2. **`openapi_postprocess`** — runs
21
- [`.circleci/openapi_postprocess.py`](.circleci/openapi_postprocess.py), the
22
- **single source of truth** for all spec fixups (see below).
23
- 3. **`update_ts_sdk`** — runs the OpenAPI Generator on the post-processed spec,
24
- bumps the package version, and opens a PR.
25
-
26
- The `update_npm_package` workflow runs on `main`:
13
+ ```bash
14
+ npm install zklighter-perps
15
+ ```
27
16
 
28
- - **`typecheck`** — `npm install && npm run typecheck` (`tsc --noEmit` using
29
- [`tsconfig.json`](tsconfig.json)). Runs on every branch, including the
30
- timestamped branches opened by `update_ts_sdk`, so a regenerated SDK that
31
- would fail to compile in a consumer (e.g. `perps-fe`) is caught on its PR.
32
- - **`update_npm_package`** — publishes to npm (requires `typecheck` to pass;
33
- `main` only).
34
- - **`publish_prerelease`** — on non-`main` branches with an open PR, publishes
35
- a prerelease version (`0.<PR>.0-<actor>.<sha>.<branch>`, npm dist-tag =
36
- branch name) and posts/updates a PR comment with the exact version to drop
37
- into a consumer's `package.json`, mirroring the prerelease flow in
38
- `zklighter-react-store`.
17
+ ## Usage
39
18
 
40
- ## OpenAPI post-processing
19
+ ```ts
20
+ import { Configuration, OrderApi } from 'zklighter-perps'
41
21
 
42
- The spec emitted by `goctl-swagger` has quirks that either break the OpenAPI
43
- Generator or produce an awkward SDK, so we post-process it before generation.
44
- All of this lives in **one place** — `.circleci/openapi_postprocess.py`.
45
- (Historically some of these fixes were inline `jq`/`sed` commands in the
46
- `update_openapi` job; they have been consolidated into the script so the logic
47
- is in a single, testable file.)
22
+ const orderApi = new OrderApi(
23
+ new Configuration({ basePath: 'https://mainnet.zklighter.elliot.ai' }),
24
+ )
48
25
 
49
- Why each transform exists:
26
+ const orderBooks = await orderApi.orderBooks()
27
+ ```
50
28
 
51
- 1. **Drop internal endpoints/definitions** (`/api/v1/feedback`,
52
- `/api/v1/ws_status`, `/stream`, `/api/v1/permission`, `ReqSendFeedback`)
53
- not part of the public SDK surface.
54
- 2. **Strip empty-string keys** the generator emits `""` keys the OpenAPI
55
- Generator cannot process.
56
- 3. **Mirror `summary` into `description`** — preserves the original
57
- human-readable text as the generated doc comment before `summary` is
58
- overwritten in step 8.
59
- 4. **Seed `summary` from `operationId`** — normalizes naming before step 8.
60
- 5. **Add a documented `400` response** referencing `ResultCode` to every
61
- operation, so the SDK models the standard error shape.
62
- 6. **Model the `types` parameter as a byte array** (`array` of `uint8`) instead
63
- of the non-standard scalar the spec declares.
64
- 7. **Remove placeholder `"-"` enum/array values** that aren't real values.
65
- 8. **Derive stable, path-based `operationId`/`summary`** (e.g.
66
- `/api/v1/account` → `account`) so generated method names are predictable.
67
- 9. **Inline non-standard `int16`/`float64`/map `$ref`s** (`int16`, `float64`,
68
- `mapint16string`, `mapstringfloat64`) — these are not standard OpenAPI and
69
- make the generator emit broken model references.
70
- 10. **Move array-level `enum` into `items`** — correct placement for array
71
- schemas.
72
- 11. **Make the `transfer/history` `type` param an array** of enum strings.
73
- 12. **Trim over-eager `required` fields** — the backend marks fields required
74
- that are actually optional in responses; we relax them so deserialization
75
- doesn't reject valid payloads.
29
+ Every API group has a class in [`apis/`](apis) (`AccountApi`, `OrderApi`,
30
+ `TransactionApi`, ...) and every request/response type lives in
31
+ [`models/`](models). `basePath` defaults to mainnet; pass the testnet URL to
32
+ target testnet. Endpoints that need authentication take an `auth` parameter.
76
33
 
77
- ## Local development
34
+ ## Development
78
35
 
79
36
  ```bash
80
- npm install # installs TypeScript (the only dependency)
81
- npm run typecheck # same check CI runs: tsc --noEmit
37
+ npm install # also wires the git hooks (.githooks)
38
+ npm run typecheck # tsc --noEmit, the same check CI runs
39
+ npm run scan:secrets
82
40
  ```
83
41
 
84
- To test the post-processing script against a spec locally, place an
85
- `openapi.json` next to the script and run `python3 openapi_postprocess.py`
86
- (it reads/writes `./openapi.json`).
42
+ `index.ts`, `runtime.ts`, `apis/` and `models/` are generated. Do not edit
43
+ them by hand; they are overwritten on the next regeneration. Regeneration,
44
+ spec post-processing and npm publishing all run in CircleCI and are documented
45
+ in [`.circleci/README.md`](.circleci/README.md).
46
+
47
+ A pre-commit hook scans staged files for secrets, and `npm publish` runs the
48
+ same scan over the whole package before uploading. Both use
49
+ [secretlint](https://github.com/secretlint/secretlint) with the recommended
50
+ preset; exclusions go in `.secretlintignore`.
@@ -36,7 +36,7 @@ export interface StockFinancialsRequest {
36
36
  export class StockfinancialsApi extends runtime.BaseAPI {
37
37
 
38
38
  /**
39
- * Get stock financials. Returns stock_financial for a single market_id, or stock_financials for every stock market when market_id is omitted. fields narrows the response to a subset of currency, exchange, industry, sector, country, market_cap, next_earnings; omit it to get the full record, which requires market_id.
39
+ * Get stock financials. Returns stock_financial for a single market_id, or stock_financials for every stock market when market_id is omitted. fields narrows the response to a subset of currency, exchange, industry, sector, country, market_cap, next_earnings, next_dividend; omit it to get the full record, which requires market_id.
40
40
  * stockFinancials
41
41
  */
42
42
  async stockFinancialsRaw(requestParameters: StockFinancialsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<RespStockFinancials>> {
@@ -63,7 +63,7 @@ export class StockfinancialsApi extends runtime.BaseAPI {
63
63
  }
64
64
 
65
65
  /**
66
- * Get stock financials. Returns stock_financial for a single market_id, or stock_financials for every stock market when market_id is omitted. fields narrows the response to a subset of currency, exchange, industry, sector, country, market_cap, next_earnings; omit it to get the full record, which requires market_id.
66
+ * Get stock financials. Returns stock_financial for a single market_id, or stock_financials for every stock market when market_id is omitted. fields narrows the response to a subset of currency, exchange, industry, sector, country, market_cap, next_earnings, next_dividend; omit it to get the full record, which requires market_id.
67
67
  * stockFinancials
68
68
  */
69
69
  async stockFinancials(requestParameters: StockFinancialsRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<RespStockFinancials> {
@@ -25,6 +25,12 @@ import {
25
25
  StockFinancialProfileFromJSONTyped,
26
26
  StockFinancialProfileToJSON,
27
27
  } from './StockFinancialProfile';
28
+ import type { StockFinancialDividends } from './StockFinancialDividends';
29
+ import {
30
+ StockFinancialDividendsFromJSON,
31
+ StockFinancialDividendsFromJSONTyped,
32
+ StockFinancialDividendsToJSON,
33
+ } from './StockFinancialDividends';
28
34
  import type { StockFinancialEarnings } from './StockFinancialEarnings';
29
35
  import {
30
36
  StockFinancialEarningsFromJSON,
@@ -56,6 +62,12 @@ export interface StockFinancial {
56
62
  * @memberof StockFinancial
57
63
  */
58
64
  earnings: StockFinancialEarnings;
65
+ /**
66
+ *
67
+ * @type {StockFinancialDividends}
68
+ * @memberof StockFinancial
69
+ */
70
+ dividends: StockFinancialDividends;
59
71
  /**
60
72
  *
61
73
  * @type {StockFinancialStatements}
@@ -71,6 +83,7 @@ export function instanceOfStockFinancial(value: object): value is StockFinancial
71
83
  if (!('market_id' in value) || value['market_id'] === undefined) return false;
72
84
  if (!('profile' in value) || value['profile'] === undefined) return false;
73
85
  if (!('earnings' in value) || value['earnings'] === undefined) return false;
86
+ if (!('dividends' in value) || value['dividends'] === undefined) return false;
74
87
  if (!('statements' in value) || value['statements'] === undefined) return false;
75
88
  return true;
76
89
  }
@@ -88,6 +101,7 @@ export function StockFinancialFromJSONTyped(json: any, ignoreDiscriminator: bool
88
101
  'market_id': json['market_id'],
89
102
  'profile': StockFinancialProfileFromJSON(json['profile']),
90
103
  'earnings': StockFinancialEarningsFromJSON(json['earnings']),
104
+ 'dividends': StockFinancialDividendsFromJSON(json['dividends']),
91
105
  'statements': StockFinancialStatementsFromJSON(json['statements']),
92
106
  };
93
107
  }
@@ -101,6 +115,7 @@ export function StockFinancialToJSON(value?: StockFinancial | null): any {
101
115
  'market_id': value['market_id'],
102
116
  'profile': StockFinancialProfileToJSON(value['profile']),
103
117
  'earnings': StockFinancialEarningsToJSON(value['earnings']),
118
+ 'dividends': StockFinancialDividendsToJSON(value['dividends']),
104
119
  'statements': StockFinancialStatementsToJSON(value['statements']),
105
120
  };
106
121
  }
@@ -0,0 +1,153 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ *
5
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
6
+ *
7
+ * The version of the OpenAPI document:
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+
15
+ import { mapValues } from '../runtime';
16
+ /**
17
+ *
18
+ * @export
19
+ * @interface StockFinancialDividendEvent
20
+ */
21
+ export interface StockFinancialDividendEvent {
22
+ /**
23
+ *
24
+ * @type {string}
25
+ * @memberof StockFinancialDividendEvent
26
+ */
27
+ source: StockFinancialDividendEventSourceEnum;
28
+ /**
29
+ *
30
+ * @type {string}
31
+ * @memberof StockFinancialDividendEvent
32
+ */
33
+ ex_date: string;
34
+ /**
35
+ *
36
+ * @type {string}
37
+ * @memberof StockFinancialDividendEvent
38
+ */
39
+ record_date: string;
40
+ /**
41
+ *
42
+ * @type {string}
43
+ * @memberof StockFinancialDividendEvent
44
+ */
45
+ payment_date: string;
46
+ /**
47
+ *
48
+ * @type {string}
49
+ * @memberof StockFinancialDividendEvent
50
+ */
51
+ declaration_date: string;
52
+ /**
53
+ *
54
+ * @type {number}
55
+ * @memberof StockFinancialDividendEvent
56
+ */
57
+ amount: number;
58
+ /**
59
+ *
60
+ * @type {number}
61
+ * @memberof StockFinancialDividendEvent
62
+ */
63
+ adj_amount: number;
64
+ /**
65
+ *
66
+ * @type {string}
67
+ * @memberof StockFinancialDividendEvent
68
+ */
69
+ frequency: string;
70
+ /**
71
+ *
72
+ * @type {string}
73
+ * @memberof StockFinancialDividendEvent
74
+ */
75
+ currency: string;
76
+ /**
77
+ *
78
+ * @type {string}
79
+ * @memberof StockFinancialDividendEvent
80
+ */
81
+ status: string;
82
+ }
83
+
84
+
85
+ /**
86
+ * @export
87
+ */
88
+ export const StockFinancialDividendEventSourceEnum = {
89
+ Fmp: 'fmp',
90
+ Robinhood: 'robinhood'
91
+ } as const;
92
+ export type StockFinancialDividendEventSourceEnum = typeof StockFinancialDividendEventSourceEnum[keyof typeof StockFinancialDividendEventSourceEnum];
93
+
94
+
95
+ /**
96
+ * Check if a given object implements the StockFinancialDividendEvent interface.
97
+ */
98
+ export function instanceOfStockFinancialDividendEvent(value: object): value is StockFinancialDividendEvent {
99
+ if (!('source' in value) || value['source'] === undefined) return false;
100
+ if (!('ex_date' in value) || value['ex_date'] === undefined) return false;
101
+ if (!('record_date' in value) || value['record_date'] === undefined) return false;
102
+ if (!('payment_date' in value) || value['payment_date'] === undefined) return false;
103
+ if (!('declaration_date' in value) || value['declaration_date'] === undefined) return false;
104
+ if (!('amount' in value) || value['amount'] === undefined) return false;
105
+ if (!('adj_amount' in value) || value['adj_amount'] === undefined) return false;
106
+ if (!('frequency' in value) || value['frequency'] === undefined) return false;
107
+ if (!('currency' in value) || value['currency'] === undefined) return false;
108
+ if (!('status' in value) || value['status'] === undefined) return false;
109
+ return true;
110
+ }
111
+
112
+ export function StockFinancialDividendEventFromJSON(json: any): StockFinancialDividendEvent {
113
+ return StockFinancialDividendEventFromJSONTyped(json, false);
114
+ }
115
+
116
+ export function StockFinancialDividendEventFromJSONTyped(json: any, ignoreDiscriminator: boolean): StockFinancialDividendEvent {
117
+ if (json == null) {
118
+ return json;
119
+ }
120
+ return {
121
+
122
+ 'source': json['source'],
123
+ 'ex_date': json['ex_date'],
124
+ 'record_date': json['record_date'],
125
+ 'payment_date': json['payment_date'],
126
+ 'declaration_date': json['declaration_date'],
127
+ 'amount': json['amount'],
128
+ 'adj_amount': json['adj_amount'],
129
+ 'frequency': json['frequency'],
130
+ 'currency': json['currency'],
131
+ 'status': json['status'],
132
+ };
133
+ }
134
+
135
+ export function StockFinancialDividendEventToJSON(value?: StockFinancialDividendEvent | null): any {
136
+ if (value == null) {
137
+ return value;
138
+ }
139
+ return {
140
+
141
+ 'source': value['source'],
142
+ 'ex_date': value['ex_date'],
143
+ 'record_date': value['record_date'],
144
+ 'payment_date': value['payment_date'],
145
+ 'declaration_date': value['declaration_date'],
146
+ 'amount': value['amount'],
147
+ 'adj_amount': value['adj_amount'],
148
+ 'frequency': value['frequency'],
149
+ 'currency': value['currency'],
150
+ 'status': value['status'],
151
+ };
152
+ }
153
+
@@ -0,0 +1,85 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /**
4
+ *
5
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
6
+ *
7
+ * The version of the OpenAPI document:
8
+ *
9
+ *
10
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11
+ * https://openapi-generator.tech
12
+ * Do not edit the class manually.
13
+ */
14
+
15
+ import { mapValues } from '../runtime';
16
+ import type { StockFinancialDividendEvent } from './StockFinancialDividendEvent';
17
+ import {
18
+ StockFinancialDividendEventFromJSON,
19
+ StockFinancialDividendEventFromJSONTyped,
20
+ StockFinancialDividendEventToJSON,
21
+ } from './StockFinancialDividendEvent';
22
+
23
+ /**
24
+ *
25
+ * @export
26
+ * @interface StockFinancialDividends
27
+ */
28
+ export interface StockFinancialDividends {
29
+ /**
30
+ *
31
+ * @type {StockFinancialDividendEvent}
32
+ * @memberof StockFinancialDividends
33
+ */
34
+ next?: StockFinancialDividendEvent;
35
+ /**
36
+ *
37
+ * @type {Array<StockFinancialDividendEvent>}
38
+ * @memberof StockFinancialDividends
39
+ */
40
+ history: Array<StockFinancialDividendEvent>;
41
+ /**
42
+ *
43
+ * @type {Array<StockFinancialDividendEvent>}
44
+ * @memberof StockFinancialDividends
45
+ */
46
+ distributions: Array<StockFinancialDividendEvent>;
47
+ }
48
+
49
+ /**
50
+ * Check if a given object implements the StockFinancialDividends interface.
51
+ */
52
+ export function instanceOfStockFinancialDividends(value: object): value is StockFinancialDividends {
53
+ if (!('history' in value) || value['history'] === undefined) return false;
54
+ if (!('distributions' in value) || value['distributions'] === undefined) return false;
55
+ return true;
56
+ }
57
+
58
+ export function StockFinancialDividendsFromJSON(json: any): StockFinancialDividends {
59
+ return StockFinancialDividendsFromJSONTyped(json, false);
60
+ }
61
+
62
+ export function StockFinancialDividendsFromJSONTyped(json: any, ignoreDiscriminator: boolean): StockFinancialDividends {
63
+ if (json == null) {
64
+ return json;
65
+ }
66
+ return {
67
+
68
+ 'next': json['next'] == null ? undefined : StockFinancialDividendEventFromJSON(json['next']),
69
+ 'history': ((json['history'] as Array<any>).map(StockFinancialDividendEventFromJSON)),
70
+ 'distributions': ((json['distributions'] as Array<any>).map(StockFinancialDividendEventFromJSON)),
71
+ };
72
+ }
73
+
74
+ export function StockFinancialDividendsToJSON(value?: StockFinancialDividends | null): any {
75
+ if (value == null) {
76
+ return value;
77
+ }
78
+ return {
79
+
80
+ 'next': StockFinancialDividendEventToJSON(value['next']),
81
+ 'history': ((value['history'] as Array<any>).map(StockFinancialDividendEventToJSON)),
82
+ 'distributions': ((value['distributions'] as Array<any>).map(StockFinancialDividendEventToJSON)),
83
+ };
84
+ }
85
+
@@ -112,10 +112,10 @@ export interface TvSymbolInfo {
112
112
  has_intraday: boolean;
113
113
  /**
114
114
  *
115
- * @type {boolean}
115
+ * @type {Array<boolean>}
116
116
  * @memberof TvSymbolInfo
117
117
  */
118
- has_no_volume: boolean;
118
+ has_no_volume: Array<boolean>;
119
119
  /**
120
120
  *
121
121
  * @type {boolean}
package/models/index.ts CHANGED
@@ -237,6 +237,8 @@ export * from './Status';
237
237
  export * from './StockBalanceSheet';
238
238
  export * from './StockCashFlowStatement';
239
239
  export * from './StockFinancial';
240
+ export * from './StockFinancialDividendEvent';
241
+ export * from './StockFinancialDividends';
240
242
  export * from './StockFinancialEarnings';
241
243
  export * from './StockFinancialEarningsEvent';
242
244
  export * from './StockFinancialHistoricalEarning';
package/openapi.json CHANGED
@@ -4832,7 +4832,7 @@
4832
4832
  "consumes": [
4833
4833
  "multipart/form-data"
4834
4834
  ],
4835
- "description": "Get stock financials. Returns stock_financial for a single market_id, or stock_financials for every stock market when market_id is omitted. fields narrows the response to a subset of currency, exchange, industry, sector, country, market_cap, next_earnings; omit it to get the full record, which requires market_id."
4835
+ "description": "Get stock financials. Returns stock_financial for a single market_id, or stock_financials for every stock market when market_id is omitted. fields narrows the response to a subset of currency, exchange, industry, sector, country, market_cap, next_earnings, next_dividend; omit it to get the full record, which requires market_id."
4836
4836
  }
4837
4837
  },
4838
4838
  "/api/v1/syntheticSpotInfo": {
@@ -15548,6 +15548,9 @@
15548
15548
  "earnings": {
15549
15549
  "$ref": "#/definitions/StockFinancialEarnings"
15550
15550
  },
15551
+ "dividends": {
15552
+ "$ref": "#/definitions/StockFinancialDividends"
15553
+ },
15551
15554
  "statements": {
15552
15555
  "$ref": "#/definitions/StockFinancialStatements"
15553
15556
  }
@@ -15557,9 +15560,89 @@
15557
15560
  "market_id",
15558
15561
  "profile",
15559
15562
  "earnings",
15563
+ "dividends",
15560
15564
  "statements"
15561
15565
  ]
15562
15566
  },
15567
+ "StockFinancialDividendEvent": {
15568
+ "type": "object",
15569
+ "properties": {
15570
+ "source": {
15571
+ "type": "string",
15572
+ "enum": [
15573
+ "fmp",
15574
+ "robinhood"
15575
+ ]
15576
+ },
15577
+ "ex_date": {
15578
+ "type": "string"
15579
+ },
15580
+ "record_date": {
15581
+ "type": "string"
15582
+ },
15583
+ "payment_date": {
15584
+ "type": "string"
15585
+ },
15586
+ "declaration_date": {
15587
+ "type": "string"
15588
+ },
15589
+ "amount": {
15590
+ "type": "number",
15591
+ "format": "double"
15592
+ },
15593
+ "adj_amount": {
15594
+ "type": "number",
15595
+ "format": "double"
15596
+ },
15597
+ "frequency": {
15598
+ "type": "string"
15599
+ },
15600
+ "currency": {
15601
+ "type": "string"
15602
+ },
15603
+ "status": {
15604
+ "type": "string"
15605
+ }
15606
+ },
15607
+ "title": "StockFinancialDividendEvent",
15608
+ "required": [
15609
+ "source",
15610
+ "ex_date",
15611
+ "record_date",
15612
+ "payment_date",
15613
+ "declaration_date",
15614
+ "amount",
15615
+ "adj_amount",
15616
+ "frequency",
15617
+ "currency",
15618
+ "status"
15619
+ ]
15620
+ },
15621
+ "StockFinancialDividends": {
15622
+ "type": "object",
15623
+ "properties": {
15624
+ "next": {
15625
+ "$ref": "#/definitions/StockFinancialDividendEvent"
15626
+ },
15627
+ "history": {
15628
+ "type": "array",
15629
+ "items": {
15630
+ "$ref": "#/definitions/StockFinancialDividendEvent"
15631
+ }
15632
+ },
15633
+ "distributions": {
15634
+ "type": "array",
15635
+ "items": {
15636
+ "$ref": "#/definitions/StockFinancialDividendEvent"
15637
+ }
15638
+ }
15639
+ },
15640
+ "title": "StockFinancialDividends",
15641
+ "required": [
15642
+ "history",
15643
+ "distributions"
15644
+ ]
15645
+ },
15563
15646
  "StockFinancialEarnings": {
15564
15647
  "type": "object",
15565
15648
  "properties": {
@@ -16859,8 +16942,10 @@
16859
16942
  "format": "boolean"
16860
16943
  },
16861
16944
  "has-no-volume": {
16862
- "type": "boolean",
16863
- "format": "boolean"
16945
+ "type": "array",
16946
+ "items": {
16947
+ "type": "boolean"
16948
+ }
16864
16949
  },
16865
16950
  "has-daily": {
16866
16951
  "type": "boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zklighter-perps",
3
- "version": "1.0.302",
3
+ "version": "1.0.304",
4
4
  "description": "Lighter Perps SDK",
5
5
  "main": "index.ts",
6
6
  "directories": {
@@ -8,8 +8,11 @@
8
8
  "test": "test"
9
9
  },
10
10
  "scripts": {
11
+ "prepare": "node -e \"require('fs').existsSync('.git')&&require('child_process').execFileSync('git',['config','core.hooksPath','.githooks'])\"",
11
12
  "test": "echo \"Error: no test specified\" && exit 1",
12
- "typecheck": "tsc --noEmit"
13
+ "typecheck": "tsc --noEmit",
14
+ "scan:secrets": "secretlint \"**/*\"",
15
+ "prepublishOnly": "npm run scan:secrets"
13
16
  },
14
17
  "repository": {
15
18
  "type": "git",
@@ -22,6 +25,8 @@
22
25
  },
23
26
  "homepage": "https://github.com/elliottech/zklighter-perps-ts#readme",
24
27
  "devDependencies": {
28
+ "@secretlint/secretlint-rule-preset-recommend": "13.0.5",
29
+ "secretlint": "13.0.5",
25
30
  "typescript": "7.0.2"
26
31
  }
27
32
  }
@@ -1,291 +0,0 @@
1
- version: 2.1
2
-
3
- jobs:
4
- update_openapi:
5
- docker:
6
- - image: cimg/go:1.22.5
7
-
8
- working_directory: ~/project
9
-
10
- steps:
11
- - checkout
12
-
13
- - run:
14
- name: Clone zklighter-perps repository
15
- command: |
16
- git clone -b << pipeline.parameters.BE_BRANCH >> --depth 1 https://${GITHUB_TOKEN}@github.com/elliottech/zklighter-perps.git
17
-
18
- - run:
19
- name: Download goctl-swagger binary
20
- command: |
21
- mv goctl-swagger-amd zklighter-perps/service/apiserver
22
- cd zklighter-perps/service/apiserver
23
- chmod +x goctl-swagger-amd
24
-
25
- - run:
26
- name: Run goctl api plugin
27
- command: |
28
- cd zklighter-perps/service/apiserver
29
- GO111MODULE=on go install github.com/zeromicro/go-zero/tools/goctl@v1.6.6
30
- sed 's/struct{}/{}/g' server.api > temp.txt && cp temp.txt server.api && rm temp.txt
31
- goctl api plugin --plugin ./goctl-swagger-amd="swagger -filename openapi.json -host mainnet.zklighter.elliot.ai -schemes https" --api server.api --dir .
32
-
33
- # NOTE: all openapi.json post-processing now lives in the dedicated
34
- # `openapi_postprocess` job (.circleci/openapi_postprocess.py). This job
35
- # only generates and persists the raw spec. See README.md.
36
-
37
- - store_artifacts:
38
- path: ~/project/zklighter-perps/service/apiserver/openapi.json
39
-
40
- - persist_to_workspace:
41
- root: ~/project/zklighter-perps/service/apiserver
42
- paths:
43
- - openapi.json
44
-
45
- openapi_postprocess:
46
- docker:
47
- - image: cimg/python:3.10.0
48
- working_directory: ~/project
49
- steps:
50
- - checkout
51
-
52
- - attach_workspace:
53
- at: /tmp/workspace
54
-
55
- - run:
56
- name: Get openapi.json from workspace
57
- command: |
58
- cp /tmp/workspace/openapi.json ./.circleci
59
-
60
- - run:
61
- name: Run postprocess script
62
- command: |
63
- cd ./.circleci
64
- python3 openapi_postprocess.py
65
-
66
- - store_artifacts:
67
- path: ~/project/.circleci/openapi.json
68
-
69
- - persist_to_workspace:
70
- root: ~/project/.circleci
71
- paths:
72
- - openapi.json
73
-
74
- update_ts_sdk:
75
- docker:
76
- - image: cimg/openjdk:21.0.2
77
- working_directory: ~/project
78
- steps:
79
- - checkout
80
-
81
- - attach_workspace:
82
- at: /tmp/workspace
83
-
84
- - run:
85
- name: Get openapi.json from workspace
86
- command: |
87
- cp /tmp/workspace/openapi.json .
88
-
89
- - run:
90
- name: Download OpenAPI Generator JAR
91
- command: |
92
- wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.7.0/openapi-generator-cli-7.7.0.jar -O openapi-generator-cli.jar
93
-
94
- - run:
95
- name: Generate TS client using OpenAPI Generator
96
- command: |
97
- java -jar openapi-generator-cli.jar generate -i ./openapi.json -g typescript-fetch -o . -c config.yaml
98
- - run:
99
- name: git status
100
- command: |
101
- if git status | grep -q "nothing to commit"; then
102
- exit 1;
103
- fi
104
- - run:
105
- name: Increase package version
106
- command: |
107
- jq '.version |= (split(".") | .[2] = ((.[2] | tonumber) + 1 | tostring) | join("."))' package.json > tmp.json && mv tmp.json package.json
108
-
109
- - run:
110
- name: push to new branch
111
- command: |
112
- git remote remove origin
113
- git remote add origin https://${GITHUB_TOKEN}@github.com/elliottech/zklighter-perps-ts
114
-
115
- git config --global user.email "hasan@circleci.com"
116
- git config --global user.name "CircleCI Hasan"
117
- export BRANCH_NAME=`date +%s`
118
- git checkout -b $BRANCH_NAME
119
- git add .
120
- git commit -m "Update SDK"
121
- git push origin $BRANCH_NAME
122
-
123
- BE_BRANCH=<< pipeline.parameters.BE_BRANCH >>
124
-
125
- PR_URL=$(curl -X POST -H "Authorization: Bearer ${GITHUB_TOKEN}" -d '{"title": "'$BE_BRANCH'", "head": "'$BRANCH_NAME'", "base": "main"}' https://api.github.com/repos/elliottech/zklighter-perps-ts/pulls | jq -r '.html_url')
126
- curl -X POST -H 'Content-type: application/json' --data '{"text":"TypeScript SDK has been updated. Check the PR here: '$PR_URL'", "type": "mrkdwn"}' ${SLACK_URL}
127
-
128
- typecheck:
129
- docker:
130
- - image: cimg/node:22.4.1
131
-
132
- working_directory: ~/project
133
-
134
- steps:
135
- - checkout
136
- - run:
137
- name: Install dependencies
138
- command: |
139
- npm install
140
- - run:
141
- name: Type-check generated SDK
142
- command: |
143
- npm run typecheck
144
-
145
- update_npm_package:
146
- docker:
147
- - image: cimg/node:22.4.1
148
-
149
- steps:
150
- - checkout
151
- - run:
152
- name: Remove binaries
153
- command: |
154
- rm -rf goctl-swagger-amd
155
- rm -rf openapi-generator-cli.jar
156
- - run:
157
- name: Publish npm package
158
- command: |
159
- VERSION=$(jq -r .version package.json)
160
- if npm view "zklighter-perps@${VERSION}" version > /dev/null 2>&1; then
161
- echo "zklighter-perps@${VERSION} is already published; skipping"
162
- exit 0
163
- fi
164
- git config --global user.email "hasan@lighter.xyz"
165
- git config --global user.name "CircleCI Hasan"
166
- npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN
167
- npm publish
168
-
169
- publish_prerelease:
170
- docker:
171
- - image: cimg/node:22.4.1
172
-
173
- steps:
174
- - checkout
175
- - run:
176
- name: Find open PR for branch
177
- command: |
178
- PR_NUMBER=$(curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \
179
- "https://api.github.com/repos/elliottech/zklighter-perps-ts/pulls?head=elliottech:${CIRCLE_BRANCH}&state=open" \
180
- | jq -r '.[0].number // empty')
181
- if [ -z "$PR_NUMBER" ]; then
182
- echo "No open PR for branch ${CIRCLE_BRANCH}; skipping prerelease publish"
183
- circleci-agent step halt
184
- else
185
- echo "Found PR #${PR_NUMBER}"
186
- echo "export PR_NUMBER=${PR_NUMBER}" >> "$BASH_ENV"
187
- fi
188
- - run:
189
- name: Generate prerelease version
190
- command: |
191
- CLEAN_BRANCH_NAME=$(echo "$CIRCLE_BRANCH" | sed 's/[^a-zA-Z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
192
- # npm rejects dist-tags that parse as a semver range (e.g. an
193
- # all-numeric branch name), so prefix those.
194
- if echo "$CLEAN_BRANCH_NAME" | grep -Eq '^[0-9]+$'; then
195
- CLEAN_BRANCH_NAME="branch-${CLEAN_BRANCH_NAME}"
196
- fi
197
- CLEAN_ACTOR=$(echo "${CIRCLE_USERNAME:-circleci}" | sed 's/[^a-zA-Z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g')
198
- COMMIT_SHA=$(echo "$CIRCLE_SHA1" | cut -c1-7)
199
-
200
- # Semver-compatible version: 0.PR_NUMBER.0-ACTOR.COMMIT_SHA.BRANCH_NAME
201
- # Always < 1.0.0 so it can never shadow a real release.
202
- PRERELEASE_VERSION="0.${PR_NUMBER}.0-${CLEAN_ACTOR}.${COMMIT_SHA}.${CLEAN_BRANCH_NAME}"
203
- echo "Prerelease version: $PRERELEASE_VERSION"
204
-
205
- echo "export CLEAN_BRANCH_NAME=${CLEAN_BRANCH_NAME}" >> "$BASH_ENV"
206
- echo "export PRERELEASE_VERSION=${PRERELEASE_VERSION}" >> "$BASH_ENV"
207
- - run:
208
- name: Remove binaries
209
- command: |
210
- rm -rf goctl-swagger-amd
211
- rm -rf openapi-generator-cli.jar
212
- - run:
213
- name: Publish prerelease npm package
214
- command: |
215
- jq --arg v "$PRERELEASE_VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json
216
- npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN
217
- npm publish --tag "$CLEAN_BRANCH_NAME"
218
- - run:
219
- name: Comment on PR
220
- command: |
221
- COMMENT_BODY=$(jq -n --arg version "$PRERELEASE_VERSION" --arg tag "$CLEAN_BRANCH_NAME" \
222
- '{body: ("## 🚀 Prerelease Published\n\n**Version:** `" + $version + "`\n**Tag:** `" + $tag + "`\n\n### Update package.json:\n```json\n\"zklighter-perps\": \"" + $version + "\"\n```")}')
223
-
224
- EXISTING_COMMENT_ID=$(curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \
225
- "https://api.github.com/repos/elliottech/zklighter-perps-ts/issues/${PR_NUMBER}/comments?per_page=100" \
226
- | jq -r '[.[] | select(.body | contains("🚀 Prerelease Published"))][0].id // empty')
227
-
228
- if [ -n "$EXISTING_COMMENT_ID" ]; then
229
- curl -s -X PATCH -H "Authorization: Bearer ${GITHUB_TOKEN}" \
230
- -d "$COMMENT_BODY" \
231
- "https://api.github.com/repos/elliottech/zklighter-perps-ts/issues/comments/${EXISTING_COMMENT_ID}" > /dev/null
232
- else
233
- curl -s -X POST -H "Authorization: Bearer ${GITHUB_TOKEN}" \
234
- -d "$COMMENT_BODY" \
235
- "https://api.github.com/repos/elliottech/zklighter-perps-ts/issues/${PR_NUMBER}/comments" > /dev/null
236
- fi
237
-
238
- parameters:
239
- update_ts_sdk:
240
- default: false
241
- type: boolean
242
- BE_BRANCH:
243
- type: string
244
- default: "main"
245
-
246
- workflows:
247
- version: 2
248
- update_ts_sdk:
249
- when: << pipeline.parameters.update_ts_sdk >>
250
- jobs:
251
- - update_openapi:
252
- filters:
253
- branches:
254
- only: main
255
- - openapi_postprocess:
256
- filters:
257
- branches:
258
- only: main
259
- requires:
260
- - update_openapi
261
- - update_ts_sdk:
262
- filters:
263
- branches:
264
- only: main
265
- requires:
266
- - openapi_postprocess
267
-
268
- update_npm_package:
269
- when:
270
- not: << pipeline.parameters.update_ts_sdk >>
271
- jobs:
272
- # Runs on every branch (including the timestamped branches opened by the
273
- # update_ts_sdk workflow) so a regenerated SDK that would break consumers
274
- # fails its PR check before merge.
275
- - typecheck
276
- - update_npm_package:
277
- filters:
278
- branches:
279
- only: main
280
- requires:
281
- - typecheck
282
- # Publishes a PR prerelease (version 0.PR.0-actor.sha.branch, dist-tag =
283
- # branch name) and posts/updates a PR comment with the version to use,
284
- # mirroring the prerelease flow in zklighter-react-store.
285
- - publish_prerelease:
286
- filters:
287
- branches:
288
- ignore: main
289
- requires:
290
- - typecheck
291
-
@@ -1,366 +0,0 @@
1
- """Post-process the swagger/openapi.json before the TypeScript SDK is generated.
2
-
3
- This is the single source of truth for every transformation applied to the
4
- spec emitted by goctl-swagger. The raw spec has a number of quirks (empty keys,
5
- non-standard int16 types, missing error responses, placeholder enum values,
6
- auto-generated operation ids, etc.) that either break the OpenAPI Generator or
7
- produce an awkward SDK. See README.md ("OpenAPI post-processing") for the
8
- rationale behind each step.
9
-
10
- Previously some of these fixes lived as inline `jq`/`sed` commands in the
11
- `update_openapi` CI job. They have been consolidated here so the logic is in one
12
- place, testable, and easy to reason about.
13
-
14
- The steps below run in the same order they used to run in the pipeline:
15
- the former `jq`/`sed` transforms first, then the original Python transforms.
16
- """
17
-
18
- import json
19
-
20
- FILE = "./openapi.json"
21
-
22
-
23
- def walk(node, fn):
24
- """Apply ``fn`` to every node bottom-up, mirroring jq's ``walk``.
25
-
26
- Children are transformed before their parent so ``fn`` always sees
27
- already-processed sub-trees, matching the semantics of the jq commands
28
- these helpers replaced.
29
- """
30
- if isinstance(node, dict):
31
- node = {key: walk(value, fn) for key, value in node.items()}
32
- elif isinstance(node, list):
33
- node = [walk(item, fn) for item in node]
34
- return fn(node)
35
-
36
-
37
- with open(FILE, "r") as f:
38
- data = json.load(f)
39
-
40
- # ---------------------------------------------------------------------------
41
- # Step 1. Drop endpoints/definitions that must not be part of the public SDK.
42
- # (was: jq 'del(.paths[...], .definitions["ReqSendFeedback"])')
43
- # ---------------------------------------------------------------------------
44
- for dropped_path in [
45
- "/api/v1/feedback",
46
- "/api/v1/ws_status",
47
- "/stream",
48
- "/api/v1/permission",
49
- ]:
50
- data.get("paths", {}).pop(dropped_path, None)
51
- data.get("definitions", {}).pop("ReqSendFeedback", None)
52
-
53
-
54
- # ---------------------------------------------------------------------------
55
- # Step 2. Remove empty-string keys emitted by the swagger generator, which the
56
- # OpenAPI Generator cannot handle.
57
- # (was: jq 'walk(... with_entries(select(.key != "")) ...)')
58
- # ---------------------------------------------------------------------------
59
- def _strip_empty_keys(node):
60
- if isinstance(node, dict):
61
- return {key: value for key, value in node.items() if key != ""}
62
- return node
63
-
64
-
65
- data = walk(data, _strip_empty_keys)
66
-
67
-
68
- # ---------------------------------------------------------------------------
69
- # Step 3. Mirror `summary` into `description` so generated SDK doc comments keep
70
- # the original human-readable text before `summary` is overwritten below.
71
- # (was: jq 'walk(if has("summary") then .description = .summary end)')
72
- # ---------------------------------------------------------------------------
73
- def _summary_to_description(node):
74
- if isinstance(node, dict) and "summary" in node:
75
- node["description"] = node["summary"]
76
- return node
77
-
78
-
79
- data = walk(data, _summary_to_description)
80
-
81
-
82
- # ---------------------------------------------------------------------------
83
- # Step 4. Seed `summary` from `operationId` (Step 8 may refine it per-path).
84
- # (was: jq 'walk(if has("operationId") then .summary = .operationId end)')
85
- # ---------------------------------------------------------------------------
86
- def _operation_id_to_summary(node):
87
- if isinstance(node, dict) and "operationId" in node:
88
- node["summary"] = node["operationId"]
89
- return node
90
-
91
-
92
- data = walk(data, _operation_id_to_summary)
93
-
94
-
95
- # ---------------------------------------------------------------------------
96
- # Step 5. Give every operation a documented 400 response referencing ResultCode.
97
- # (was: jq 'walk(if has("responses") then .responses["400"] = {...} end)')
98
- # ---------------------------------------------------------------------------
99
- def _add_bad_request_response(node):
100
- if isinstance(node, dict) and isinstance(node.get("responses"), dict):
101
- node["responses"]["400"] = {
102
- "description": "Bad request",
103
- "schema": {"$ref": "#/definitions/ResultCode"},
104
- }
105
- return node
106
-
107
-
108
- data = walk(data, _add_bad_request_response)
109
-
110
-
111
- # ---------------------------------------------------------------------------
112
- # Step 6. Restore array request parameters from their request definitions. The
113
- # generator preserves arrays in definitions but flattens them in path
114
- # parameters.
115
- # ---------------------------------------------------------------------------
116
- def restore_array_parameters(data):
117
- definitions = data.get("definitions", {})
118
-
119
- for path_item in data.get("paths", {}).values():
120
- for operation in path_item.values():
121
- if not isinstance(operation, dict):
122
- continue
123
-
124
- request = definitions.get(f"Req{operation.get('operationId')}", {})
125
- properties = request.get("properties", {})
126
-
127
- for parameter in operation.get("parameters", []):
128
- property_schema = properties.get(parameter.get("name"), {})
129
- if (
130
- property_schema.get("type") != "array"
131
- or parameter.get("type") == "array"
132
- ):
133
- continue
134
-
135
- items = {"type": parameter["type"]}
136
- if "format" in parameter:
137
- items["format"] = parameter.pop("format")
138
- if "enum" in parameter:
139
- items["enum"] = parameter.pop("enum")
140
-
141
- parameter["type"] = "array"
142
- parameter["items"] = items
143
-
144
-
145
- restore_array_parameters(data)
146
-
147
-
148
- # ---------------------------------------------------------------------------
149
- # Step 7. Remove placeholder "-" values from enum/array lists.
150
- # The former `sed 's/"-",//g'` only stripped a "-" element when it was
151
- # followed by another element (i.e. not the last item); we preserve that
152
- # behaviour here.
153
- # ---------------------------------------------------------------------------
154
- def _drop_dash_values(node):
155
- if isinstance(node, list):
156
- last_index = len(node) - 1
157
- return [
158
- value
159
- for index, value in enumerate(node)
160
- if not (value == "-" and index != last_index)
161
- ]
162
- return node
163
-
164
-
165
- data = walk(data, _drop_dash_values)
166
-
167
-
168
- # ---------------------------------------------------------------------------
169
- # Step 8. Derive stable, path-based operationId/summary so generated method
170
- # names are predictable (e.g. /api/v1/account -> "account").
171
- # ---------------------------------------------------------------------------
172
- for path in data["paths"]:
173
- methods = list(data["paths"][path].keys())
174
- has_multiple_methods = len(methods) > 1
175
-
176
- for method in methods:
177
- if "api/v1/" in path:
178
- base_name = path.split("api/v1/")[1].replace("/", "_")
179
- data["paths"][path][method]["summary"] = base_name
180
- if has_multiple_methods:
181
- data["paths"][path][method]["operationId"] = f"{base_name}_{method}"
182
- else:
183
- data["paths"][path][method]["operationId"] = base_name
184
- elif "api/v2/" in path:
185
- base_name = path.split("api/v2/")[1].replace("/", "_") + "_v2"
186
- data["paths"][path][method]["summary"] = base_name
187
- if has_multiple_methods:
188
- data["paths"][path][method]["operationId"] = f"{base_name}_{method}"
189
- else:
190
- data["paths"][path][method]["operationId"] = base_name
191
- else:
192
- base_name = path.split("/")[-1]
193
- data["paths"][path][method]["summary"] = base_name
194
- if has_multiple_methods:
195
- data["paths"][path][method]["operationId"] = f"{base_name}_{method}"
196
- else:
197
- data["paths"][path][method]["operationId"] = base_name
198
-
199
- if data["paths"][path][method]["summary"] == "":
200
- data["paths"][path][method]["summary"] = "status"
201
- if has_multiple_methods:
202
- data["paths"][path][method]["operationId"] = f"status_{method}"
203
- else:
204
- data["paths"][path][method]["operationId"] = "status"
205
-
206
- # Replace $ref to int16/float64 types with inline equivalents across all
207
- # definitions. int16, float64 and derived map types are not standard OpenAPI
208
- # and cause the generator to emit broken model references.
209
- def replace_int16_refs(obj):
210
- if isinstance(obj, dict):
211
- if obj.get("$ref") == "#/definitions/int16":
212
- return {"type": "integer", "format": "int64"}
213
- if obj.get("$ref") == "#/definitions/float64":
214
- return {"type": "number", "format": "double"}
215
- if obj.get("$ref") == "#/definitions/mapint16string":
216
- return {"type": "object", "additionalProperties": {"type": "string"}}
217
- if obj.get("$ref") == "#/definitions/mapstringfloat64":
218
- return {"type": "object", "additionalProperties": {"type": "number", "format": "double"}}
219
- return {k: replace_int16_refs(v) for k, v in obj.items()}
220
- if isinstance(obj, list):
221
- return [replace_int16_refs(i) for i in obj]
222
- return obj
223
-
224
- for defn_name in list(data["definitions"]):
225
- data["definitions"][defn_name] = replace_int16_refs(
226
- data["definitions"][defn_name]
227
- )
228
-
229
- # Fix enum placement for array types: move enum from array level into items
230
- for defn in data["definitions"].values():
231
- for prop in defn.get("properties", {}).values():
232
- if prop.get("type") == "array" and "enum" in prop:
233
- prop["items"]["enum"] = prop.pop("enum")
234
-
235
- # goctl-swagger does not preserve Go pointer types. Restore nullability for
236
- # stock-financial fields that are always present but may contain JSON null.
237
- nullable_properties = {
238
- "StockBalanceSheet": {
239
- "cash_and_cash_equivalents",
240
- "total_assets",
241
- "total_liabilities",
242
- "total_equity",
243
- "total_debt",
244
- "net_debt",
245
- },
246
- "StockCashFlowStatement": {
247
- "operating_cash_flow",
248
- "capital_expenditure",
249
- "free_cash_flow",
250
- },
251
- "StockFinancialEarningsEvent": {
252
- "eps_estimated",
253
- "revenue_estimated",
254
- },
255
- "StockFinancialHistoricalEarning": {
256
- "eps_estimated",
257
- "eps_actual",
258
- "revenue_estimated",
259
- "revenue_actual",
260
- },
261
- "StockFinancialProfile": {"market_cap"},
262
- "StockIncomeStatement": {
263
- "revenue",
264
- "gross_profit",
265
- "operating_income",
266
- "ebitda",
267
- "net_income",
268
- "eps",
269
- "eps_diluted",
270
- },
271
- "UpcomingEarning": {
272
- "eps_estimated",
273
- "revenue_estimated",
274
- },
275
- }
276
-
277
- for definition_name, property_names in nullable_properties.items():
278
- properties = (
279
- data["definitions"].get(definition_name, {}).get("properties", {})
280
- )
281
- for property_name in property_names:
282
- if property_name in properties:
283
- properties[property_name]["x-nullable"] = True
284
-
285
- # These fields use `omitempty` in server.api and are absent when FMP has no
286
- # value. They are optional rather than nullable in the generated SDK.
287
- optional_properties = {
288
- "RespStockFinancials": {"stock_financials"},
289
- "StockFinancialEarnings": {"history"},
290
- "StockFinancialEarningsEvent": {"timing"},
291
- "StockFinancialPeriod": {"reported_currency"},
292
- "StockFinancialProfile": {
293
- "currency",
294
- "exchange",
295
- "industry",
296
- "sector",
297
- "country",
298
- },
299
- "StockFinancialStatements": {"annual", "quarterly"},
300
- "Token": {"backend_symbol"},
301
- "UpcomingEarning": {"currency", "timing"},
302
- }
303
-
304
- for path in data["definitions"]:
305
- if not path.startswith("Req"):
306
- required_fields = list(data["definitions"][path]["properties"].keys())
307
- if "message" in required_fields:
308
- required_fields.remove("message")
309
-
310
- if "next" in required_fields:
311
- required_fields.remove("next")
312
-
313
- if "next_cursor" in required_fields:
314
- required_fields.remove("next_cursor")
315
-
316
- if "account_share" in required_fields:
317
- required_fields.remove("account_share")
318
-
319
- if "total_funding_paid_out" in required_fields:
320
- required_fields.remove("total_funding_paid_out")
321
-
322
- if "pending_unlocks" in required_fields:
323
- required_fields.remove("pending_unlocks")
324
-
325
- if "account_trading_mode" in required_fields:
326
- required_fields.remove("account_trading_mode")
327
-
328
- if "funding_fee_discounts_enabled" in required_fields:
329
- required_fields.remove("funding_fee_discounts_enabled")
330
-
331
- if "hidden" in required_fields:
332
- required_fields.remove("hidden")
333
-
334
- if "approved_integrators" in required_fields:
335
- required_fields.remove("approved_integrators")
336
-
337
- if "ask_id_str" in required_fields:
338
- required_fields.remove("ask_id_str")
339
-
340
- if "bid_id_str" in required_fields:
341
- required_fields.remove("bid_id_str")
342
-
343
- if "market_maker_incentive_account_index" in required_fields:
344
- required_fields.remove("market_maker_incentive_account_index")
345
-
346
- if "user_tier_last_update" in required_fields:
347
- required_fields.remove("user_tier_last_update")
348
-
349
- if path == "Trade":
350
- if "taker_fee" in required_fields:
351
- required_fields.remove("taker_fee")
352
-
353
- if "maker_fee" in required_fields:
354
- required_fields.remove("maker_fee")
355
-
356
- for field in optional_properties.get(path, set()):
357
- if field in required_fields:
358
- required_fields.remove(field)
359
-
360
- if len(required_fields) > 0:
361
- data["definitions"][path]["required"] = required_fields
362
- else:
363
- data["definitions"][path].pop("required", None)
364
-
365
- with open(FILE, "w") as f:
366
- json.dump(data, f, indent=2)