@n1xyz/nord-ts 0.0.1 → 0.0.4

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