@n1xyz/nord-ts 0.0.1 → 0.0.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.
Files changed (260) hide show
  1. package/README.md +148 -65
  2. package/dist/bridge/client.d.ts +150 -0
  3. package/dist/bridge/client.js +394 -0
  4. package/dist/bridge/const.d.ts +23 -0
  5. package/dist/bridge/const.js +47 -0
  6. package/dist/bridge/index.d.ts +5 -0
  7. package/dist/bridge/index.js +23 -0
  8. package/dist/bridge/types.d.ts +118 -0
  9. package/dist/bridge/types.js +16 -0
  10. package/dist/bridge/utils.d.ts +64 -0
  11. package/dist/bridge/utils.js +131 -0
  12. package/dist/const.d.ts +2 -5
  13. package/dist/const.js +2 -6
  14. package/dist/gen/common.d.ts +6 -1
  15. package/dist/gen/common.js +19 -9
  16. package/dist/gen/nord.d.ts +76 -21
  17. package/dist/gen/nord.js +987 -423
  18. package/dist/idl/bridge.d.ts +2 -0
  19. package/dist/idl/bridge.js +703 -0
  20. package/dist/index.d.ts +5 -2
  21. package/dist/index.js +18 -2
  22. package/dist/nord/{actions.d.ts → api/actions.d.ts} +6 -6
  23. package/dist/nord/{actions.js → api/actions.js} +6 -10
  24. package/dist/nord/api/core.d.ts +49 -0
  25. package/dist/nord/api/core.js +121 -0
  26. package/dist/nord/api/market.d.ts +36 -0
  27. package/dist/nord/api/market.js +98 -0
  28. package/dist/nord/api/metrics.d.ts +67 -0
  29. package/dist/nord/api/metrics.js +132 -0
  30. package/dist/nord/api/queries.d.ts +81 -0
  31. package/dist/nord/api/queries.js +187 -0
  32. package/dist/nord/client/Nord.d.ts +335 -0
  33. package/dist/nord/client/Nord.js +532 -0
  34. package/dist/nord/client/NordUser.d.ts +320 -0
  35. package/dist/nord/client/NordUser.js +697 -0
  36. package/dist/nord/index.d.ts +9 -2
  37. package/dist/nord/index.js +30 -6
  38. package/dist/nord/models/Subscriber.d.ts +37 -0
  39. package/dist/nord/models/Subscriber.js +25 -0
  40. package/dist/nord/utils/NordError.d.ts +35 -0
  41. package/dist/nord/utils/NordError.js +46 -0
  42. package/dist/types.d.ts +143 -86
  43. package/dist/types.js +12 -1
  44. package/dist/utils.d.ts +9 -0
  45. package/dist/utils.js +20 -1
  46. package/dist/websocket/NordWebSocketClient.d.ts +71 -0
  47. package/dist/websocket/NordWebSocketClient.js +343 -0
  48. package/dist/websocket/events.d.ts +19 -0
  49. package/dist/websocket/events.js +2 -0
  50. package/dist/websocket/index.d.ts +2 -0
  51. package/dist/websocket/index.js +5 -0
  52. package/docs/assets/hierarchy.js +1 -0
  53. package/docs/assets/highlight.css +16 -16
  54. package/docs/assets/icons.js +17 -14
  55. package/docs/assets/icons.svg +1 -1
  56. package/docs/assets/main.js +5 -4
  57. package/docs/assets/navigation.js +1 -1
  58. package/docs/assets/search.js +1 -1
  59. package/docs/assets/style.css +1423 -1227
  60. package/docs/classes/Nord.html +189 -43
  61. package/docs/classes/NordError.html +24 -0
  62. package/docs/classes/NordUser.html +120 -35
  63. package/docs/classes/NordWebSocketClient.html +335 -0
  64. package/docs/classes/SolanaBridgeClient.html +86 -0
  65. package/docs/classes/Subscriber.html +10 -6
  66. package/docs/enums/FillMode.html +5 -5
  67. package/docs/enums/KeyType.html +4 -4
  68. package/docs/enums/MetricPeriod.html +9 -0
  69. package/docs/enums/PdaSeedType.html +9 -0
  70. package/docs/enums/PeakTpsPeriodUnit.html +7 -7
  71. package/docs/enums/Side.html +3 -3
  72. package/docs/enums/WebSocketMessageType.html +7 -0
  73. package/docs/functions/actionQueryRollman.html +6 -0
  74. package/docs/functions/actionsQueryRollman.html +6 -0
  75. package/docs/functions/aggregateMetrics-1.html +7 -0
  76. package/docs/functions/assert.html +1 -1
  77. package/docs/functions/bigIntToProtoU128.html +3 -3
  78. package/docs/functions/blockQueryRollman.html +6 -0
  79. package/docs/functions/blockSummaryQueryRollman.html +6 -0
  80. package/docs/functions/bridgeToBN.html +5 -0
  81. package/docs/functions/bufferToHex.html +4 -0
  82. package/docs/functions/cancelOrder.html +1 -0
  83. package/docs/functions/checkPubKeyLength.html +1 -1
  84. package/docs/functions/checkedFetch.html +4 -4
  85. package/docs/functions/createSession.html +1 -0
  86. package/docs/functions/decodeLengthDelimited.html +7 -6
  87. package/docs/functions/encodeLengthDelimited.html +4 -4
  88. package/docs/functions/fillModeToProtoFillMode.html +4 -4
  89. package/docs/functions/findMarket.html +1 -1
  90. package/docs/functions/findPda.html +6 -0
  91. package/docs/functions/findToken.html +1 -1
  92. package/docs/functions/fromBN.html +5 -0
  93. package/docs/functions/getAccount.html +6 -0
  94. package/docs/functions/getActionNonce.html +5 -0
  95. package/docs/functions/getCurrentTps.html +6 -0
  96. package/docs/functions/getInfo.html +5 -0
  97. package/docs/functions/getMedianLatency.html +6 -0
  98. package/docs/functions/getOrderbook.html +6 -0
  99. package/docs/functions/getPeakTps.html +6 -0
  100. package/docs/functions/getTimestamp.html +5 -0
  101. package/docs/functions/getTotalTransactions.html +5 -0
  102. package/docs/functions/getTrades.html +6 -0
  103. package/docs/functions/getUserAccountIds.html +6 -0
  104. package/docs/functions/hexToBuffer.html +4 -0
  105. package/docs/functions/initWebSocketClient.html +12 -0
  106. package/docs/functions/keypairFromPrivateKey.html +4 -0
  107. package/docs/functions/makeSigningFunction.html +4 -0
  108. package/docs/functions/makeWalletSignFn.html +5 -5
  109. package/docs/functions/marketsStats.html +5 -0
  110. package/docs/functions/optExpect.html +4 -4
  111. package/docs/functions/optMap.html +5 -5
  112. package/docs/functions/optUnwrap.html +2 -2
  113. package/docs/functions/panic.html +1 -1
  114. package/docs/functions/placeOrder.html +1 -0
  115. package/docs/functions/queryAction.html +6 -0
  116. package/docs/functions/queryBlock.html +6 -0
  117. package/docs/functions/queryLastNBlocks.html +5 -0
  118. package/docs/functions/queryPrometheus.html +6 -0
  119. package/docs/functions/queryRecentActions.html +6 -0
  120. package/docs/functions/queryRecentBlocks.html +6 -0
  121. package/docs/functions/revokeSession.html +1 -0
  122. package/docs/functions/shortenPublicKey.html +5 -0
  123. package/docs/functions/signAction.html +2 -2
  124. package/docs/functions/toBN.html +5 -0
  125. package/docs/functions/toScaledU128.html +5 -5
  126. package/docs/functions/toScaledU64.html +5 -5
  127. package/docs/functions/transfer.html +1 -0
  128. package/docs/functions/withdraw.html +1 -0
  129. package/docs/hierarchy.html +1 -0
  130. package/docs/index.html +39 -20
  131. package/docs/interfaces/Account.html +8 -8
  132. package/docs/interfaces/ActionInfo.html +8 -8
  133. package/docs/interfaces/ActionNonceResponse.html +3 -0
  134. package/docs/interfaces/ActionQuery.html +4 -4
  135. package/docs/interfaces/ActionResponse.html +8 -8
  136. package/docs/interfaces/ActionsExtendedInfo.html +10 -10
  137. package/docs/interfaces/ActionsQuery.html +5 -5
  138. package/docs/interfaces/ActionsResponse.html +6 -6
  139. package/docs/interfaces/AggregateMetrics.html +12 -12
  140. package/docs/interfaces/BlockFacts.html +10 -0
  141. package/docs/interfaces/BlockQuery.html +6 -6
  142. package/docs/interfaces/BlockResponse.html +6 -6
  143. package/docs/interfaces/BlockSummary.html +8 -8
  144. package/docs/interfaces/BlockSummaryResponse.html +6 -6
  145. package/docs/interfaces/DeltaEvent.html +6 -6
  146. package/docs/interfaces/DepositSplParams.html +10 -0
  147. package/docs/interfaces/Info.html +3 -3
  148. package/docs/interfaces/Market.html +8 -6
  149. package/docs/interfaces/MarketStats.html +7 -7
  150. package/docs/interfaces/MarketsStatsResponse.html +2 -2
  151. package/docs/interfaces/NordConfig.html +14 -5
  152. package/docs/interfaces/NordWebSocketClientEvents.html +4 -0
  153. package/docs/interfaces/NordWebSocketEvents.html +8 -0
  154. package/docs/interfaces/Order.html +6 -6
  155. package/docs/interfaces/OrderInfo.html +6 -6
  156. package/docs/interfaces/OrderbookEntry.html +4 -0
  157. package/docs/interfaces/OrderbookQuery.html +6 -0
  158. package/docs/interfaces/OrderbookResponse.html +6 -10
  159. package/docs/interfaces/OrderbookSubscription.html +159 -0
  160. package/docs/interfaces/PerpMarketStats.html +5 -5
  161. package/docs/interfaces/RollmanActionExtendedInfo.html +4 -4
  162. package/docs/interfaces/RollmanActionInfo.html +4 -4
  163. package/docs/interfaces/RollmanActionResponse.html +4 -4
  164. package/docs/interfaces/RollmanActionsResponse.html +2 -2
  165. package/docs/interfaces/RollmanBlockResponse.html +3 -3
  166. package/docs/interfaces/SPLTokenInfo.html +10 -0
  167. package/docs/interfaces/SolanaBridgeConfig.html +10 -0
  168. package/docs/interfaces/StateFacts.html +10 -0
  169. package/docs/interfaces/SubscriberConfig.html +3 -3
  170. package/docs/interfaces/TimestampResponse.html +3 -0
  171. package/docs/interfaces/Token.html +5 -5
  172. package/docs/interfaces/TokenInfo.html +5 -0
  173. package/docs/interfaces/Trade.html +5 -5
  174. package/docs/interfaces/TradeSubscription.html +159 -0
  175. package/docs/interfaces/Trades.html +5 -5
  176. package/docs/interfaces/TradesQuery.html +6 -0
  177. package/docs/interfaces/TradesResponse.html +7 -12
  178. package/docs/interfaces/TransferParams.html +8 -0
  179. package/docs/interfaces/UserAccountIdsQuery.html +3 -0
  180. package/docs/interfaces/UserAccountIdsResponse.html +3 -0
  181. package/docs/interfaces/WebSocketDeltaUpdate.html +9 -0
  182. package/docs/interfaces/WebSocketSubscription.html +4 -0
  183. package/docs/interfaces/WebSocketTradeUpdate.html +6 -0
  184. package/docs/interfaces/WebSocketUserUpdate.html +6 -0
  185. package/docs/interfaces/WithdrawalClaim.html +14 -0
  186. package/docs/interfaces/WithdrawalParams.html +8 -0
  187. package/docs/modules.html +1 -77
  188. package/docs/types/BigIntValue.html +2 -2
  189. package/docs/types/WebSocketMessage.html +1 -0
  190. package/docs/variables/DEBUG_KEYS.html +1 -1
  191. package/docs/variables/DEFAULT_FUNDING_AMOUNTS.html +1 -1
  192. package/docs/variables/DEV_TOKEN_INFOS.html +1 -1
  193. package/docs/variables/DEV_URL.html +1 -1
  194. package/docs/variables/MAX_BUFFER_LEN.html +1 -1
  195. package/docs/variables/SESSION_TTL.html +1 -1
  196. package/docs/variables/WEBSERVER_DEV_URL.html +1 -1
  197. package/docs/variables/ZERO_DECIMAL.html +1 -1
  198. package/docs/variables/_private.html +2 -0
  199. package/eslint.config.mjs +66 -0
  200. package/package.json +20 -23
  201. package/src/bridge/client.ts +487 -0
  202. package/src/bridge/const.ts +53 -0
  203. package/src/bridge/index.ts +7 -0
  204. package/src/bridge/types.ts +127 -0
  205. package/src/bridge/utils.ts +140 -0
  206. package/src/const.ts +4 -9
  207. package/src/gen/common.ts +27 -10
  208. package/src/gen/nord.ts +1045 -487
  209. package/src/idl/bridge.ts +702 -0
  210. package/src/index.ts +21 -2
  211. package/src/nord/{actions.ts → api/actions.ts} +12 -16
  212. package/src/nord/api/core.ts +130 -0
  213. package/src/nord/api/market.ts +125 -0
  214. package/src/nord/api/metrics.ts +154 -0
  215. package/src/nord/api/queries.ts +236 -0
  216. package/src/nord/client/Nord.ts +652 -0
  217. package/src/nord/client/NordUser.ts +1101 -0
  218. package/src/nord/index.ts +16 -2
  219. package/src/nord/models/Subscriber.ts +56 -0
  220. package/src/nord/utils/NordError.ts +72 -0
  221. package/src/types.ts +163 -92
  222. package/src/utils.ts +22 -1
  223. package/src/websocket/NordWebSocketClient.ts +432 -0
  224. package/src/websocket/events.ts +31 -0
  225. package/src/websocket/index.ts +2 -0
  226. package/tsconfig.eslint.json +12 -0
  227. package/.eslintignore +0 -1
  228. package/.eslintrc.js +0 -20
  229. package/dist/abis/ERC20_ABI.d.ts +0 -39
  230. package/dist/abis/ERC20_ABI.js +0 -313
  231. package/dist/abis/NORD_GETTERS_FACET_ABI.d.ts +0 -34
  232. package/dist/abis/NORD_GETTERS_FACET_ABI.js +0 -195
  233. package/dist/abis/NORD_RAMP_FACET_ABI.d.ts +0 -35
  234. package/dist/abis/NORD_RAMP_FACET_ABI.js +0 -144
  235. package/dist/abis/index.d.ts +0 -3
  236. package/dist/abis/index.js +0 -9
  237. package/dist/nord/Nord.d.ts +0 -76
  238. package/dist/nord/Nord.js +0 -376
  239. package/dist/nord/NordImpl.d.ts +0 -7
  240. package/dist/nord/NordImpl.js +0 -6
  241. package/dist/nord/NordUser.d.ts +0 -77
  242. package/dist/nord/NordUser.js +0 -249
  243. package/docs/functions/createWebSocketSubscription.html +0 -12
  244. package/docs/interfaces/ERC20TokenInfo.html +0 -5
  245. package/docs/interfaces/OrderbookOrder.html +0 -6
  246. package/docs/interfaces/TradeInfo.html +0 -20
  247. package/docs/interfaces/TradesQueryParams.html +0 -10
  248. package/docs/variables/DEV_CONTRACT_ADDRESS.html +0 -1
  249. package/docs/variables/ERC20_ABI.html +0 -1
  250. package/docs/variables/EVM_DEV_URL.html +0 -1
  251. package/docs/variables/FAUCET_PRIVATE_ADDRESS.html +0 -1
  252. package/docs/variables/NORD_GETTERS_FACET_ABI.html +0 -1
  253. package/docs/variables/NORD_RAMP_FACET_ABI.html +0 -1
  254. package/src/abis/ERC20_ABI.ts +0 -310
  255. package/src/abis/NORD_GETTERS_FACET_ABI.ts +0 -192
  256. package/src/abis/NORD_RAMP_FACET_ABI.ts +0 -141
  257. package/src/abis/index.ts +0 -3
  258. package/src/nord/Nord.ts +0 -504
  259. package/src/nord/NordImpl.ts +0 -8
  260. package/src/nord/NordUser.ts +0 -469
