@n1xyz/nord-ts 0.1.3 → 0.1.5

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.
@@ -60,7 +60,7 @@ export interface paths {
60
60
  [name: string]: unknown;
61
61
  };
62
62
  content: {
63
- "application/json": components["schemas"]["Info2"];
63
+ "application/json": components["schemas"]["MarketsInfo"];
64
64
  };
65
65
  };
66
66
  };
@@ -362,6 +362,14 @@ export interface paths {
362
362
  "application/json": number;
363
363
  };
364
364
  };
365
+ 404: {
366
+ headers: {
367
+ [name: string]: unknown;
368
+ };
369
+ content: {
370
+ "application/json": components["schemas"]["UserNotFound"];
371
+ };
372
+ };
365
373
  };
366
374
  };
367
375
  put?: never;
@@ -1069,6 +1077,161 @@ export interface paths {
1069
1077
  patch?: never;
1070
1078
  trace?: never;
1071
1079
  };
1080
+ "/state/info": {
1081
+ parameters: {
1082
+ query?: never;
1083
+ header?: never;
1084
+ path?: never;
1085
+ cookie?: never;
1086
+ };
1087
+ get: {
1088
+ parameters: {
1089
+ query?: never;
1090
+ header?: never;
1091
+ path?: never;
1092
+ cookie?: never;
1093
+ };
1094
+ requestBody?: never;
1095
+ responses: {
1096
+ 200: {
1097
+ headers: {
1098
+ [name: string]: unknown;
1099
+ };
1100
+ content: {
1101
+ "application/json": components["schemas"]["StateInfo"];
1102
+ };
1103
+ };
1104
+ };
1105
+ };
1106
+ put?: never;
1107
+ post?: never;
1108
+ delete?: never;
1109
+ options?: never;
1110
+ head?: never;
1111
+ patch?: never;
1112
+ trace?: never;
1113
+ };
1114
+ "/fee/brackets/info": {
1115
+ parameters: {
1116
+ query?: never;
1117
+ header?: never;
1118
+ path?: never;
1119
+ cookie?: never;
1120
+ };
1121
+ get: {
1122
+ parameters: {
1123
+ query?: never;
1124
+ header?: never;
1125
+ path?: never;
1126
+ cookie?: never;
1127
+ };
1128
+ requestBody?: never;
1129
+ responses: {
1130
+ 200: {
1131
+ headers: {
1132
+ [name: string]: unknown;
1133
+ };
1134
+ content: {
1135
+ "application/json": [
1136
+ components["schemas"]["FeeTierId"],
1137
+ components["schemas"]["FeeTierConfig"]
1138
+ ][];
1139
+ };
1140
+ };
1141
+ };
1142
+ };
1143
+ put?: never;
1144
+ post?: never;
1145
+ delete?: never;
1146
+ options?: never;
1147
+ head?: never;
1148
+ patch?: never;
1149
+ trace?: never;
1150
+ };
1151
+ "/account/{account_id}/fee/tier": {
1152
+ parameters: {
1153
+ query?: never;
1154
+ header?: never;
1155
+ path?: never;
1156
+ cookie?: never;
1157
+ };
1158
+ get: {
1159
+ parameters: {
1160
+ query?: never;
1161
+ header?: never;
1162
+ path: {
1163
+ account_id: number;
1164
+ };
1165
+ cookie?: never;
1166
+ };
1167
+ requestBody?: never;
1168
+ responses: {
1169
+ 200: {
1170
+ headers: {
1171
+ [name: string]: unknown;
1172
+ };
1173
+ content: {
1174
+ "application/json": components["schemas"]["FeeTierId"];
1175
+ };
1176
+ };
1177
+ 404: {
1178
+ headers: {
1179
+ [name: string]: unknown;
1180
+ };
1181
+ content: {
1182
+ "application/json": components["schemas"]["UserNotFound"];
1183
+ };
1184
+ };
1185
+ };
1186
+ };
1187
+ put?: never;
1188
+ post?: never;
1189
+ delete?: never;
1190
+ options?: never;
1191
+ head?: never;
1192
+ patch?: never;
1193
+ trace?: never;
1194
+ };
1195
+ "/accounts/fee-tiers": {
1196
+ parameters: {
1197
+ query?: never;
1198
+ header?: never;
1199
+ path?: never;
1200
+ cookie?: never;
1201
+ };
1202
+ /** @description List fee tiers assigned to accounts. */
1203
+ get: {
1204
+ parameters: {
1205
+ query?: {
1206
+ /** @description fetch results starting with this page; query starts with first entry if page isn't specified */
1207
+ startInclusive?: number | null;
1208
+ /** @description Query returns up to 50 trades in one go. */
1209
+ pageSize?: number | null;
1210
+ };
1211
+ header?: never;
1212
+ path?: never;
1213
+ cookie?: never;
1214
+ };
1215
+ requestBody?: never;
1216
+ responses: {
1217
+ 200: {
1218
+ headers: {
1219
+ [name: string]: unknown;
1220
+ };
1221
+ content: {
1222
+ "application/json": components["schemas"]["PageResult_for_uint32_and_AccountFeeTier"];
1223
+ };
1224
+ };
1225
+ };
1226
+ };
1227
+ put?: never;
1228
+ post?: never;
1229
+ delete?: never;
1230
+ options?: never;
1231
+ head?: never;
1232
+ patch?: never;
1233
+ trace?: never;
1234
+ };
1072
1235
  "/tv": {
1073
1236
  parameters: {
1074
1237
  query?: never;
@@ -1968,7 +2131,7 @@ export interface components {
1968
2131
  } & {
1969
2132
  [key: string]: unknown;
1970
2133
  };
1971
- Info2: {
2134
+ MarketsInfo: {
1972
2135
  markets: components["schemas"]["MarketInfo"][];
1973
2136
  tokens: components["schemas"]["TokenInfo"][];
1974
2137
  };
@@ -2042,6 +2205,7 @@ export interface components {
2042
2205
  ActionNotFound: null;
2043
2206
  /** @description Returns fee parts per market per balance change per action per account. Fee is taken from user without return. Please note that some operations need some deposit which will be returned - these are not part of fees. */
2044
2207
  FillRole: "maker" | "taker";
2208
+ UserNotFound: null;
2045
2209
  OrderbookInfo: {
2046
2210
  /** Format: uint64 */
2047
2211
  updateId: number;
@@ -2119,7 +2283,6 @@ export interface components {
2119
2283
  pubkey: string;
2120
2284
  expiry: string;
2121
2285
  };
2122
- UserNotFound: null;
2123
2286
  PageQueryPart_for_String: {
2124
2287
  /** @description fetch results starting with this page; query starts with first entry if page isn't specified */
2125
2288
  startInclusive?: string | null;
@@ -2476,6 +2639,56 @@ export interface components {
2476
2639
  version?: components["schemas"]["BinaryId"] | null;
2477
2640
  };
2478
2641
  BinaryId: string;
2642
+ StateInfo: {
2643
+ version: components["schemas"]["ExecutableVersion"];
2644
+ state: components["schemas"]["EngineStateHeader"];
2645
+ };
2646
+ EngineStateHeader: {
2647
+ /** Format: uint16 */
2648
+ version: number;
2649
+ /** Format: uint64 */
2650
+ action_id: number;
2651
+ /** Format: uint64 */
2652
+ action_nonce: number;
2653
+ /** Format: uint64 */
2654
+ timestamp: number;
2655
+ paused: boolean;
2656
+ };
2657
+ /** Format: uint32 */
2658
+ FeeTierId: number;
2659
+ FeeTierConfig: {
2660
+ /** Format: uint16 */
2661
+ maker_fee_bps: number;
2662
+ /** Format: uint16 */
2663
+ taker_fee_bps: number;
2664
+ };
2665
+ PageQueryPart_for_uint32: {
2666
+ /**
2667
+ * Format: uint32
2668
+ * @description fetch results starting with this page; query starts with first entry if page isn't specified
2669
+ */
2670
+ startInclusive?: number | null;
2671
+ /**
2672
+ * Format: uint8
2673
+ * @description Query returns up to 50 trades in one go.
2674
+ * @default null
2675
+ */
2676
+ pageSize: number | null;
2677
+ };
2678
+ PageResult_for_uint32_and_AccountFeeTier: {
2679
+ /** @description Set of items for requested by query. */
2680
+ items: components["schemas"]["AccountFeeTier"][];
2681
+ /**
2682
+ * Format: uint32
2683
+ * @description If request contains more data, this is the id is set with which next request should be performed to get next page. If no more data, then it is undefined.
2684
+ */
2685
+ nextStartInclusive?: number | null;
2686
+ };
2687
+ AccountFeeTier: {
2688
+ /** Format: uint32 */
2689
+ accountId: number;
2690
+ feeTier: components["schemas"]["FeeTierId"];
2691
+ };
2479
2692
  /** @description TV config query response https://www.tradingview.com/charting-library-docs/latest/connecting_data/UDF/#data-feed-configuration-data */
2480
2693
  TvConfigResponse: {
2481
2694
  supported_resolutions: components["schemas"]["Resolution"][];
@@ -2,7 +2,6 @@ import Decimal from "decimal.js";
2
2
  import * as proto from "../../gen/nord_pb";
3
3
  import { paths } from "../../gen/openapi";
4
4
  import createClient from "openapi-fetch";
5
-
6
5
  import { create } from "@bufbuild/protobuf";
7
6
  import {
8
7
  FillMode,
@@ -21,6 +20,32 @@ import {
21
20
  toScaledU64,
22
21
  } from "../../utils";
23
22
  import { sizeDelimitedEncode } from "@bufbuild/protobuf/wire";
23
+ import { NordError } from "../utils/NordError";
24
+
25
+ type ReceiptKind = NonNullable<proto.Receipt["kind"]>;
26
+ type ExtractReceiptKind<K extends ReceiptKind["case"]> = Extract<
27
+ ReceiptKind,
28
+ { case: K }
29
+ >;
30
+
31
+ export function formatReceiptError(receipt: proto.Receipt): string {
32
+ if (receipt.kind?.case === "err") {
33
+ const err = receipt.kind.value;
34
+ return proto.Error[err] ?? err.toString();
35
+ }
36
+ return receipt.kind?.case ?? "unknown";
37
+ }
38
+
39
+ export function expectReceiptKind<K extends ReceiptKind["case"]>(
40
+ receipt: proto.Receipt,
41
+ expected: K,
42
+ action: string,
43
+ ): asserts receipt is proto.Receipt & { kind: ExtractReceiptKind<K> } {
44
+ if (receipt.kind?.case !== expected) {
45
+ const label = formatReceiptError(receipt);
46
+ throw new NordError(`Failed to ${action}: ${label}`);
47
+ }
48
+ }
24
49
 
25
50
  async function sessionSign(
26
51
  signFn: (message: Uint8Array) => Promise<Uint8Array>,
@@ -39,7 +64,7 @@ async function walletSign(
39
64
  }
40
65
 
41
66
  // Helper to create an action with common fields
42
- function createAction(
67
+ export function createAction(
43
68
  currentTimestamp: bigint,
44
69
  nonce: number,
45
70
  kind: proto.Action["kind"],
@@ -51,7 +76,7 @@ function createAction(
51
76
  });
52
77
  }
53
78
 
54
- async function sendAction(
79
+ export async function sendAction(
55
80
  serverUrl: string,
56
81
  makeSignedMessage: (message: Uint8Array) => Promise<Uint8Array>,
57
82
  action: proto.Action,
@@ -508,7 +533,6 @@ export async function removeTrigger(
508
533
  }
509
534
  throw new Error(`Unexpected receipt kind ${resp.kind?.case}`);
510
535
  }
511
-
512
536
  export type AtomicSubaction =
513
537
  | {
514
538
  kind: "place";
@@ -10,17 +10,28 @@ import {
10
10
  AccountPnlQuery,
11
11
  ActionResponse,
12
12
  AggregateMetrics,
13
- Info,
13
+ MarketsInfo,
14
14
  Market,
15
15
  MarketStats,
16
16
  NordConfig,
17
17
  OrderbookQuery,
18
18
  OrderbookResponse,
19
+ FeeTierConfig,
19
20
  PeakTpsPeriodUnit,
20
21
  SubscriptionPattern,
21
22
  Token,
22
23
  TradesResponse,
23
24
  User,
25
+ AccountTriggerInfo,
26
+ HistoryTriggerQuery,
27
+ TriggerHistoryPage,
28
+ FeeTierId,
29
+ AccountFeeTierPage,
30
+ PageResultStringOrderInfo,
31
+ PageResultStringTrade,
32
+ OrderInfoFromApi,
33
+ TokenStats,
34
+ FillRole,
24
35
  } from "../../types";
25
36
  import * as utils from "../../utils";
26
37
  import { NordWebSocketClient } from "../../websocket/index";
@@ -55,9 +66,6 @@ export class Nord {
55
66
  /** Base URL for the Nord web server */
56
67
  public readonly webServerUrl: string;
57
68
 
58
- /** Bridge verification key */
59
- public readonly bridgeVk: PublicKey;
60
-
61
69
  /** Solana RPC URL */
62
70
  public readonly solanaUrl: string;
63
71
 
@@ -70,7 +78,7 @@ export class Nord {
70
78
  /** Map of symbol to market_id */
71
79
  private symbolToMarketId: Map<string, number> = new Map();
72
80
 
73
- /** Proton client for bridge and hansel operations */
81
+ /** Proton client for proton related operations */
74
82
  public protonClient: ProtonClient;
75
83
 
76
84
  /** HTTP client for Nord operations */
@@ -81,23 +89,19 @@ export class Nord {
81
89
  *
82
90
  * @param config - Configuration options for the Nord client
83
91
  * @param config.webServerUrl - Base URL for the Nord web server
84
- * @param config.bridgeVk - Bridge verification key
85
92
  * @param config.solanaUrl - Solana cluster URL
86
93
  * @throws {Error} If required configuration is missing
87
94
  */
88
95
  private constructor({
89
- bridgeVk,
90
96
  solanaUrl,
91
97
  webServerUrl,
92
98
  protonClient,
93
99
  }: Readonly<{
94
- bridgeVk: PublicKey;
95
100
  solanaUrl: string;
96
101
  webServerUrl: string;
97
102
  protonClient: ProtonClient;
98
103
  }>) {
99
104
  this.webServerUrl = webServerUrl;
100
- this.bridgeVk = bridgeVk;
101
105
  this.solanaUrl = solanaUrl;
102
106
  this.protonClient = protonClient;
103
107
  this.httpClient = createClient<paths>({ baseUrl: webServerUrl });
@@ -226,13 +230,13 @@ export class Nord {
226
230
  *
227
231
  * @param nordConfig - Configuration options for the Nord client
228
232
  * @param nordConfig.webServerUrl - Base URL for the Nord web server
229
- * @param nordConfig.bridgeVk - Bridge verification key
233
+ * @param nordConfig.app - App address
230
234
  * @param nordConfig.solanaUrl - Solana cluster URL
231
235
  * @returns Initialized Nord client
232
236
  * @throws {NordError} If initialization fails
233
237
  */
234
238
  public static async initNord({
235
- bridgeVk: bridgeVk_,
239
+ app,
236
240
  solanaUrl,
237
241
  webServerUrl,
238
242
  }: Readonly<NordConfig>): Promise<Nord> {
@@ -240,14 +244,12 @@ export class Nord {
240
244
  // this is a dogshit api, only here to be compatible with the shitty
241
245
  // vibecoded code and not break zero one team's workflow.
242
246
  const connection = new Connection(solanaUrl, { commitment: "confirmed" });
243
- const bridgeVk = new PublicKey(bridgeVk_);
244
247
  const protonClient = await ProtonClient.init({
245
248
  protonUrl: webServerUrl,
246
- bridgeVk,
249
+ app: new PublicKey(app),
247
250
  solConn: connection,
248
251
  });
249
252
  const nord = new Nord({
250
- bridgeVk,
251
253
  protonClient,
252
254
  solanaUrl,
253
255
  webServerUrl,
@@ -619,10 +621,35 @@ export class Nord {
619
621
  * @returns Information about markets and tokens
620
622
  * @throws {NordError} If the request fails
621
623
  */
622
- public async getInfo(): Promise<Info> {
624
+ public async getInfo(): Promise<MarketsInfo> {
623
625
  return await this.GET("/info", {});
624
626
  }
625
627
 
628
+ /**
629
+ * Fetch the current fee tier brackets configured on Nord.
630
+ *
631
+ * @returns Array of fee tier identifiers paired with their configuration
632
+ * @throws {NordError} If the request fails
633
+ */
634
+ public async getFeeBrackets(): Promise<Array<[FeeTierId, FeeTierConfig]>> {
635
+ return await this.GET("/fee/brackets/info", {});
636
+ }
637
+
638
+ /**
639
+ * Retrieve the fee tier assigned to a specific account.
640
+ *
641
+ * @param accountId - Account identifier to query
642
+ * @returns Fee tier details for the requested account
643
+ * @throws {NordError} If the request fails
644
+ */
645
+ public async getAccountFeeTier(accountId: number): Promise<FeeTierId> {
646
+ return await this.GET("/account/{account_id}/fee/tier", {
647
+ params: {
648
+ path: { account_id: accountId },
649
+ },
650
+ });
651
+ }
652
+
626
653
  /**
627
654
  * Get account information
628
655
  *
@@ -638,6 +665,76 @@ export class Nord {
638
665
  });
639
666
  }
640
667
 
668
+ /**
669
+ * Get the public key associated with an account id.
670
+ *
671
+ * @param accountId - Account id to query
672
+ * @returns Base58-encoded account public key
673
+ * @throws {NordError} If the request fails
674
+ */
675
+ public async getAccountPubkey(accountId: number): Promise<string> {
676
+ return await this.GET("/account/{account_id}/pubkey", {
677
+ params: {
678
+ path: { account_id: accountId },
679
+ },
680
+ });
681
+ }
682
+
683
+ /**
684
+ * Get the withdrawal fee charged for an account.
685
+ *
686
+ * @param accountId - Account id to query
687
+ * @returns Withdrawal fee quoted in quote token units
688
+ * @throws {NordError} If the request fails
689
+ */
690
+ public async getAccountWithdrawalFee(accountId: number): Promise<number> {
691
+ return await this.GET("/account/{account_id}/fees/withdrawal", {
692
+ params: {
693
+ path: { account_id: accountId },
694
+ },
695
+ });
696
+ }
697
+
698
+ /**
699
+ * Get open orders for an account.
700
+ *
701
+ * @param accountId - Account id to query
702
+ * @param query - Optional pagination parameters
703
+ * @returns Page of orders keyed by client order id
704
+ * @throws {NordError} If the request fails
705
+ */
706
+ public async getAccountOrders(
707
+ accountId: number,
708
+ query?: { startInclusive?: string | null; pageSize?: number | null },
709
+ ): Promise<PageResultStringOrderInfo> {
710
+ return await this.GET("/account/{account_id}/orders", {
711
+ params: {
712
+ path: { account_id: accountId },
713
+ query: {
714
+ startInclusive: query?.startInclusive,
715
+ pageSize: query?.pageSize,
716
+ },
717
+ },
718
+ });
719
+ }
720
+
721
+ /**
722
+ * List account fee tiers with pagination support.
723
+ */
724
+ public async getAccountsFeeTiers(query?: {
725
+ startInclusive?: number | null;
726
+ pageSize?: number | null;
727
+ }): Promise<AccountFeeTierPage> {
728
+ return await this.GET("/accounts/fee-tiers", {
729
+ params: {
730
+ query: {
731
+ startInclusive: query?.startInclusive ?? undefined,
732
+ pageSize: query?.pageSize ?? undefined,
733
+ },
734
+ },
735
+ });
736
+ }
737
+
641
738
  /**
642
739
  * Get profit and loss history for an account
643
740
  *
@@ -680,6 +777,86 @@ export class Nord {
680
777
  });
681
778
  }
682
779
 
780
+ /**
781
+ * Fetch the per-market fee quote for an account.
782
+ *
783
+ * @param params - Market id, fee kind, and account id to quote
784
+ * @returns Fee in quote token units (negative means fee is charged)
785
+ * @throws {NordError} If the request fails
786
+ */
787
+ public async getMarketFee({
788
+ marketId,
789
+ feeKind,
790
+ accountId,
791
+ }: {
792
+ marketId: number;
793
+ feeKind: FillRole;
794
+ accountId: number;
795
+ }): Promise<number> {
796
+ return await this.GET("/market/{market_id}/fees/{fee_kind}/{account_id}", {
797
+ params: {
798
+ path: {
799
+ market_id: marketId,
800
+ fee_kind: feeKind,
801
+ account_id: accountId,
802
+ },
803
+ },
804
+ });
805
+ }
806
+
807
+ /**
808
+ * Fetch token statistics such as index price and oracle metadata.
809
+ *
810
+ * @param tokenId - Token identifier
811
+ * @returns Token stats
812
+ * @throws {NordError} If the request fails
813
+ */
814
+ public async getTokenStats(tokenId: number): Promise<TokenStats> {
815
+ return await this.GET("/tokens/{token_id}/stats", {
816
+ params: {
817
+ path: { token_id: tokenId },
818
+ },
819
+ });
820
+ }
821
+
822
+ /**
823
+ * Get order summary by order id.
824
+ *
825
+ * @param orderId - Order identifier
826
+ * @returns Order information
827
+ * @throws {NordError} If the request fails
828
+ */
829
+ public async getOrder(orderId: string): Promise<OrderInfoFromApi> {
830
+ return await this.GET("/order/{order_id}", {
831
+ params: {
832
+ path: { order_id: orderId },
833
+ },
834
+ });
835
+ }
836
+
837
+ /**
838
+ * Get trade history for a specific order.
839
+ *
840
+ * @param orderId - Order identifier
841
+ * @param query - Optional pagination parameters
842
+ * @returns Page of trades associated with the order
843
+ * @throws {NordError} If the request fails
844
+ */
845
+ public async getOrderTrades(
846
+ orderId: string,
847
+ query?: { startInclusive?: string | null; pageSize?: number | null },
848
+ ): Promise<PageResultStringTrade> {
849
+ return await this.GET("/order/{order_id}/trades", {
850
+ params: {
851
+ path: { order_id: orderId },
852
+ query: {
853
+ startInclusive: query?.startInclusive,
854
+ pageSize: query?.pageSize,
855
+ },
856
+ },
857
+ });
858
+ }
859
+
683
860
  /**
684
861
  * Check if an account exists for the given address
685
862
  *
@@ -690,4 +867,70 @@ export class Nord {
690
867
  public async accountExists(pubkey: string | PublicKey): Promise<boolean> {
691
868
  return !!(await this.getUser({ pubkey }));
692
869
  }
870
+
871
+ /**
872
+ * Fetch active triggers for an account.
873
+ *
874
+ * @param params Optional parameters containing an explicit account id.
875
+ * @throws {NordError} If no account can be resolved or the request fails.
876
+ */
877
+ async getAccountTriggers(params?: {
878
+ accountId?: number;
879
+ }): Promise<AccountTriggerInfo[]> {
880
+ const accountId = params?.accountId;
881
+
882
+ if (accountId == null) {
883
+ throw new NordError(
884
+ "Account ID is undefined. Make sure to call updateAccountId() before requesting triggers.",
885
+ );
886
+ }
887
+
888
+ try {
889
+ const triggers = await this.GET("/account/{account_id}/triggers", {
890
+ params: {
891
+ path: { account_id: accountId },
892
+ },
893
+ });
894
+ return triggers ?? [];
895
+ } catch (error) {
896
+ throw new NordError("Failed to fetch account triggers", { cause: error });
897
+ }
898
+ }
899
+
900
+ /**
901
+ * Fetch trigger history for an account.
902
+ *
903
+ * @param params Optional parameters with account id and history query filters.
904
+ * @throws {NordError} If no account can be resolved or the request fails.
905
+ */
906
+ async getAccountTriggerHistory(
907
+ params: HistoryTriggerQuery & { accountId?: number },
908
+ ): Promise<TriggerHistoryPage> {
909
+ const accountId = params?.accountId;
910
+
911
+ if (accountId == null) {
912
+ throw new NordError(
913
+ "Account ID is undefined. Make sure to call updateAccountId() before requesting trigger history.",
914
+ );
915
+ }
916
+
917
+ const { accountId: _, ...query } = params;
918
+ try {
919
+ return await this.GET("/account/{account_id}/triggers/history", {
920
+ params: {
921
+ path: { account_id: accountId },
922
+ query: {
923
+ since: query.since,
924
+ until: query.until,
925
+ pageSize: query.pageSize,
926
+ startInclusive: query.startInclusive,
927
+ },
928
+ },
929
+ });
930
+ } catch (error) {
931
+ throw new NordError("Failed to fetch account trigger history", {
932
+ cause: error,
933
+ });
934
+ }
935
+ }
693
936
  }