@n1xyz/nord-ts 0.0.18-8121ed05.0 → 0.0.19
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.
- package/.claude/settings.local.json +11 -0
- package/.local/qa.ts +77 -0
- package/.local/test-atomic.ts +112 -0
- package/check.sh +4 -0
- package/default.nix +47 -0
- package/package.json +20 -27
- package/src/index.ts +0 -16
- package/src/nord/api/actions.ts +131 -9
- package/src/nord/api/core.ts +0 -71
- package/src/nord/client/Nord.ts +142 -76
- package/src/nord/client/NordUser.ts +171 -50
- package/src/nord/index.ts +0 -2
- package/src/nord/models/Subscriber.ts +2 -2
- package/src/types.ts +55 -216
- package/src/utils.ts +6 -42
- package/src/websocket/NordWebSocketClient.ts +23 -15
- package/src/websocket/index.ts +1 -1
- package/tests/utils.spec.ts +1 -34
- package/dist/bridge/client.d.ts +0 -151
- package/dist/bridge/client.js +0 -434
- package/dist/bridge/const.d.ts +0 -23
- package/dist/bridge/const.js +0 -47
- package/dist/bridge/index.d.ts +0 -4
- package/dist/bridge/index.js +0 -23
- package/dist/bridge/types.d.ts +0 -120
- package/dist/bridge/types.js +0 -18
- package/dist/bridge/utils.d.ts +0 -64
- package/dist/bridge/utils.js +0 -131
- package/dist/const.d.ts +0 -8
- package/dist/const.js +0 -30
- package/dist/gen/common.d.ts +0 -68
- package/dist/gen/common.js +0 -215
- package/dist/gen/nord.d.ts +0 -853
- package/dist/gen/nord.js +0 -6368
- package/dist/idl/bridge.d.ts +0 -569
- package/dist/idl/bridge.js +0 -8
- package/dist/idl/bridge.json +0 -1506
- package/dist/idl/index.d.ts +0 -607
- package/dist/idl/index.js +0 -8
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -30
- package/dist/nord/api/actions.d.ts +0 -106
- package/dist/nord/api/actions.js +0 -256
- package/dist/nord/api/core.d.ts +0 -49
- package/dist/nord/api/core.js +0 -164
- package/dist/nord/api/market.d.ts +0 -36
- package/dist/nord/api/market.js +0 -96
- package/dist/nord/api/metrics.d.ts +0 -67
- package/dist/nord/api/metrics.js +0 -229
- package/dist/nord/api/queries.d.ts +0 -46
- package/dist/nord/api/queries.js +0 -109
- package/dist/nord/client/Nord.d.ts +0 -284
- package/dist/nord/client/Nord.js +0 -491
- package/dist/nord/client/NordUser.d.ts +0 -287
- package/dist/nord/client/NordUser.js +0 -595
- package/dist/nord/index.d.ts +0 -9
- package/dist/nord/index.js +0 -33
- package/dist/nord/models/Subscriber.d.ts +0 -37
- package/dist/nord/models/Subscriber.js +0 -25
- package/dist/nord/utils/NordError.d.ts +0 -35
- package/dist/nord/utils/NordError.js +0 -49
- package/dist/types.d.ts +0 -407
- package/dist/types.js +0 -103
- package/dist/utils.d.ts +0 -116
- package/dist/utils.js +0 -271
- package/dist/websocket/NordWebSocketClient.d.ts +0 -68
- package/dist/websocket/NordWebSocketClient.js +0 -338
- package/dist/websocket/events.d.ts +0 -19
- package/dist/websocket/events.js +0 -2
- package/dist/websocket/index.d.ts +0 -2
- package/dist/websocket/index.js +0 -5
- package/jest.config.ts +0 -9
- package/nodemon.json +0 -4
- package/protoc-generate.sh +0 -23
- package/src/idl/bridge.json +0 -1506
- package/src/nord/api/market.ts +0 -122
- package/src/nord/api/queries.ts +0 -135
package/src/nord/client/Nord.ts
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
|
+
import { PublicKey, Connection } from "@solana/web3.js";
|
|
3
|
+
import createClient, { Client, FetchOptions } from "openapi-fetch";
|
|
2
4
|
import {
|
|
5
|
+
Info,
|
|
3
6
|
Account,
|
|
4
|
-
ActionQuery,
|
|
5
7
|
ActionResponse,
|
|
6
|
-
ActionsResponse,
|
|
7
8
|
AggregateMetrics,
|
|
8
|
-
Info,
|
|
9
9
|
Market,
|
|
10
|
-
MarketsStatsResponse,
|
|
11
10
|
NordConfig,
|
|
12
11
|
OrderbookQuery,
|
|
13
12
|
OrderbookResponse,
|
|
14
13
|
PeakTpsPeriodUnit,
|
|
15
|
-
RollmanActionResponse,
|
|
16
|
-
RollmanActionsResponse,
|
|
17
14
|
SubscriptionPattern,
|
|
18
15
|
Token,
|
|
19
|
-
TradesQuery,
|
|
20
16
|
TradesResponse,
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
User,
|
|
18
|
+
MarketStats,
|
|
23
19
|
} from "../../types";
|
|
24
20
|
import { ProtonClient } from "@n1xyz/proton";
|
|
21
|
+
import * as proto from "../../gen/nord";
|
|
22
|
+
// import { base64 } from "@scure/base";
|
|
25
23
|
import { NordWebSocketClient } from "../../websocket/index";
|
|
26
24
|
import * as core from "../api/core";
|
|
27
|
-
import * as market from "../api/market";
|
|
28
25
|
import * as metrics from "../api/metrics";
|
|
29
|
-
import * as
|
|
26
|
+
import * as utils from "../../utils";
|
|
30
27
|
import { OrderbookSubscription, TradeSubscription } from "../models/Subscriber";
|
|
31
28
|
import { NordError } from "../utils/NordError";
|
|
32
|
-
import {
|
|
29
|
+
import type { paths } from "../../gen/openapi.ts";
|
|
33
30
|
|
|
34
31
|
/**
|
|
35
32
|
* User subscription interface
|
|
@@ -72,9 +69,12 @@ export class Nord {
|
|
|
72
69
|
/** Map of symbol to market_id */
|
|
73
70
|
private symbolToMarketId: Map<string, number> = new Map();
|
|
74
71
|
|
|
75
|
-
/** Proton client for bridge and
|
|
72
|
+
/** Proton client for bridge and hansel operations */
|
|
76
73
|
public protonClient: ProtonClient;
|
|
77
74
|
|
|
75
|
+
/** HTTP client for Nord operations */
|
|
76
|
+
private httpClient: Client<paths>;
|
|
77
|
+
|
|
78
78
|
/**
|
|
79
79
|
* Create a new Nord client
|
|
80
80
|
*
|
|
@@ -99,6 +99,7 @@ export class Nord {
|
|
|
99
99
|
this.bridgeVk = bridgeVk;
|
|
100
100
|
this.solanaUrl = solanaUrl;
|
|
101
101
|
this.protonClient = protonClient;
|
|
102
|
+
this.httpClient = createClient<paths>({ baseUrl: webServerUrl });
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/**
|
|
@@ -162,6 +163,22 @@ export class Nord {
|
|
|
162
163
|
return core.initWebSocketClient(this.webServerUrl, subscriptions);
|
|
163
164
|
}
|
|
164
165
|
|
|
166
|
+
private async GET<P extends keyof paths & string>(
|
|
167
|
+
path: P,
|
|
168
|
+
options: FetchOptions<paths[P]["get"]>,
|
|
169
|
+
) {
|
|
170
|
+
const r = await this.httpClient.GET(path, options);
|
|
171
|
+
if (r.error) {
|
|
172
|
+
throw new NordError(`failed to GET ${path}`, { cause: r.error });
|
|
173
|
+
}
|
|
174
|
+
if (r.data === undefined) {
|
|
175
|
+
// this should never happen, but the type checker seems unhappy.
|
|
176
|
+
// if we catch this we'll need to debug accordingly.
|
|
177
|
+
throw new NordError("internal assertion violation", { cause: r });
|
|
178
|
+
}
|
|
179
|
+
return r.data;
|
|
180
|
+
}
|
|
181
|
+
|
|
165
182
|
/**
|
|
166
183
|
* Get the current timestamp from the Nord server
|
|
167
184
|
*
|
|
@@ -169,7 +186,7 @@ export class Nord {
|
|
|
169
186
|
* @throws {NordError} If the request fails
|
|
170
187
|
*/
|
|
171
188
|
async getTimestamp(): Promise<bigint> {
|
|
172
|
-
return
|
|
189
|
+
return BigInt(await this.GET("/timestamp", {}));
|
|
173
190
|
}
|
|
174
191
|
|
|
175
192
|
/**
|
|
@@ -179,7 +196,7 @@ export class Nord {
|
|
|
179
196
|
* @throws {NordError} If the request fails
|
|
180
197
|
*/
|
|
181
198
|
async getActionNonce(): Promise<number> {
|
|
182
|
-
return
|
|
199
|
+
return await this.GET("/event/last-acked-nonce", {});
|
|
183
200
|
}
|
|
184
201
|
|
|
185
202
|
/**
|
|
@@ -189,7 +206,7 @@ export class Nord {
|
|
|
189
206
|
*/
|
|
190
207
|
async fetchNordInfo(): Promise<void> {
|
|
191
208
|
try {
|
|
192
|
-
const info = await
|
|
209
|
+
const info = await this.GET("/info", {});
|
|
193
210
|
this.markets = info.markets;
|
|
194
211
|
this.tokens = info.tokens;
|
|
195
212
|
|
|
@@ -246,16 +263,6 @@ export class Nord {
|
|
|
246
263
|
await this.fetchNordInfo();
|
|
247
264
|
}
|
|
248
265
|
|
|
249
|
-
/**
|
|
250
|
-
* Get market statistics
|
|
251
|
-
*
|
|
252
|
-
* @returns Market statistics response
|
|
253
|
-
* @throws {NordError} If the request fails
|
|
254
|
-
*/
|
|
255
|
-
public async marketsStats(): Promise<MarketsStatsResponse> {
|
|
256
|
-
return market.marketsStats(this.webServerUrl);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
266
|
/**
|
|
260
267
|
* Query a specific action
|
|
261
268
|
*
|
|
@@ -263,8 +270,19 @@ export class Nord {
|
|
|
263
270
|
* @returns Action response
|
|
264
271
|
* @throws {NordError} If the request fails
|
|
265
272
|
*/
|
|
266
|
-
async queryAction(
|
|
267
|
-
|
|
273
|
+
async queryAction({
|
|
274
|
+
action_id,
|
|
275
|
+
}: {
|
|
276
|
+
action_id: number;
|
|
277
|
+
}): Promise<ActionResponse | null> {
|
|
278
|
+
return (
|
|
279
|
+
(
|
|
280
|
+
await this.queryRecentActions({
|
|
281
|
+
from: action_id,
|
|
282
|
+
to: action_id,
|
|
283
|
+
})
|
|
284
|
+
)[0] ?? null
|
|
285
|
+
);
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
/**
|
|
@@ -275,8 +293,23 @@ export class Nord {
|
|
|
275
293
|
* @returns Actions response
|
|
276
294
|
* @throws {NordError} If the request fails
|
|
277
295
|
*/
|
|
278
|
-
async queryRecentActions(
|
|
279
|
-
|
|
296
|
+
async queryRecentActions(query: {
|
|
297
|
+
from: number;
|
|
298
|
+
to: number;
|
|
299
|
+
}): Promise<ActionResponse[]> {
|
|
300
|
+
const xs = await this.GET("/action", {
|
|
301
|
+
params: {
|
|
302
|
+
query,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
return xs.map((x) => ({
|
|
306
|
+
actionId: x.actionId,
|
|
307
|
+
action: utils.decodeLengthDelimited(
|
|
308
|
+
Buffer.from(x.payload, "base64"),
|
|
309
|
+
proto.Action,
|
|
310
|
+
),
|
|
311
|
+
physicalExecTime: new Date(x.physicalTime * 1000),
|
|
312
|
+
}));
|
|
280
313
|
}
|
|
281
314
|
|
|
282
315
|
/**
|
|
@@ -286,7 +319,7 @@ export class Nord {
|
|
|
286
319
|
* @throws {NordError} If the request fails
|
|
287
320
|
*/
|
|
288
321
|
async getLastActionId(): Promise<number> {
|
|
289
|
-
return
|
|
322
|
+
return await this.GET("/action/last-executed-id", {});
|
|
290
323
|
}
|
|
291
324
|
|
|
292
325
|
/**
|
|
@@ -351,28 +384,6 @@ export class Nord {
|
|
|
351
384
|
return metrics.getTotalTransactions(this.webServerUrl);
|
|
352
385
|
}
|
|
353
386
|
|
|
354
|
-
/**
|
|
355
|
-
* Query an action from Rollman
|
|
356
|
-
*
|
|
357
|
-
* @param query - Action query parameters
|
|
358
|
-
* @returns Rollman action response
|
|
359
|
-
* @throws {NordError} If the request fails
|
|
360
|
-
*/
|
|
361
|
-
async actionQueryRollman(query: ActionQuery): Promise<RollmanActionResponse> {
|
|
362
|
-
return queries.actionQueryRollman(this.webServerUrl, query);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Query actions from Rollman
|
|
367
|
-
*
|
|
368
|
-
* @param last_n - Number of recent actions to query
|
|
369
|
-
* @returns Rollman actions response
|
|
370
|
-
* @throws {NordError} If the request fails
|
|
371
|
-
*/
|
|
372
|
-
async actionsQueryRollman(last_n: number): Promise<RollmanActionsResponse> {
|
|
373
|
-
return queries.actionsQueryRollman(this.webServerUrl, last_n);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
387
|
/**
|
|
377
388
|
* Query Prometheus metrics
|
|
378
389
|
*
|
|
@@ -511,8 +522,38 @@ export class Nord {
|
|
|
511
522
|
* @returns Trades response
|
|
512
523
|
* @throws {NordError} If the request fails
|
|
513
524
|
*/
|
|
514
|
-
public async getTrades(
|
|
515
|
-
|
|
525
|
+
public async getTrades(
|
|
526
|
+
query: Readonly<{
|
|
527
|
+
marketId?: number;
|
|
528
|
+
takerId?: number;
|
|
529
|
+
makerId?: number;
|
|
530
|
+
takerSide?: "bid" | "ask";
|
|
531
|
+
pageSize?: number;
|
|
532
|
+
sinceRcf3339?: string;
|
|
533
|
+
untilRfc3339?: string;
|
|
534
|
+
pageId?: string;
|
|
535
|
+
}>,
|
|
536
|
+
): Promise<TradesResponse> {
|
|
537
|
+
if (query.sinceRcf3339 && !utils.isRfc3339(query.sinceRcf3339)) {
|
|
538
|
+
throw new NordError(`Invalid RFC3339 timestamp: ${query.sinceRcf3339}`);
|
|
539
|
+
}
|
|
540
|
+
if (query.untilRfc3339 && !utils.isRfc3339(query.untilRfc3339)) {
|
|
541
|
+
throw new NordError(`Invalid RFC3339 timestamp: ${query.untilRfc3339}`);
|
|
542
|
+
}
|
|
543
|
+
return await this.GET("/trades", {
|
|
544
|
+
params: {
|
|
545
|
+
query: {
|
|
546
|
+
takerId: query.takerId,
|
|
547
|
+
makerId: query.makerId,
|
|
548
|
+
marketId: query.marketId,
|
|
549
|
+
pageSize: query.pageSize,
|
|
550
|
+
takerSide: query.takerSide,
|
|
551
|
+
since: query.sinceRcf3339,
|
|
552
|
+
until: query.untilRfc3339,
|
|
553
|
+
startInclusive: query.pageId,
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
});
|
|
516
557
|
}
|
|
517
558
|
|
|
518
559
|
/**
|
|
@@ -522,10 +563,18 @@ export class Nord {
|
|
|
522
563
|
* @returns User account IDs response
|
|
523
564
|
* @throws {NordError} If the request fails
|
|
524
565
|
*/
|
|
525
|
-
public async
|
|
526
|
-
|
|
527
|
-
): Promise<
|
|
528
|
-
|
|
566
|
+
public async getUser(query: {
|
|
567
|
+
pubkey: string | PublicKey;
|
|
568
|
+
}): Promise<User | null> {
|
|
569
|
+
const r = await this.httpClient.GET("/user/{pubkey}", {
|
|
570
|
+
params: {
|
|
571
|
+
path: { pubkey: query.pubkey.toString() },
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
if (r.response.status === 404) {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
return r.data!;
|
|
529
578
|
}
|
|
530
579
|
|
|
531
580
|
/**
|
|
@@ -539,21 +588,31 @@ export class Nord {
|
|
|
539
588
|
*/
|
|
540
589
|
public async getOrderbook(query: OrderbookQuery): Promise<OrderbookResponse> {
|
|
541
590
|
// If only symbol is provided, convert it to market_id
|
|
591
|
+
let marketId: number;
|
|
542
592
|
if (query.symbol && query.market_id === undefined) {
|
|
543
593
|
// If the map is empty, try to fetch market information first
|
|
544
594
|
if (this.symbolToMarketId.size === 0) {
|
|
545
595
|
await this.fetchNordInfo();
|
|
546
596
|
}
|
|
547
597
|
|
|
548
|
-
const
|
|
549
|
-
if (
|
|
598
|
+
const id = this.symbolToMarketId.get(query.symbol);
|
|
599
|
+
if (id === undefined) {
|
|
550
600
|
throw new NordError(`Unknown market symbol: ${query.symbol}`);
|
|
551
601
|
}
|
|
552
|
-
|
|
553
|
-
|
|
602
|
+
marketId = id;
|
|
603
|
+
} else if (query.market_id !== undefined) {
|
|
604
|
+
marketId = query.market_id;
|
|
605
|
+
} else {
|
|
606
|
+
throw new NordError(
|
|
607
|
+
"Either symbol or market_id must be provided for orderbook query",
|
|
608
|
+
);
|
|
554
609
|
}
|
|
555
610
|
|
|
556
|
-
return
|
|
611
|
+
return await this.GET("/market/{market_id}/orderbook", {
|
|
612
|
+
params: {
|
|
613
|
+
path: { market_id: marketId },
|
|
614
|
+
},
|
|
615
|
+
});
|
|
557
616
|
}
|
|
558
617
|
|
|
559
618
|
/**
|
|
@@ -563,7 +622,7 @@ export class Nord {
|
|
|
563
622
|
* @throws {NordError} If the request fails
|
|
564
623
|
*/
|
|
565
624
|
public async getInfo(): Promise<Info> {
|
|
566
|
-
return
|
|
625
|
+
return await this.GET("/info", {});
|
|
567
626
|
}
|
|
568
627
|
|
|
569
628
|
/**
|
|
@@ -574,17 +633,28 @@ export class Nord {
|
|
|
574
633
|
* @throws {NordError} If the request fails
|
|
575
634
|
*/
|
|
576
635
|
public async getAccount(accountId: number): Promise<Account> {
|
|
577
|
-
return
|
|
636
|
+
return await this.GET("/account/{account_id}", {
|
|
637
|
+
params: {
|
|
638
|
+
path: { account_id: accountId },
|
|
639
|
+
},
|
|
640
|
+
});
|
|
578
641
|
}
|
|
579
642
|
|
|
580
643
|
/**
|
|
581
644
|
* Get market statistics (alias for marketsStats for backward compatibility)
|
|
582
645
|
*
|
|
583
|
-
* @deprecated Use marketsStats instead
|
|
584
646
|
* @returns Market statistics response
|
|
585
647
|
*/
|
|
586
|
-
public async getMarketStats(
|
|
587
|
-
|
|
648
|
+
public async getMarketStats({
|
|
649
|
+
marketId,
|
|
650
|
+
}: {
|
|
651
|
+
marketId: number;
|
|
652
|
+
}): Promise<MarketStats> {
|
|
653
|
+
return await this.GET("/market/{market_id}/stats", {
|
|
654
|
+
params: {
|
|
655
|
+
path: { market_id: marketId },
|
|
656
|
+
},
|
|
657
|
+
});
|
|
588
658
|
}
|
|
589
659
|
|
|
590
660
|
/**
|
|
@@ -592,13 +662,9 @@ export class Nord {
|
|
|
592
662
|
*
|
|
593
663
|
* @param address - The public key address to check
|
|
594
664
|
* @returns True if the account exists, false otherwise
|
|
665
|
+
* @deprecated use getUser instead
|
|
595
666
|
*/
|
|
596
|
-
public async accountExists(
|
|
597
|
-
|
|
598
|
-
await market.getUserAccountIds(this.webServerUrl, { pubkey: address });
|
|
599
|
-
return true;
|
|
600
|
-
} catch {
|
|
601
|
-
return false;
|
|
602
|
-
}
|
|
667
|
+
public async accountExists(pubkey: string | PublicKey): Promise<boolean> {
|
|
668
|
+
return !!(await this.getUser({ pubkey }));
|
|
603
669
|
}
|
|
604
670
|
}
|
|
@@ -9,8 +9,9 @@ import Decimal from "decimal.js";
|
|
|
9
9
|
import * as ed from "@noble/ed25519";
|
|
10
10
|
import { sha512 } from "@noble/hashes/sha512";
|
|
11
11
|
ed.etc.sha512Sync = sha512;
|
|
12
|
-
import {
|
|
13
|
-
import { FillMode, Order, Side } from "../../types";
|
|
12
|
+
import { floatToScaledBigIntLossy } from "@n1xyz/proton";
|
|
13
|
+
import { FillMode, Order, Side, SPLTokenInfo } from "../../types";
|
|
14
|
+
import * as proto from "../../gen/nord";
|
|
14
15
|
import {
|
|
15
16
|
BigIntValue,
|
|
16
17
|
checkedFetch,
|
|
@@ -26,6 +27,8 @@ import {
|
|
|
26
27
|
revokeSession,
|
|
27
28
|
transfer,
|
|
28
29
|
withdraw,
|
|
30
|
+
atomic as atomicAction,
|
|
31
|
+
AtomicSubaction as ApiAtomicSubaction,
|
|
29
32
|
} from "../api/actions";
|
|
30
33
|
import { NordError } from "../utils/NordError";
|
|
31
34
|
import { Nord } from "./Nord";
|
|
@@ -47,7 +50,7 @@ export interface NordUserParams {
|
|
|
47
50
|
sessionSignFn: (message: Uint8Array) => Promise<Uint8Array>;
|
|
48
51
|
|
|
49
52
|
/** Function to sign transactions with the user's wallet (optional) */
|
|
50
|
-
transactionSignFn: (
|
|
53
|
+
transactionSignFn: <T extends Transaction>(tx: T) => Promise<T>;
|
|
51
54
|
|
|
52
55
|
/** Solana connection (optional) */
|
|
53
56
|
connection?: Connection;
|
|
@@ -111,6 +114,41 @@ export interface TransferParams {
|
|
|
111
114
|
toAccountId: number;
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Parameters for individual atomic subactions (user-friendly version)
|
|
119
|
+
*/
|
|
120
|
+
export interface UserAtomicSubaction {
|
|
121
|
+
/** The type of action to perform. */
|
|
122
|
+
kind: "place" | "cancel";
|
|
123
|
+
|
|
124
|
+
/** The market ID to place the order in. */
|
|
125
|
+
marketId?: number;
|
|
126
|
+
|
|
127
|
+
/** The order ID to cancel. */
|
|
128
|
+
orderId?: BigIntValue;
|
|
129
|
+
|
|
130
|
+
/** Order side (bid or ask) */
|
|
131
|
+
side?: Side;
|
|
132
|
+
|
|
133
|
+
/** Fill mode (limit, market, etc.) */
|
|
134
|
+
fillMode?: FillMode;
|
|
135
|
+
|
|
136
|
+
/** Whether the order is reduce-only. */
|
|
137
|
+
isReduceOnly?: boolean;
|
|
138
|
+
|
|
139
|
+
/** The size of the order. */
|
|
140
|
+
size?: Decimal.Value;
|
|
141
|
+
|
|
142
|
+
/** Order price */
|
|
143
|
+
price?: Decimal.Value;
|
|
144
|
+
|
|
145
|
+
/** Quote size (for market orders) */
|
|
146
|
+
quoteSize?: Decimal.Value;
|
|
147
|
+
|
|
148
|
+
/** The client order ID of the order. */
|
|
149
|
+
clientOrderId?: BigIntValue;
|
|
150
|
+
}
|
|
151
|
+
|
|
114
152
|
/**
|
|
115
153
|
* User class for interacting with the Nord protocol
|
|
116
154
|
*/
|
|
@@ -130,16 +168,15 @@ export class NordUser {
|
|
|
130
168
|
public readonly sessionSignFn: (message: Uint8Array) => Promise<Uint8Array>;
|
|
131
169
|
|
|
132
170
|
/** Function to sign transactions with the user's wallet */
|
|
133
|
-
public readonly transactionSignFn:
|
|
171
|
+
public readonly transactionSignFn: <T extends Transaction>(
|
|
172
|
+
tx: T,
|
|
173
|
+
) => Promise<T>;
|
|
134
174
|
|
|
135
175
|
/** User balances by token symbol */
|
|
136
176
|
public balances: {
|
|
137
177
|
[key: string]: { accountId: number; balance: number; symbol: string }[];
|
|
138
178
|
} = {};
|
|
139
179
|
|
|
140
|
-
/** User orders by market symbol */
|
|
141
|
-
public orders: { [key: string]: Order[] } = {};
|
|
142
|
-
|
|
143
180
|
/** User positions by account ID */
|
|
144
181
|
public positions: {
|
|
145
182
|
[key: string]: {
|
|
@@ -275,7 +312,6 @@ export class NordUser {
|
|
|
275
312
|
|
|
276
313
|
// Copy other properties
|
|
277
314
|
cloned.balances = { ...this.balances };
|
|
278
|
-
cloned.orders = { ...this.orders };
|
|
279
315
|
cloned.positions = { ...this.positions };
|
|
280
316
|
cloned.margins = { ...this.margins };
|
|
281
317
|
cloned.accountIds = this.accountIds ? [...this.accountIds] : undefined;
|
|
@@ -416,10 +452,37 @@ export class NordUser {
|
|
|
416
452
|
*
|
|
417
453
|
* @param amount - Amount to deposit
|
|
418
454
|
* @param tokenId - Token ID
|
|
455
|
+
* @param recipient - Recipient address; defaults to the user's address
|
|
419
456
|
* @returns Transaction signature
|
|
457
|
+
* @deprecated Use deposit instead
|
|
420
458
|
* @throws {NordError} If required parameters are missing or operation fails
|
|
421
459
|
*/
|
|
422
|
-
async depositSpl(
|
|
460
|
+
async depositSpl(
|
|
461
|
+
amount: number,
|
|
462
|
+
tokenId: number,
|
|
463
|
+
recipient?: PublicKey,
|
|
464
|
+
): Promise<string> {
|
|
465
|
+
return this.deposit({ amount, tokenId, recipient });
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Deposit SPL tokens to the bridge
|
|
470
|
+
*
|
|
471
|
+
* @param amount - Amount to deposit
|
|
472
|
+
* @param tokenId - Token ID
|
|
473
|
+
* @param recipient - Recipient address; defaults to the user's address
|
|
474
|
+
* @returns Transaction signature
|
|
475
|
+
* @throws {NordError} If required parameters are missing or operation fails
|
|
476
|
+
*/
|
|
477
|
+
async deposit({
|
|
478
|
+
amount,
|
|
479
|
+
tokenId,
|
|
480
|
+
recipient,
|
|
481
|
+
}: Readonly<{
|
|
482
|
+
amount: number;
|
|
483
|
+
tokenId: number;
|
|
484
|
+
recipient?: PublicKey;
|
|
485
|
+
}>): Promise<string> {
|
|
423
486
|
try {
|
|
424
487
|
// Find the token info
|
|
425
488
|
const tokenInfo = this.splTokenInfos.find((t) => t.tokenId === tokenId);
|
|
@@ -428,35 +491,28 @@ export class NordUser {
|
|
|
428
491
|
}
|
|
429
492
|
|
|
430
493
|
const mint = new PublicKey(tokenInfo.mint);
|
|
431
|
-
// Get the user's token account
|
|
432
494
|
const fromAccount = await this.getAssociatedTokenAccount(mint);
|
|
495
|
+
const payer = this.getSolanaPublicKey();
|
|
433
496
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const depositParams: DepositSplParams = {
|
|
439
|
-
amount: amountBN,
|
|
497
|
+
const { ix, extraSigner } = await this.nord.protonClient.buildDepositIx({
|
|
498
|
+
payer,
|
|
499
|
+
recipient: recipient ?? payer,
|
|
500
|
+
quantAmount: floatToScaledBigIntLossy(amount, tokenInfo.precision),
|
|
440
501
|
mint,
|
|
441
|
-
fromAccount,
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
// Build the deposit transaction using proton client
|
|
445
|
-
const depositTx = await this.nord.protonClient.buildDepositTx(
|
|
446
|
-
depositParams,
|
|
447
|
-
this.getSolanaPublicKey(),
|
|
448
|
-
this.connection,
|
|
449
|
-
);
|
|
502
|
+
sourceTokenAccount: fromAccount,
|
|
503
|
+
});
|
|
450
504
|
|
|
451
|
-
const { blockhash } =
|
|
505
|
+
const { blockhash } =
|
|
506
|
+
await this.connection.getLatestBlockhash("confirmed");
|
|
507
|
+
const tx = new Transaction();
|
|
452
508
|
|
|
453
|
-
|
|
454
|
-
|
|
509
|
+
tx.add(ix);
|
|
510
|
+
tx.recentBlockhash = blockhash;
|
|
511
|
+
tx.feePayer = payer;
|
|
455
512
|
|
|
456
|
-
const signedTx = await this.transactionSignFn(
|
|
513
|
+
const signedTx = await this.transactionSignFn(tx);
|
|
514
|
+
signedTx.partialSign(extraSigner);
|
|
457
515
|
|
|
458
|
-
// TODO: should use `VersionedTransaction` and remove any for `transactionSignFn`,
|
|
459
|
-
// this is incredibly annoying to debug and i could've saved 30 mins.
|
|
460
516
|
const signature = await this.connection.sendRawTransaction(
|
|
461
517
|
signedTx.serialize(),
|
|
462
518
|
);
|
|
@@ -490,10 +546,14 @@ export class NordUser {
|
|
|
490
546
|
throw new NordError("Public key is required to update account ID");
|
|
491
547
|
}
|
|
492
548
|
|
|
493
|
-
const resp = await this.nord.
|
|
549
|
+
const resp = await this.nord.getUser({
|
|
494
550
|
pubkey: this.publicKey.toBase58(),
|
|
495
551
|
});
|
|
496
552
|
|
|
553
|
+
if (!resp) {
|
|
554
|
+
throw new NordError(`User ${this.publicKey.toBase58()} not found`);
|
|
555
|
+
}
|
|
556
|
+
|
|
497
557
|
this.accountIds = resp.accountIds;
|
|
498
558
|
} catch (error) {
|
|
499
559
|
throw new NordError("Failed to update account ID", { cause: error });
|
|
@@ -582,19 +642,6 @@ export class NordUser {
|
|
|
582
642
|
});
|
|
583
643
|
}
|
|
584
644
|
|
|
585
|
-
// Process orders
|
|
586
|
-
this.orders[accountData.accountId] = accountData.orders.map(
|
|
587
|
-
(order: OpenOrder) => {
|
|
588
|
-
return {
|
|
589
|
-
orderId: order.orderId,
|
|
590
|
-
isLong: order.side === "bid",
|
|
591
|
-
size: order.size,
|
|
592
|
-
price: order.price,
|
|
593
|
-
marketId: order.marketId,
|
|
594
|
-
};
|
|
595
|
-
},
|
|
596
|
-
);
|
|
597
|
-
|
|
598
645
|
// Process positions
|
|
599
646
|
this.positions[accountData.accountId] = accountData.positions;
|
|
600
647
|
|
|
@@ -610,7 +657,6 @@ export class NordUser {
|
|
|
610
657
|
* @throws {NordError} If the operation fails
|
|
611
658
|
*/
|
|
612
659
|
async refreshSession(): Promise<void> {
|
|
613
|
-
console.log(this.publicKey);
|
|
614
660
|
this.sessionId = await createSession(
|
|
615
661
|
this.nord.webServerUrl,
|
|
616
662
|
this.walletSignFn,
|
|
@@ -666,10 +712,13 @@ export class NordUser {
|
|
|
666
712
|
* @param amount - Amount to withdraw
|
|
667
713
|
* @throws {NordError} If the operation fails
|
|
668
714
|
*/
|
|
669
|
-
async withdraw(
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
715
|
+
async withdraw({
|
|
716
|
+
amount,
|
|
717
|
+
tokenId,
|
|
718
|
+
}: Readonly<{
|
|
719
|
+
tokenId: number;
|
|
720
|
+
amount: number;
|
|
721
|
+
}>): Promise<{ actionId: bigint }> {
|
|
673
722
|
try {
|
|
674
723
|
this.checkSessionValidity();
|
|
675
724
|
const { actionId } = await withdraw(
|
|
@@ -796,6 +845,78 @@ export class NordUser {
|
|
|
796
845
|
}
|
|
797
846
|
}
|
|
798
847
|
|
|
848
|
+
/**
|
|
849
|
+
* Execute up to four place/cancel operations atomically.
|
|
850
|
+
* Per Market:
|
|
851
|
+
* 1. cancels can only be in the start (one cannot predict future order ids)
|
|
852
|
+
* 2. intermediate trades can trade only
|
|
853
|
+
* 3. placements go last
|
|
854
|
+
*
|
|
855
|
+
* Across Markets, order action can be any
|
|
856
|
+
*
|
|
857
|
+
* @param userActions array of user-friendly subactions
|
|
858
|
+
* @param providedAccountId optional account performing the action (defaults to first account)
|
|
859
|
+
*/
|
|
860
|
+
async atomic(
|
|
861
|
+
userActions: UserAtomicSubaction[],
|
|
862
|
+
providedAccountId?: number,
|
|
863
|
+
): Promise<proto.Receipt_AtomicResult> {
|
|
864
|
+
try {
|
|
865
|
+
this.checkSessionValidity();
|
|
866
|
+
|
|
867
|
+
const accountId =
|
|
868
|
+
providedAccountId != null ? providedAccountId : this.accountIds?.[0];
|
|
869
|
+
|
|
870
|
+
if (accountId == null) {
|
|
871
|
+
throw new NordError(
|
|
872
|
+
"Account ID is undefined. Make sure to call updateAccountId() before atomic operations.",
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const apiActions: ApiAtomicSubaction[] = userActions.map((act) => {
|
|
877
|
+
if (act.kind === "place") {
|
|
878
|
+
const market = findMarket(this.nord.markets, act.marketId!);
|
|
879
|
+
if (!market) {
|
|
880
|
+
throw new NordError(`Market ${act.marketId} not found`);
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
kind: "place",
|
|
884
|
+
marketId: act.marketId,
|
|
885
|
+
side: act.side,
|
|
886
|
+
fillMode: act.fillMode,
|
|
887
|
+
isReduceOnly: act.isReduceOnly,
|
|
888
|
+
sizeDecimals: market.sizeDecimals,
|
|
889
|
+
priceDecimals: market.priceDecimals,
|
|
890
|
+
size: act.size,
|
|
891
|
+
price: act.price,
|
|
892
|
+
quoteSizeSize: act.quoteSize, // treated as quote size; we pass only size component
|
|
893
|
+
quoteSizePrice: undefined,
|
|
894
|
+
clientOrderId: act.clientOrderId,
|
|
895
|
+
} as ApiAtomicSubaction;
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
kind: "cancel",
|
|
899
|
+
orderId: act.orderId,
|
|
900
|
+
} as ApiAtomicSubaction;
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
const result = await atomicAction(
|
|
904
|
+
this.nord.webServerUrl,
|
|
905
|
+
this.sessionSignFn,
|
|
906
|
+
await this.nord.getTimestamp(),
|
|
907
|
+
this.getNonce(),
|
|
908
|
+
{
|
|
909
|
+
sessionId: optExpect(this.sessionId, "No session"),
|
|
910
|
+
accountId: accountId,
|
|
911
|
+
actions: apiActions,
|
|
912
|
+
},
|
|
913
|
+
);
|
|
914
|
+
return result;
|
|
915
|
+
} catch (error) {
|
|
916
|
+
throw new NordError("Atomic operation failed", { cause: error });
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
799
920
|
/**
|
|
800
921
|
* Helper function to retry a promise with exponential backoff
|
|
801
922
|
*
|