@@ -0,0 +1,1101 @@
1
+ import * as anchor from "@coral-xyz/anchor";
2
+ import {
3
+ ASSOCIATED_TOKEN_PROGRAM_ID,
4
+ getAssociatedTokenAddress,
5
+ TOKEN_2022_PROGRAM_ID,
6
+ } from "@solana/spl-token";
7
+ import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
8
+ import Decimal from "decimal.js";
9
+ import * as nacl from "tweetnacl";
10
+ import { SolanaBridgeClient } from "../../bridge/client";
11
+ import { SPLTokenInfo } from "../../bridge/types";
12
+ import { keypairFromPrivateKey } from "../../bridge/utils";
13
+ import { FillMode, Order, Side } from "../../types";
14
+ import {
15
+ BigIntValue,
16
+ checkedFetch,
17
+ findMarket,
18
+ findToken,
19
+ optExpect,
20
+ toBN as utilsToBN,
21
+ } from "../../utils";
22
+ import {
23
+ cancelOrder,
24
+ createSession,
25
+ placeOrder,
26
+ revokeSession,
27
+ transfer,
28
+ withdraw,
29
+ } from "../api/actions";
30
+ import { NordError } from "../utils/NordError";
31
+ import { Nord } from "./Nord";
32
+
33
+ /**
34
+ * Parameters for creating a NordUser instance
35
+ */
36
+ export interface NordUserParams {
37
+ /** Nord client instance */
38
+ nord: Nord;
39
+
40
+ /** User's blockchain address */
41
+ address: string;
42
+
43
+ /** Function to sign messages with the user's wallet */
44
+ walletSignFn: (message: Uint8Array | string) => Promise<Uint8Array>;
45
+
46
+ /** Function to sign messages with the user's session key */
47
+ sessionSignFn: (message: Uint8Array) => Promise<Uint8Array>;
48
+
49
+ /** Function to sign transactions with the user's wallet (optional) */
50
+ transactionSignFn: (transaction: any) => Promise<any>;
51
+
52
+ /** Solana connection (optional) */
53
+ connection?: Connection;
54
+
55
+ /** Session ID (optional) */
56
+ sessionId?: bigint;
57
+
58
+ /** Session public key (required) */
59
+ sessionPubKey: Uint8Array;
60
+
61
+ /** Session public key (required) */
62
+ publicKey: PublicKey;
63
+ }
64
+
65
+ /**
66
+ * Parameters for placing an order
67
+ */
68
+ export interface PlaceOrderParams {
69
+ /** Market ID */
70
+ marketId: number;
71
+
72
+ /** Order side (bid or ask) */
73
+ side: Side;
74
+
75
+ /** Fill mode (limit, market, etc.) */
76
+ fillMode: FillMode;
77
+
78
+ /** Whether the order is reduce-only */
79
+ isReduceOnly: boolean;
80
+
81
+ /** Order size */
82
+ size?: Decimal.Value;
83
+
84
+ /** Order price */
85
+ price?: Decimal.Value;
86
+
87
+ /** Quote size (for market orders) */
88
+ quoteSize?: Decimal.Value;
89
+
90
+ /** Account ID to place the order from */
91
+ accountId?: number;
92
+ }
93
+
94
+ /**
95
+ * Parameters for transferring tokens between accounts
96
+ */
97
+ export interface TransferParams {
98
+ /** Recipient user */
99
+ to: NordUser;
100
+
101
+ /** Token ID to transfer */
102
+ tokenId: number;
103
+
104
+ /** Amount to transfer */
105
+ amount: Decimal.Value;
106
+
107
+ /** Source account ID */
108
+ fromAccountId: number;
109
+
110
+ /** Destination account ID */
111
+ toAccountId: number;
112
+ }
113
+
114
+ /**
115
+ * Parameters for creating a new account
116
+ */
117
+ export interface CreateAccountParams {
118
+ /** Token ID for initial funding */
119
+ tokenId: number;
120
+
121
+ /** Initial funding amount */
122
+ amount: Decimal.Value;
123
+ }
124
+
125
+ /**
126
+ * User class for interacting with the Nord protocol
127
+ */
128
+ export class NordUser {
129
+ /** Nord client instance */
130
+ public readonly nord: Nord;
131
+
132
+ /** User's blockchain address */
133
+ public readonly address: string;
134
+
135
+ /** Function to sign messages with the user's wallet */
136
+ public readonly walletSignFn: (
137
+ message: Uint8Array | string,
138
+ ) => Promise<Uint8Array>;
139
+
140
+ /** Function to sign messages with the user's session key */
141
+ public readonly sessionSignFn: (message: Uint8Array) => Promise<Uint8Array>;
142
+
143
+ /** Function to sign transactions with the user's wallet */
144
+ public readonly transactionSignFn: (transaction: any) => Promise<any>;
145
+
146
+ /** User balances by token symbol */
147
+ public balances: {
148
+ [key: string]: { accountId: number; balance: number; symbol: string }[];
149
+ } = {};
150
+
151
+ /** User orders by market symbol */
152
+ public orders: { [key: string]: Order[] } = {};
153
+
154
+ /** User positions by account ID */
155
+ public positions: {
156
+ [key: string]: {
157
+ marketId: number;
158
+ openOrders: number;
159
+ perp?: {
160
+ baseSize: number;
161
+ price: number;
162
+ updatedFundingRateIndex: number;
163
+ fundingPaymentPnl: number;
164
+ sizePricePnl: number;
165
+ isLong: boolean;
166
+ };
167
+ actionId: number;
168
+ }[];
169
+ } = {};
170
+
171
+ /** User margins by account ID */
172
+ public margins: {
173
+ [key: string]: {
174
+ omf: number;
175
+ mf: number;
176
+ imf: number;
177
+ cmf: number;
178
+ mmf: number;
179
+ pon: number;
180
+ pn: number;
181
+ bankruptcy: boolean;
182
+ };
183
+ } = {};
184
+
185
+ /** User's account IDs */
186
+ public accountIds?: number[];
187
+
188
+ /** Current session ID */
189
+ public sessionId?: bigint;
190
+
191
+ /** User's public key */
192
+ public publicKey: PublicKey;
193
+
194
+ /** Session public key */
195
+ public sessionPubKey: Uint8Array;
196
+
197
+ /** Last timestamp used */
198
+ public lastTs = 0;
199
+
200
+ /** Last nonce used */
201
+ public lastNonce = 0;
202
+
203
+ // Solana-specific properties
204
+ /** Solana bridge client */
205
+ public bridgeClient?: SolanaBridgeClient;
206
+
207
+ /** Solana connection */
208
+ public connection: Connection;
209
+
210
+ /** SPL token information */
211
+ public splTokenInfos: SPLTokenInfo[] = [];
212
+
213
+ /**
214
+ * Create a new NordUser instance
215
+ *
216
+ * @param params - Parameters for creating a NordUser
217
+ * @throws {NordError} If required parameters are missing
218
+ */
219
+ constructor(params: NordUserParams) {
220
+ if (!params.nord) {
221
+ throw new NordError("Nord instance is required");
222
+ }
223
+ if (!params.address) {
224
+ throw new NordError("Address is required");
225
+ }
226
+ if (!params.walletSignFn) {
227
+ throw new NordError("Wallet sign function is required");
228
+ }
229
+ if (!params.sessionSignFn) {
230
+ throw new NordError("Session sign function is required");
231
+ }
232
+ if (!params.sessionPubKey) {
233
+ throw new NordError("Session public key is required");
234
+ }
235
+
236
+ this.nord = params.nord;
237
+ this.address = params.address;
238
+ this.walletSignFn = params.walletSignFn;
239
+ this.sessionSignFn = params.sessionSignFn;
240
+ this.transactionSignFn = params.transactionSignFn;
241
+ this.sessionPubKey = params.sessionPubKey;
242
+ this.publicKey = params.publicKey;
243
+ this.connection =
244
+ params.connection ||
245
+ new Connection(params.nord.solanaUrl, {
246
+ commitment: "confirmed",
247
+ });
248
+
249
+ // Set sessionId if provided
250
+ if (params.sessionId !== undefined) {
251
+ this.sessionId = params.sessionId;
252
+ }
253
+
254
+ // Initialize bridge client if needed
255
+ try {
256
+ this.initBridgeClient();
257
+ } catch (error) {
258
+ console.warn("Failed to initialize bridge client:", error);
259
+ }
260
+
261
+ // Convert tokens from info endpoint to SPLTokenInfo
262
+ if (this.nord.tokens && this.nord.tokens.length > 0) {
263
+ this.splTokenInfos = this.nord.tokens.map((token) => ({
264
+ mint: token.mintAddr, // Use mintAddr as mint
265
+ precision: token.decimals,
266
+ tokenId: token.tokenId,
267
+ name: token.symbol,
268
+ }));
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Create a clone of this NordUser instance
274
+ *
275
+ * @returns A new NordUser instance with the same properties
276
+ */
277
+ clone(): NordUser {
278
+ const cloned = new NordUser({
279
+ nord: this.nord,
280
+ address: this.address,
281
+ walletSignFn: this.walletSignFn,
282
+ sessionSignFn: this.sessionSignFn,
283
+ transactionSignFn: this.transactionSignFn,
284
+ connection: this.connection,
285
+ sessionPubKey: this.sessionPubKey,
286
+ publicKey: this.publicKey,
287
+ });
288
+
289
+ // Copy other properties
290
+ cloned.balances = { ...this.balances };
291
+ cloned.orders = { ...this.orders };
292
+ cloned.positions = { ...this.positions };
293
+ cloned.margins = { ...this.margins };
294
+ cloned.accountIds = this.accountIds ? [...this.accountIds] : undefined;
295
+ cloned.sessionId = this.sessionId;
296
+ cloned.publicKey = this.publicKey;
297
+ cloned.lastTs = this.lastTs;
298
+ cloned.lastNonce = this.lastNonce;
299
+ cloned.splTokenInfos = [...this.splTokenInfos];
300
+
301
+ return cloned;
302
+ }
303
+
304
+ /**
305
+ * Initialize the Solana bridge client
306
+ *
307
+ * @private
308
+ * @throws {NordError} If required parameters are missing
309
+ */
310
+ private initBridgeClient(): void {
311
+ if (
312
+ !this.getSolanaPublicKey() ||
313
+ !this.connection ||
314
+ !this.nord.solanaProgramId
315
+ ) {
316
+ throw new NordError(
317
+ "Solana public key, connection, and program ID are required to initialize bridge client",
318
+ );
319
+ }
320
+
321
+ // Create an Anchor wallet that uses walletSignFn for signing
322
+ const wallet: anchor.Wallet = {
323
+ publicKey: this.getSolanaPublicKey(),
324
+ signTransaction: async (tx: any) => {
325
+ return await this.transactionSignFn(tx);
326
+ },
327
+ signAllTransactions: async (txs: any[]) => {
328
+ return Promise.all(
329
+ txs.map(async (tx) => {
330
+ return await this.transactionSignFn(tx);
331
+ }),
332
+ );
333
+ },
334
+ // Create a keypair-like object with just the public key
335
+ payer: {
336
+ publicKey: this.getSolanaPublicKey(),
337
+ secretKey: new Uint8Array(64), // Dummy secret key to satisfy the type
338
+ } as Keypair,
339
+ };
340
+
341
+ // Initialize the bridge client
342
+ this.bridgeClient = new SolanaBridgeClient(
343
+ {
344
+ rpcUrl: this.connection.rpcEndpoint,
345
+ programId: this.nord.solanaProgramId,
346
+ commitment: "confirmed",
347
+ tokenInfos: this.splTokenInfos,
348
+ },
349
+ wallet,
350
+ );
351
+ }
352
+
353
+ /**
354
+ * Create a NordUser from a private key
355
+ *
356
+ * @param nord - Nord instance
357
+ * @param privateKey - Private key as string or Uint8Array
358
+ * @param connection - Solana connection (optional)
359
+ * @returns NordUser instance
360
+ * @throws {NordError} If the private key is invalid
361
+ */
362
+ static fromPrivateKey(
363
+ nord: Nord,
364
+ privateKey: string | Uint8Array,
365
+ connection?: Connection,
366
+ ): NordUser {
367
+ try {
368
+ const keypair = keypairFromPrivateKey(privateKey);
369
+ const publicKey = keypair.publicKey;
370
+
371
+ // Create a signing function that uses the keypair but doesn't expose it
372
+ const walletSignFn = async (
373
+ message: Uint8Array | string,
374
+ ): Promise<Uint8Array> => {
375
+ const messageBuffer =
376
+ typeof message === "string"
377
+ ? Buffer.from(message)
378
+ : Buffer.from(message);
379
+
380
+ // Use the keypair to sign the message
381
+ const signature = nacl.sign.detached(messageBuffer, keypair.secretKey);
382
+ return signature;
383
+ };
384
+
385
+ const sessionSignFn = async (
386
+ message: Uint8Array,
387
+ ): Promise<Uint8Array> => {
388
+ // Use the keypair to sign the message
389
+ return nacl.sign.detached(message, keypair.secretKey);
390
+ };
391
+
392
+ // Create a transaction signing function
393
+ const transactionSignFn = async (transaction: any): Promise<any> => {
394
+ // This is a basic implementation - actual implementation would depend on the transaction type
395
+ if (transaction.serializeMessage) {
396
+ // Solana transaction
397
+ transaction.sign([keypair]);
398
+ return transaction;
399
+ }
400
+
401
+ // For other transaction types, would need specific implementation
402
+ throw new NordError("Unsupported transaction type for signing");
403
+ };
404
+
405
+ return new NordUser({
406
+ nord,
407
+ address: publicKey.toBase58(),
408
+ walletSignFn,
409
+ sessionSignFn,
410
+ transactionSignFn,
411
+ connection,
412
+ publicKey,
413
+ sessionPubKey: publicKey.toBytes(), // Use the public key derived from the private key as the session public key
414
+ });
415
+ } catch (error) {
416
+ throw new NordError("Failed to create NordUser from private key", {
417
+ cause: error,
418
+ });
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Get the associated token account for a token mint
424
+ *
425
+ * @param mint - Token mint address
426
+ * @returns Associated token account address
427
+ * @throws {NordError} If required parameters are missing or operation fails
428
+ */
429
+ async getAssociatedTokenAccount(mint: PublicKey): Promise<PublicKey> {
430
+ if (!this.getSolanaPublicKey()) {
431
+ throw new NordError(
432
+ "Solana public key is required to get associated token account",
433
+ );
434
+ }
435
+
436
+ try {
437
+ return await getAssociatedTokenAddress(
438
+ mint,
439
+ this.getSolanaPublicKey(),
440
+ false,
441
+ TOKEN_2022_PROGRAM_ID,
442
+ ASSOCIATED_TOKEN_PROGRAM_ID,
443
+ );
444
+ } catch (error) {
445
+ throw new NordError("Failed to get associated token account", {
446
+ cause: error,
447
+ });
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Deposit SPL tokens to the bridge
453
+ *
454
+ * @param amount - Amount to deposit
455
+ * @param tokenId - Token ID
456
+ * @returns Transaction signature
457
+ * @throws {NordError} If required parameters are missing or operation fails
458
+ */
459
+ async depositSpl(amount: number, tokenId: number): Promise<string> {
460
+ if (!this.bridgeClient || !this.getSolanaPublicKey() || !this.connection) {
461
+ throw new NordError(
462
+ "Bridge client, Solana public key, and connection are required for deposit",
463
+ );
464
+ }
465
+
466
+ try {
467
+ // Find the token info
468
+ const tokenInfo = this.splTokenInfos.find((t) => t.tokenId === tokenId);
469
+ if (!tokenInfo) {
470
+ throw new NordError(`Token with ID ${tokenId} not found`);
471
+ }
472
+
473
+ const mint = new PublicKey(tokenInfo.mint);
474
+ // Get the user's token account
475
+ const fromAccount = await this.getAssociatedTokenAccount(mint);
476
+
477
+ // Get the bridge's token account
478
+ const [authority] = await this.bridgeClient.findAuthorityPda();
479
+ const toAccount = await getAssociatedTokenAddress(
480
+ mint,
481
+ authority,
482
+ true,
483
+ TOKEN_2022_PROGRAM_ID,
484
+ );
485
+
486
+ // Convert amount to BN with proper decimals
487
+ const amountBN = utilsToBN(amount, tokenInfo.precision);
488
+
489
+ // Deposit tokens
490
+ return await this.bridgeClient.depositSpl({
491
+ amount: amountBN,
492
+ mint,
493
+ fromAccount,
494
+ toAccount,
495
+ });
496
+ } catch (error) {
497
+ throw new NordError(
498
+ `Failed to deposit ${amount} of token ID ${tokenId}`,
499
+ { cause: error },
500
+ );
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Withdraw SPL tokens from the bridge
506
+ *
507
+ * @param claim - Claim data
508
+ * @returns Transaction signature
509
+ * @throws {NordError} If required parameters are missing or operation fails
510
+ */
511
+ async withdrawSpl(claim: any): Promise<string> {
512
+ if (!this.bridgeClient || !this.getSolanaPublicKey() || !this.connection) {
513
+ throw new NordError(
514
+ "Bridge client, Solana public key, and connection are required for withdrawal",
515
+ );
516
+ }
517
+
518
+ try {
519
+ // Find the token info
520
+ const tokenInfo = this.splTokenInfos.find(
521
+ (t) => t.tokenId === claim.tokenId,
522
+ );
523
+ if (!tokenInfo) {
524
+ throw new NordError(`Token with ID ${claim.tokenId} not found`);
525
+ }
526
+
527
+ const mint = new PublicKey(tokenInfo.mint);
528
+
529
+ // Get the user's token account
530
+ const toAccount = await this.getAssociatedTokenAccount(mint);
531
+
532
+ // Get the bridge's token account
533
+ const [authority] = await this.bridgeClient.findAuthorityPda();
534
+ const fromAccount = await getAssociatedTokenAddress(
535
+ mint,
536
+ authority,
537
+ true,
538
+ TOKEN_2022_PROGRAM_ID,
539
+ );
540
+
541
+ // Create a transaction signer that uses walletSignFn
542
+ const transactionSigner = {
543
+ publicKey: this.getSolanaPublicKey(),
544
+ secretKey: new Uint8Array(64), // Dummy secret key, not actually used
545
+ sign: async (tx: Transaction) => {
546
+ const message = tx.serializeMessage();
547
+ await this.walletSignFn(message);
548
+ return tx;
549
+ },
550
+ };
551
+
552
+ // Withdraw tokens
553
+ return await this.bridgeClient.withdraw(
554
+ {
555
+ claim,
556
+ fromAccount,
557
+ toAccount,
558
+ },
559
+ transactionSigner as unknown as Keypair, // Type cast to satisfy the API
560
+ );
561
+ } catch (error) {
562
+ throw new NordError("Failed to withdraw SPL tokens", { cause: error });
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Get a new nonce for actions
568
+ *
569
+ * @returns Nonce as number
570
+ */
571
+ getNonce(): number {
572
+ return ++this.lastNonce;
573
+ }
574
+
575
+ /**
576
+ * Update account IDs for this user
577
+ *
578
+ * @throws {NordError} If the operation fails
579
+ */
580
+ async updateAccountId(): Promise<void> {
581
+ try {
582
+ if (!this.publicKey) {
583
+ throw new NordError("Public key is required to update account ID");
584
+ }
585
+
586
+ const resp = await this.nord.getUserAccountIds({
587
+ pubkey: this.publicKey.toBase58(),
588
+ });
589
+
590
+ this.accountIds = resp.accountIds;
591
+ } catch (error) {
592
+ throw new NordError("Failed to update account ID", { cause: error });
593
+ }
594
+ }
595
+
596
+ /**
597
+ * Fetch user information including balances and orders
598
+ *
599
+ * @throws {NordError} If the operation fails
600
+ */
601
+ async fetchInfo(): Promise<void> {
602
+ interface FetchOrder {
603
+ orderId: number;
604
+ size: number;
605
+ price: number;
606
+ marketId: number;
607
+ side: "ask" | "bid";
608
+ originalOrderSize?: number;
609
+ clientOrderId?: number | null;
610
+ }
611
+
612
+ interface Balance {
613
+ tokenId: number;
614
+ token: string;
615
+ amount: number;
616
+ }
617
+
618
+ interface Position {
619
+ marketId: number;
620
+ openOrders: number;
621
+ perp?: {
622
+ baseSize: number;
623
+ price: number;
624
+ updatedFundingRateIndex: number;
625
+ fundingPaymentPnl: number;
626
+ sizePricePnl: number;
627
+ isLong: boolean;
628
+ };
629
+ actionId: number;
630
+ }
631
+
632
+ interface Margins {
633
+ omf: number;
634
+ mf: number;
635
+ imf: number;
636
+ cmf: number;
637
+ mmf: number;
638
+ pon: number;
639
+ pn: number;
640
+ bankruptcy: boolean;
641
+ }
642
+
643
+ interface Account {
644
+ updateId: number;
645
+ orders: FetchOrder[];
646
+ positions: Position[];
647
+ balances: Balance[];
648
+ margins: Margins;
649
+ actionNonce?: number | null;
650
+ accountId: number;
651
+ }
652
+
653
+ if (this.accountIds !== undefined) {
654
+ const accountsData = await Promise.all(
655
+ this.accountIds.map(async (accountId) => {
656
+ const response = await checkedFetch(
657
+ `${this.nord.webServerUrl}/account?account_id=${accountId}`,
658
+ );
659
+ const accountData = (await response.json()) as Account;
660
+ // Ensure we have the correct accountId
661
+ return {
662
+ ...accountData,
663
+ accountId,
664
+ };
665
+ }),
666
+ );
667
+
668
+ for (const accountData of accountsData) {
669
+ // Process balances
670
+ this.balances[accountData.accountId] = [];
671
+ for (const balance of accountData.balances) {
672
+ this.balances[accountData.accountId].push({
673
+ accountId: accountData.accountId,
674
+ balance: balance.amount,
675
+ symbol: balance.token,
676
+ });
677
+ }
678
+
679
+ // Process orders
680
+ this.orders[accountData.accountId] = accountData.orders.map(
681
+ (order: {
682
+ orderId: number;
683
+ side: string;
684
+ size: number;
685
+ price: number;
686
+ marketId: number;
687
+ }) => {
688
+ return {
689
+ orderId: order.orderId,
690
+ isLong: order.side === "bid",
691
+ size: order.size,
692
+ price: order.price,
693
+ marketId: order.marketId,
694
+ };
695
+ },
696
+ );
697
+
698
+ // Process positions
699
+ this.positions[accountData.accountId] = accountData.positions;
700
+
701
+ // Process margins
702
+ this.margins[accountData.accountId] = accountData.margins;
703
+ }
704
+ }
705
+ }
706
+
707
+ /**
708
+ * Refresh the user's session
709
+ *
710
+ * @throws {NordError} If the operation fails
711
+ */
712
+ async refreshSession(): Promise<void> {
713
+ console.log(this.publicKey);
714
+ this.sessionId = await createSession(
715
+ this.nord.webServerUrl,
716
+ this.walletSignFn,
717
+ await this.nord.getTimestamp(),
718
+ this.getNonce(),
719
+ {
720
+ userPubkey: optExpect(this.publicKey.toBytes(), "No user's public key"),
721
+ sessionPubkey: this.sessionPubKey,
722
+ },
723
+ );
724
+ }
725
+ /**
726
+ * Revoke a session
727
+ *
728
+ * @param sessionId - Session ID to revoke
729
+ * @throws {NordError} If the operation fails
730
+ */
731
+ async revokeSession(sessionId: BigIntValue): Promise<void> {
732
+ try {
733
+ await revokeSession(
734
+ this.nord.webServerUrl,
735
+ this.walletSignFn,
736
+ await this.nord.getTimestamp(),
737
+ this.getNonce(),
738
+ {
739
+ sessionId,
740
+ },
741
+ );
742
+ } catch (error) {
743
+ throw new NordError(`Failed to revoke session ${sessionId}`, {
744
+ cause: error,
745
+ });
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Checks if the session is valid
751
+ * @private
752
+ * @throws {NordError} If the session is not valid
753
+ */
754
+ private checkSessionValidity(): void {
755
+ if (this.sessionId === undefined || this.sessionId === BigInt(0)) {
756
+ throw new NordError(
757
+ "Invalid or empty session ID. Please create or refresh your session.",
758
+ );
759
+ }
760
+ }
761
+
762
+ /**
763
+ * Withdraw tokens from the exchange
764
+ *
765
+ * @param tokenId - Token ID to withdraw
766
+ * @param amount - Amount to withdraw
767
+ * @throws {NordError} If the operation fails
768
+ */
769
+ async withdraw(tokenId: number, amount: number): Promise<void> {
770
+ try {
771
+ this.checkSessionValidity();
772
+ await withdraw(
773
+ this.nord.webServerUrl,
774
+ this.sessionSignFn,
775
+ await this.nord.getTimestamp(),
776
+ this.getNonce(),
777
+ {
778
+ sizeDecimals: findToken(this.nord.tokens, tokenId).decimals,
779
+ sessionId: optExpect(this.sessionId, "No session"),
780
+ tokenId: tokenId,
781
+ amount,
782
+ },
783
+ );
784
+ } catch (error) {
785
+ throw new NordError(
786
+ `Failed to withdraw ${amount} of token ID ${tokenId}`,
787
+ { cause: error },
788
+ );
789
+ }
790
+ }
791
+
792
+ /**
793
+ * Place an order on the exchange
794
+ *
795
+ * @param params - Order parameters
796
+ * @returns Order ID if successful
797
+ * @throws {NordError} If the operation fails
798
+ */
799
+ async placeOrder(params: PlaceOrderParams): Promise<bigint | undefined> {
800
+ try {
801
+ this.checkSessionValidity();
802
+ const market = findMarket(this.nord.markets, params.marketId);
803
+ if (!market) {
804
+ throw new NordError(`Market with ID ${params.marketId} not found`);
805
+ }
806
+
807
+ return placeOrder(
808
+ this.nord.webServerUrl,
809
+ this.sessionSignFn,
810
+ await this.nord.getTimestamp(),
811
+ this.getNonce(),
812
+ {
813
+ sessionId: optExpect(this.sessionId, "No session"),
814
+ senderId: params.accountId,
815
+ sizeDecimals: market.sizeDecimals,
816
+ priceDecimals: market.priceDecimals,
817
+ marketId: params.marketId,
818
+ side: params.side,
819
+ fillMode: params.fillMode,
820
+ isReduceOnly: params.isReduceOnly,
821
+ size: params.size,
822
+ price: params.price,
823
+ quoteSize: params.quoteSize,
824
+ },
825
+ );
826
+ } catch (error) {
827
+ throw new NordError("Failed to place order", { cause: error });
828
+ }
829
+ }
830
+
831
+ /**
832
+ * Cancel an order
833
+ *
834
+ * @param orderId - Order ID to cancel
835
+ * @param accountId - Account ID that placed the order
836
+ * @returns Action ID if successful
837
+ * @throws {NordError} If the operation fails
838
+ */
839
+ async cancelOrder(
840
+ orderId: BigIntValue,
841
+ providedAccountId?: number,
842
+ ): Promise<bigint> {
843
+ const accountId =
844
+ providedAccountId != null ? providedAccountId : this.accountIds?.[0];
845
+ try {
846
+ this.checkSessionValidity();
847
+ return cancelOrder(
848
+ this.nord.webServerUrl,
849
+ this.sessionSignFn,
850
+ await this.nord.getTimestamp(),
851
+ this.getNonce(),
852
+ {
853
+ sessionId: optExpect(this.sessionId, "No session"),
854
+ senderId: accountId,
855
+ orderId,
856
+ },
857
+ );
858
+ } catch (error) {
859
+ throw new NordError(`Failed to cancel order ${orderId}`, {
860
+ cause: error,
861
+ });
862
+ }
863
+ }
864
+
865
+ /**
866
+ * Transfer tokens to another account
867
+ *
868
+ * @param params - Transfer parameters
869
+ * @throws {NordError} If the operation fails
870
+ */
871
+ async transferToAccount(params: TransferParams): Promise<void> {
872
+ try {
873
+ this.checkSessionValidity();
874
+ const token = findToken(this.nord.tokens, params.tokenId);
875
+
876
+ await transfer(
877
+ this.nord.webServerUrl,
878
+ this.sessionSignFn,
879
+ await this.nord.getTimestamp(),
880
+ this.getNonce(),
881
+ {
882
+ sessionId: optExpect(this.sessionId, "No session"),
883
+ fromAccountId: optExpect(params.fromAccountId, "No source account"),
884
+ toAccountId: optExpect(params.toAccountId, "No target account"),
885
+ tokenId: params.tokenId,
886
+ tokenDecimals: token.decimals,
887
+ amount: params.amount,
888
+ },
889
+ );
890
+ } catch (error) {
891
+ throw new NordError("Failed to transfer tokens", { cause: error });
892
+ }
893
+ }
894
+
895
+ /**
896
+ * Create a new account
897
+ *
898
+ * @param params - Account creation parameters
899
+ * @returns New NordUser instance
900
+ * @throws {NordError} If the operation fails
901
+ */
902
+ async createAccount(params: CreateAccountParams): Promise<NordUser> {
903
+ try {
904
+ this.checkSessionValidity();
905
+ // Create a new keypair for the account
906
+ const keypair = Keypair.generate();
907
+
908
+ // Create a new NordUser
909
+ const newUser = NordUser.fromPrivateKey(
910
+ this.nord,
911
+ keypair.secretKey,
912
+ this.connection,
913
+ );
914
+
915
+ // Transfer initial funds
916
+ await this.transferToAccount({
917
+ to: newUser,
918
+ tokenId: params.tokenId,
919
+ amount: params.amount,
920
+ fromAccountId: optExpect(this.accountIds?.[0], "No account ID"),
921
+ toAccountId: optExpect(
922
+ newUser.accountIds?.[0],
923
+ "No account ID for new user",
924
+ ),
925
+ });
926
+
927
+ return newUser;
928
+ } catch (error) {
929
+ throw new NordError("Failed to create account", { cause: error });
930
+ }
931
+ }
932
+
933
+ /**
934
+ * Helper function to retry a promise with exponential backoff
935
+ *
936
+ * @param fn - Function to retry
937
+ * @param maxRetries - Maximum number of retries
938
+ * @param initialDelay - Initial delay in milliseconds
939
+ * @returns Promise result
940
+ */
941
+ private async retryWithBackoff<T>(
942
+ fn: () => Promise<T>,
943
+ maxRetries: number = 3,
944
+ initialDelay: number = 500,
945
+ ): Promise<T> {
946
+ let retries = 0;
947
+ let delay = initialDelay;
948
+
949
+ while (true) {
950
+ try {
951
+ return await fn();
952
+ } catch (error) {
953
+ if (retries >= maxRetries) {
954
+ throw error;
955
+ }
956
+
957
+ // Check if error is rate limiting related
958
+ const isRateLimitError =
959
+ error instanceof Error &&
960
+ (error.message.includes("rate limit") ||
961
+ error.message.includes("429") ||
962
+ error.message.includes("too many requests"));
963
+
964
+ if (!isRateLimitError) {
965
+ throw error;
966
+ }
967
+
968
+ retries++;
969
+ await new Promise((resolve) => setTimeout(resolve, delay));
970
+ delay *= 2; // Exponential backoff
971
+ }
972
+ }
973
+ }
974
+
975
+ /**
976
+ * Get user's token balances on Solana chain using mintAddr
977
+ *
978
+ * @param options - Optional parameters
979
+ * @param options.includeZeroBalances - Whether to include tokens with zero balance (default: true)
980
+ * @param options.includeTokenAccounts - Whether to include token account addresses in the result (default: false)
981
+ * @param options.maxConcurrent - Maximum number of concurrent requests (default: 5)
982
+ * @param options.maxRetries - Maximum number of retries for rate-limited requests (default: 3)
983
+ * @returns Object with token balances and optional token account addresses
984
+ * @throws {NordError} If required parameters are missing or operation fails
985
+ */
986
+ async getSolanaBalances(
987
+ options: {
988
+ includeZeroBalances?: boolean;
989
+ includeTokenAccounts?: boolean;
990
+ maxConcurrent?: number;
991
+ maxRetries?: number;
992
+ } = {},
993
+ ): Promise<{
994
+ balances: { [symbol: string]: number };
995
+ tokenAccounts?: { [symbol: string]: string };
996
+ }> {
997
+ const {
998
+ includeZeroBalances = true,
999
+ includeTokenAccounts = false,
1000
+ maxConcurrent = 5,
1001
+ maxRetries = 3,
1002
+ } = options;
1003
+
1004
+ if (!this.connection || !this.getSolanaPublicKey()) {
1005
+ throw new NordError(
1006
+ "Connection and Solana public key are required to get Solana balances",
1007
+ );
1008
+ }
1009
+
1010
+ const balances: { [symbol: string]: number } = {};
1011
+ const tokenAccounts: { [symbol: string]: string } = {};
1012
+
1013
+ try {
1014
+ // Get SOL balance (native token)
1015
+ const solBalance = await this.retryWithBackoff(
1016
+ () => this.connection!.getBalance(this.getSolanaPublicKey()),
1017
+ maxRetries,
1018
+ );
1019
+ balances["SOL"] = solBalance / 1e9; // Convert lamports to SOL
1020
+ if (includeTokenAccounts) {
1021
+ tokenAccounts["SOL"] = this.getSolanaPublicKey().toString();
1022
+ }
1023
+
1024
+ // Get SPL token balances using mintAddr from Nord tokens
1025
+ if (this.nord.tokens && this.nord.tokens.length > 0) {
1026
+ const tokens = this.nord.tokens.filter((token) => !!token.mintAddr);
1027
+
1028
+ // Process tokens in batches to avoid rate limiting
1029
+ for (let i = 0; i < tokens.length; i += maxConcurrent) {
1030
+ const batch = tokens.slice(i, i + maxConcurrent);
1031
+
1032
+ // Process batch in parallel
1033
+ const batchPromises = batch.map(async (token) => {
1034
+ try {
1035
+ const mint = new PublicKey(token.mintAddr);
1036
+ const associatedTokenAddress = await this.retryWithBackoff(
1037
+ () =>
1038
+ getAssociatedTokenAddress(mint, this.getSolanaPublicKey()),
1039
+ maxRetries,
1040
+ );
1041
+
1042
+ if (includeTokenAccounts) {
1043
+ tokenAccounts[token.symbol] = associatedTokenAddress.toString();
1044
+ }
1045
+
1046
+ try {
1047
+ const tokenBalance = await this.retryWithBackoff(
1048
+ () =>
1049
+ this.connection!.getTokenAccountBalance(
1050
+ associatedTokenAddress,
1051
+ ),
1052
+ maxRetries,
1053
+ );
1054
+ const balance = Number(tokenBalance.value.uiAmount);
1055
+
1056
+ if (balance > 0 || includeZeroBalances) {
1057
+ balances[token.symbol] = balance;
1058
+ }
1059
+ } catch {
1060
+ // Token account might not exist yet, set balance to 0
1061
+ if (includeZeroBalances) {
1062
+ balances[token.symbol] = 0;
1063
+ }
1064
+ }
1065
+ } catch (error) {
1066
+ console.error(
1067
+ `Error getting balance for token ${token.symbol}:`,
1068
+ error,
1069
+ );
1070
+ if (includeZeroBalances) {
1071
+ balances[token.symbol] = 0;
1072
+ }
1073
+ }
1074
+ });
1075
+
1076
+ // Wait for current batch to complete before processing next batch
1077
+ await Promise.all(batchPromises);
1078
+ }
1079
+ }
1080
+
1081
+ return includeTokenAccounts ? { balances, tokenAccounts } : { balances };
1082
+ } catch (error) {
1083
+ throw new NordError("Failed to get Solana token balances", {
1084
+ cause: error,
1085
+ });
1086
+ }
1087
+ }
1088
+
1089
+ /**
1090
+ * Get the Solana public key derived from the address
1091
+ *
1092
+ * @returns The Solana public key
1093
+ */
1094
+ getSolanaPublicKey(): PublicKey {
1095
+ try {
1096
+ return new PublicKey(this.address);
1097
+ } catch (error) {
1098
+ throw new NordError("Invalid Solana address", { cause: error });
1099
+ }
1100
+ }
1101
+ }