@n1xyz/nord-ts 0.3.2 → 0.3.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.
- package/dist/actions.js +184 -0
- package/dist/client/Nord.d.ts +9 -15
- package/dist/client/Nord.js +759 -0
- package/dist/client/NordAdmin.js +362 -0
- package/dist/client/NordUser.d.ts +3 -1
- package/dist/client/NordUser.js +752 -0
- package/dist/const.js +27 -0
- package/dist/error.js +51 -0
- package/dist/gen/nord_pb.d.ts +132 -114
- package/dist/gen/nord_pb.js +1068 -0
- package/dist/gen/openapi.d.ts +345 -72
- package/dist/gen/openapi.js +5 -0
- package/dist/index.browser.js +61342 -80207
- package/dist/index.common.js +59722 -87597
- package/dist/index.js +10 -0
- package/dist/nord/api/actions.d.ts +128 -0
- package/dist/nord/api/actions.js +396 -0
- package/dist/nord/api/core.d.ts +16 -0
- package/dist/nord/api/core.js +81 -0
- package/dist/nord/api/metrics.d.ts +67 -0
- package/dist/nord/api/metrics.js +229 -0
- package/dist/nord/api/triggers.d.ts +7 -0
- package/dist/nord/api/triggers.js +38 -0
- package/dist/nord/client/Nord.d.ts +387 -0
- package/dist/nord/client/Nord.js +747 -0
- package/dist/nord/client/NordAdmin.d.ts +226 -0
- package/dist/nord/client/NordAdmin.js +410 -0
- package/dist/nord/client/NordClient.d.ts +16 -0
- package/dist/nord/client/NordClient.js +28 -0
- package/dist/nord/client/NordUser.d.ts +379 -0
- package/dist/nord/client/NordUser.js +787 -0
- package/dist/nord/index.d.ts +8 -0
- package/dist/nord/index.js +34 -0
- package/dist/nord/models/Subscriber.d.ts +37 -0
- package/dist/nord/models/Subscriber.js +25 -0
- package/dist/nord/utils/NordError.d.ts +35 -0
- package/dist/nord/utils/NordError.js +49 -0
- package/dist/types.d.ts +15 -6
- package/dist/types.js +92 -0
- package/dist/utils.js +193 -0
- package/dist/websocket/NordWebSocketClient.d.ts +1 -0
- package/dist/websocket/NordWebSocketClient.js +242 -0
- package/dist/websocket/Subscriber.d.ts +7 -1
- package/dist/websocket/Subscriber.js +24 -0
- package/dist/websocket/events.d.ts +2 -1
- package/dist/websocket/events.js +1 -0
- package/dist/websocket/index.d.ts +1 -1
- package/dist/websocket/index.js +80 -0
- package/package.json +2 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Nord } from "./client/Nord";
|
|
2
|
+
export { NordUser } from "./client/NordUser";
|
|
3
|
+
export { NordAdmin, AclRole } from "./client/NordAdmin";
|
|
4
|
+
export { NordError } from "./utils/NordError";
|
|
5
|
+
export * from "./api/core";
|
|
6
|
+
export * from "./api/metrics";
|
|
7
|
+
export * from "./api/actions";
|
|
8
|
+
export * from "./models/Subscriber";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.NordError = exports.AclRole = exports.NordAdmin = exports.NordUser = exports.Nord = void 0;
|
|
18
|
+
// Export main client classes
|
|
19
|
+
var Nord_1 = require("./client/Nord");
|
|
20
|
+
Object.defineProperty(exports, "Nord", { enumerable: true, get: function () { return Nord_1.Nord; } });
|
|
21
|
+
var NordUser_1 = require("./client/NordUser");
|
|
22
|
+
Object.defineProperty(exports, "NordUser", { enumerable: true, get: function () { return NordUser_1.NordUser; } });
|
|
23
|
+
var NordAdmin_1 = require("./client/NordAdmin");
|
|
24
|
+
Object.defineProperty(exports, "NordAdmin", { enumerable: true, get: function () { return NordAdmin_1.NordAdmin; } });
|
|
25
|
+
Object.defineProperty(exports, "AclRole", { enumerable: true, get: function () { return NordAdmin_1.AclRole; } });
|
|
26
|
+
// Export utility classes
|
|
27
|
+
var NordError_1 = require("./utils/NordError");
|
|
28
|
+
Object.defineProperty(exports, "NordError", { enumerable: true, get: function () { return NordError_1.NordError; } });
|
|
29
|
+
// Export API modules
|
|
30
|
+
__exportStar(require("./api/core"), exports);
|
|
31
|
+
__exportStar(require("./api/metrics"), exports);
|
|
32
|
+
__exportStar(require("./api/actions"), exports);
|
|
33
|
+
// Export models
|
|
34
|
+
__exportStar(require("./models/Subscriber"), exports);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import { Account, DeltaEvent, OrderbookResponse, SubscriberConfig, StreamTrade, Trades } from "../../types";
|
|
3
|
+
/**
|
|
4
|
+
* Subscriber class for handling WebSocket subscriptions
|
|
5
|
+
*/
|
|
6
|
+
export declare class Subscriber {
|
|
7
|
+
streamURL: string;
|
|
8
|
+
buffer: (DeltaEvent | Trades | Account)[];
|
|
9
|
+
maxBufferLen: number;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new Subscriber instance
|
|
12
|
+
* @param config Subscriber configuration
|
|
13
|
+
*/
|
|
14
|
+
constructor(config: SubscriberConfig);
|
|
15
|
+
/**
|
|
16
|
+
* Subscribe to WebSocket events
|
|
17
|
+
*/
|
|
18
|
+
subscribe(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Interface for orderbook subscription
|
|
22
|
+
*/
|
|
23
|
+
export interface OrderbookSubscription extends EventEmitter {
|
|
24
|
+
on(event: "message", listener: (data: OrderbookResponse) => void): this;
|
|
25
|
+
on(event: "error", listener: (error: Error) => void): this;
|
|
26
|
+
close(): void;
|
|
27
|
+
removeAllListeners(event?: string): this;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Interface for trade subscription
|
|
31
|
+
*/
|
|
32
|
+
export interface TradeSubscription extends EventEmitter {
|
|
33
|
+
on(event: "message", listener: (data: StreamTrade[]) => void): this;
|
|
34
|
+
on(event: "error", listener: (error: Error) => void): this;
|
|
35
|
+
close(): void;
|
|
36
|
+
removeAllListeners(event?: string): this;
|
|
37
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Subscriber = void 0;
|
|
4
|
+
const utils_1 = require("../../utils");
|
|
5
|
+
/**
|
|
6
|
+
* Subscriber class for handling WebSocket subscriptions
|
|
7
|
+
*/
|
|
8
|
+
class Subscriber {
|
|
9
|
+
/**
|
|
10
|
+
* Create a new Subscriber instance
|
|
11
|
+
* @param config Subscriber configuration
|
|
12
|
+
*/
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.streamURL = config.streamURL;
|
|
15
|
+
this.buffer = [];
|
|
16
|
+
this.maxBufferLen = config.maxBufferLen ?? utils_1.MAX_BUFFER_LEN;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Subscribe to WebSocket events
|
|
20
|
+
*/
|
|
21
|
+
subscribe() {
|
|
22
|
+
// TODO: Implement subscription logic
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.Subscriber = Subscriber;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for creating a NordError
|
|
3
|
+
*/
|
|
4
|
+
export interface NordErrorOptions {
|
|
5
|
+
/** The original error that caused this error */
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
/** HTTP status code (if applicable) */
|
|
8
|
+
statusCode?: number;
|
|
9
|
+
/** Additional error details */
|
|
10
|
+
details?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Custom error class for Nord-related errors
|
|
14
|
+
*/
|
|
15
|
+
export declare class NordError extends Error {
|
|
16
|
+
/** The original error that caused this error */
|
|
17
|
+
readonly cause?: unknown;
|
|
18
|
+
/** HTTP status code (if applicable) */
|
|
19
|
+
readonly statusCode?: number;
|
|
20
|
+
/** Additional error details */
|
|
21
|
+
readonly details?: Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Create a new NordError
|
|
24
|
+
*
|
|
25
|
+
* @param message - Error message
|
|
26
|
+
* @param options - Error options
|
|
27
|
+
*/
|
|
28
|
+
constructor(message: string, options?: NordErrorOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Convert the error to a string representation
|
|
31
|
+
*
|
|
32
|
+
* @returns String representation of the error
|
|
33
|
+
*/
|
|
34
|
+
toString(): string;
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NordError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Custom error class for Nord-related errors
|
|
6
|
+
*/
|
|
7
|
+
class NordError extends Error {
|
|
8
|
+
/**
|
|
9
|
+
* Create a new NordError
|
|
10
|
+
*
|
|
11
|
+
* @param message - Error message
|
|
12
|
+
* @param options - Error options
|
|
13
|
+
*/
|
|
14
|
+
constructor(message, options = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "NordError";
|
|
17
|
+
this.cause = options.cause;
|
|
18
|
+
this.statusCode = options.statusCode;
|
|
19
|
+
this.details = options.details;
|
|
20
|
+
// Capture stack trace
|
|
21
|
+
if (Error.captureStackTrace) {
|
|
22
|
+
Error.captureStackTrace(this, NordError);
|
|
23
|
+
}
|
|
24
|
+
// Handle nested errors
|
|
25
|
+
if (this.cause instanceof Error) {
|
|
26
|
+
this.stack =
|
|
27
|
+
this.stack + "\nCaused by: " + (this.cause.stack || this.cause.message);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Convert the error to a string representation
|
|
32
|
+
*
|
|
33
|
+
* @returns String representation of the error
|
|
34
|
+
*/
|
|
35
|
+
toString() {
|
|
36
|
+
let result = `${this.name}: ${this.message}`;
|
|
37
|
+
if (this.statusCode) {
|
|
38
|
+
result += ` \nstatus: ${this.statusCode}`;
|
|
39
|
+
}
|
|
40
|
+
if (this.details && Object.keys(this.details).length > 0) {
|
|
41
|
+
result += ` \ndetails: ${JSON.stringify(this.details, null, 2)}`;
|
|
42
|
+
}
|
|
43
|
+
if (this.cause) {
|
|
44
|
+
result += ` \ncause: ${this.cause.toString()}`;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.NordError = NordError;
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Connection } from "@solana/web3.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Nord subscription type for trades or deltas
|
|
7
7
|
*/
|
|
8
|
-
export type SubscriptionType = "trades" | "deltas" | "account";
|
|
8
|
+
export type SubscriptionType = "trades" | "deltas" | "account" | "candle";
|
|
9
9
|
/**
|
|
10
10
|
* Pattern for a valid Nord subscription
|
|
11
11
|
* Format should be: "<type>@<parameter>"
|
|
@@ -64,7 +64,7 @@ export type AccountPnlInfo = components["schemas"]["AccountPnlInfo"];
|
|
|
64
64
|
export type AccountPnlInfoPage = components["schemas"]["PageResult_for_String_and_AccountPnlInfo"];
|
|
65
65
|
export type AccountTriggerInfo = components["schemas"]["TriggerInfo"];
|
|
66
66
|
export type TriggerHistoryPage = components["schemas"]["PageResult_for_String_and_Trigger"];
|
|
67
|
-
export type WithdrawalHistoryPage = components["schemas"]["
|
|
67
|
+
export type WithdrawalHistoryPage = components["schemas"]["PageResult_for_String_and_WithdrawalInfo"];
|
|
68
68
|
export type FeeTierConfig = components["schemas"]["FeeTierConfig"];
|
|
69
69
|
export type FeeTierId = components["schemas"]["FeeTierId"];
|
|
70
70
|
export type TokenStats = components["schemas"]["TokenStats"];
|
|
@@ -73,9 +73,7 @@ export type AccountFeeTierPage = components["schemas"]["PageResult_for_uint32_an
|
|
|
73
73
|
export type AdminInfo = components["schemas"]["AdminInfo"];
|
|
74
74
|
export type GetAccountVolumeQuery = components["schemas"]["GetAccountVolumeQuery"];
|
|
75
75
|
export type AccountVolumeInfo = components["schemas"]["AccountVolumeInfo"];
|
|
76
|
-
export type
|
|
77
|
-
export type PreviousMarketPrice = components["schemas"]["PreviousMarketPrice"];
|
|
78
|
-
export type WithdrawalInfo = components["schemas"]["Withdrawal"];
|
|
76
|
+
export type WithdrawalInfo = components["schemas"]["WithdrawalInfo"];
|
|
79
77
|
/**
|
|
80
78
|
* Configuration options for the Nord client
|
|
81
79
|
*/
|
|
@@ -196,6 +194,7 @@ export declare enum WebSocketMessageType {
|
|
|
196
194
|
DeltaUpdate = "delta",
|
|
197
195
|
AccountUpdate = "account"
|
|
198
196
|
}
|
|
197
|
+
export type CandleResolution = "1" | "5" | "15" | "30" | "60" | "1D" | "1W" | "1M";
|
|
199
198
|
/**
|
|
200
199
|
* WebSocket trade update message
|
|
201
200
|
*/
|
|
@@ -242,13 +241,23 @@ export interface WebSocketAccountUpdate {
|
|
|
242
241
|
}>;
|
|
243
242
|
balances: Record<string, number>;
|
|
244
243
|
}
|
|
244
|
+
export interface WebSocketCandleUpdate {
|
|
245
|
+
res: CandleResolution;
|
|
246
|
+
mid: number;
|
|
247
|
+
t: number;
|
|
248
|
+
o: number;
|
|
249
|
+
h: number;
|
|
250
|
+
l: number;
|
|
251
|
+
c: number;
|
|
252
|
+
v: number;
|
|
253
|
+
}
|
|
245
254
|
export type WebSocketMessage = {
|
|
246
255
|
trades: WebSocketTradeUpdate;
|
|
247
256
|
} | {
|
|
248
257
|
delta: WebSocketDeltaUpdate;
|
|
249
258
|
} | {
|
|
250
259
|
account: WebSocketAccountUpdate;
|
|
251
|
-
};
|
|
260
|
+
} | WebSocketCandleUpdate;
|
|
252
261
|
export interface SPLTokenInfo {
|
|
253
262
|
mint: string;
|
|
254
263
|
precision: number;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as proto from "./gen/nord_pb";
|
|
2
|
+
import Decimal from "decimal.js";
|
|
3
|
+
import { toScaledU64 } from "./utils";
|
|
4
|
+
export var Side;
|
|
5
|
+
(function (Side) {
|
|
6
|
+
Side["Ask"] = "ask";
|
|
7
|
+
Side["Bid"] = "bid";
|
|
8
|
+
})(Side || (Side = {}));
|
|
9
|
+
export var FillMode;
|
|
10
|
+
(function (FillMode) {
|
|
11
|
+
FillMode[FillMode["Limit"] = 0] = "Limit";
|
|
12
|
+
FillMode[FillMode["PostOnly"] = 1] = "PostOnly";
|
|
13
|
+
FillMode[FillMode["ImmediateOrCancel"] = 2] = "ImmediateOrCancel";
|
|
14
|
+
FillMode[FillMode["FillOrKill"] = 3] = "FillOrKill";
|
|
15
|
+
})(FillMode || (FillMode = {}));
|
|
16
|
+
export var TriggerKind;
|
|
17
|
+
(function (TriggerKind) {
|
|
18
|
+
TriggerKind[TriggerKind["StopLoss"] = 0] = "StopLoss";
|
|
19
|
+
TriggerKind[TriggerKind["TakeProfit"] = 1] = "TakeProfit";
|
|
20
|
+
})(TriggerKind || (TriggerKind = {}));
|
|
21
|
+
export var TriggerStatus;
|
|
22
|
+
(function (TriggerStatus) {
|
|
23
|
+
TriggerStatus[TriggerStatus["Active"] = 0] = "Active";
|
|
24
|
+
TriggerStatus[TriggerStatus["Success"] = 1] = "Success";
|
|
25
|
+
TriggerStatus[TriggerStatus["Cancel"] = 2] = "Cancel";
|
|
26
|
+
TriggerStatus[TriggerStatus["Remove"] = 4] = "Remove";
|
|
27
|
+
})(TriggerStatus || (TriggerStatus = {}));
|
|
28
|
+
/**
|
|
29
|
+
* Converts a `FillMode` enum to its corresponding protobuf representation.
|
|
30
|
+
*
|
|
31
|
+
* @param x - The fill mode to convert.
|
|
32
|
+
* @returns The corresponding protobuf fill mode.
|
|
33
|
+
* @throws Will throw an error if provided with an invalid fill mode.
|
|
34
|
+
*/
|
|
35
|
+
export function fillModeToProtoFillMode(x) {
|
|
36
|
+
if (x === FillMode.Limit)
|
|
37
|
+
return proto.FillMode.LIMIT;
|
|
38
|
+
if (x === FillMode.PostOnly)
|
|
39
|
+
return proto.FillMode.POST_ONLY;
|
|
40
|
+
if (x === FillMode.ImmediateOrCancel) {
|
|
41
|
+
return proto.FillMode.IMMEDIATE_OR_CANCEL;
|
|
42
|
+
}
|
|
43
|
+
if (x === FillMode.FillOrKill)
|
|
44
|
+
return proto.FillMode.FILL_OR_KILL;
|
|
45
|
+
throw new Error("Invalid fill mode");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* WebSocket message types
|
|
49
|
+
*/
|
|
50
|
+
export var WebSocketMessageType;
|
|
51
|
+
(function (WebSocketMessageType) {
|
|
52
|
+
WebSocketMessageType["TradeUpdate"] = "trades";
|
|
53
|
+
WebSocketMessageType["DeltaUpdate"] = "delta";
|
|
54
|
+
WebSocketMessageType["AccountUpdate"] = "account";
|
|
55
|
+
})(WebSocketMessageType || (WebSocketMessageType = {}));
|
|
56
|
+
// Positive decimal price and size.
|
|
57
|
+
// Example:
|
|
58
|
+
// ```
|
|
59
|
+
// const limit = new QuoteSize(new Decimal(114000), new Decimal(0.00035)),
|
|
60
|
+
//```
|
|
61
|
+
// Gives 40$ USD limit.
|
|
62
|
+
//
|
|
63
|
+
// Given price is same(or very close) to the market price,
|
|
64
|
+
// limit gives size tick as close as possible to settlemnt size tick.
|
|
65
|
+
// If you want to get smaller tick on client (on server it will not change),
|
|
66
|
+
// do `new QuoteSize(new Decimal(114000/2), new Decimal(0.00070))`.
|
|
67
|
+
// It will be 40$ limit, but may help if BTC suddently moves fast.
|
|
68
|
+
export class QuoteSize {
|
|
69
|
+
price;
|
|
70
|
+
size;
|
|
71
|
+
/// Input can be only positive values.
|
|
72
|
+
constructor(quotePrice, quoteSize) {
|
|
73
|
+
const p = new Decimal(quotePrice);
|
|
74
|
+
const s = new Decimal(quoteSize);
|
|
75
|
+
if (!p.isPositive() || !s.isPositive()) {
|
|
76
|
+
throw new Error("quotePrice and quoteSize must be positive");
|
|
77
|
+
}
|
|
78
|
+
this.price = p;
|
|
79
|
+
this.size = s;
|
|
80
|
+
}
|
|
81
|
+
// USD value of limit, use for debug
|
|
82
|
+
value() {
|
|
83
|
+
return this.price.mul(this.size);
|
|
84
|
+
}
|
|
85
|
+
// Converts to wire format to be send to server, scaling price and size according to market decimals.
|
|
86
|
+
toWire(marketPriceDecimals, marketSizeDecimals) {
|
|
87
|
+
return {
|
|
88
|
+
price: toScaledU64(this.price, marketPriceDecimals),
|
|
89
|
+
size: toScaledU64(this.size, marketSizeDecimals),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Decimal } from "decimal.js";
|
|
2
|
+
import { sizeDelimitedPeek } from "@bufbuild/protobuf/wire";
|
|
3
|
+
import { fromBinary } from "@bufbuild/protobuf";
|
|
4
|
+
import { ethers } from "ethers";
|
|
5
|
+
import fetch from "node-fetch";
|
|
6
|
+
import { Keypair } from "@solana/web3.js";
|
|
7
|
+
import bs58 from "bs58";
|
|
8
|
+
import * as solana from "@solana/web3.js";
|
|
9
|
+
import { Buffer } from "buffer";
|
|
10
|
+
export const SESSION_TTL = 60n * 60n * 24n * 30n;
|
|
11
|
+
export const ZERO_DECIMAL = new Decimal(0);
|
|
12
|
+
export const MAX_BUFFER_LEN = 10_000;
|
|
13
|
+
// Max size of data returned from Nord endpoints
|
|
14
|
+
const MAX_PAYLOAD_SIZE = 100 * 1024; // 100 kB
|
|
15
|
+
export function panic(message) {
|
|
16
|
+
throw new Error(message);
|
|
17
|
+
}
|
|
18
|
+
export function isRfc3339(s) {
|
|
19
|
+
const REGEX = /^((?:(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}(?:\.\d+)?))(Z|[\+-]\d{2}:\d{2})?)$/;
|
|
20
|
+
return REGEX.test(s);
|
|
21
|
+
}
|
|
22
|
+
export function assert(predicate, message) {
|
|
23
|
+
if (!predicate)
|
|
24
|
+
panic(message ?? "Assertion violated");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Extracts value out of optional if it's defined, or throws error if it's not
|
|
28
|
+
* @param value Optional value to unwrap
|
|
29
|
+
* @param message Error message
|
|
30
|
+
* @returns Unwrapped value
|
|
31
|
+
*/
|
|
32
|
+
export function optExpect(value, message) {
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
throw new Error(message);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
/** Behaves same as `node-fetch/fetch` but throws if response is a failure
|
|
38
|
+
*
|
|
39
|
+
* @param url Request HTTP URL
|
|
40
|
+
* @param init Request parameters
|
|
41
|
+
* @returns Raw response if fetch succeeded
|
|
42
|
+
* @throws If response wasn't Ok
|
|
43
|
+
*/
|
|
44
|
+
export async function checkedFetch(url, init) {
|
|
45
|
+
const resp = await fetch(url, init);
|
|
46
|
+
assert(resp.ok, `Request failed with ${resp.status}: ${resp.statusText}`);
|
|
47
|
+
return resp;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Constructs wallet signing function, usable with `NordUser` type
|
|
51
|
+
*
|
|
52
|
+
* @param walletKey Either raw signing key as bytes array or hex string prefixed with `"0x"`
|
|
53
|
+
* @returns Async function which accepts arbitrary message, generates its digets,
|
|
54
|
+
* then signs it with provided user wallet key and returns signature
|
|
55
|
+
* as hex string prefixed with `"0x"`
|
|
56
|
+
*/
|
|
57
|
+
export function makeWalletSignFn(walletKey) {
|
|
58
|
+
const signingKey = new ethers.SigningKey(walletKey);
|
|
59
|
+
return async (message) => signingKey.sign(ethers.hashMessage(message)).serialized;
|
|
60
|
+
}
|
|
61
|
+
// Returned numbers do fit into specified bits range, or error is thrown.
|
|
62
|
+
function makeToScaledBigUint(params) {
|
|
63
|
+
const Dec = Decimal.clone({
|
|
64
|
+
precision: params.precision,
|
|
65
|
+
toExpPos: params.exponent,
|
|
66
|
+
toExpNeg: -params.exponent,
|
|
67
|
+
});
|
|
68
|
+
const Ten = new Dec(10);
|
|
69
|
+
const Max = new Dec(((1n << BigInt(params.bits)) - 1n).toString());
|
|
70
|
+
return (x, decimals) => {
|
|
71
|
+
const dec = new Dec(x);
|
|
72
|
+
if (dec.isZero()) {
|
|
73
|
+
return 0n;
|
|
74
|
+
}
|
|
75
|
+
if (dec.isNeg()) {
|
|
76
|
+
throw new Error(`Number is negative`);
|
|
77
|
+
}
|
|
78
|
+
const scaled = Ten.pow(decimals).mul(dec).truncated();
|
|
79
|
+
if (scaled.isZero()) {
|
|
80
|
+
throw new Error(`Precision loss when converting ${dec} to scaled integer`);
|
|
81
|
+
}
|
|
82
|
+
if (scaled.greaterThan(Max)) {
|
|
83
|
+
throw new Error(`Integer is out of range: ${scaled} exceeds limit ${Max}`);
|
|
84
|
+
}
|
|
85
|
+
return BigInt(scaled.toString());
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Converts decimal value into rescaled 64-bit unsigned integer
|
|
90
|
+
* by scaling it up by specified number of decimal digits.
|
|
91
|
+
*
|
|
92
|
+
* Ensures that number won't accidentally become zero
|
|
93
|
+
* or exceed U64's value range
|
|
94
|
+
*
|
|
95
|
+
* @param x Decimal value to rescale
|
|
96
|
+
* @param decimals Number of decimal digits
|
|
97
|
+
* @returns Rescaled unsigned integer
|
|
98
|
+
*/
|
|
99
|
+
export const toScaledU64 = makeToScaledBigUint({
|
|
100
|
+
bits: 64,
|
|
101
|
+
precision: 20,
|
|
102
|
+
exponent: 28,
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Converts decimal value into rescaled 128-bit unsigned integer
|
|
106
|
+
* by scaling it up by specified number of decimal digits.
|
|
107
|
+
*
|
|
108
|
+
* Ensures that number won't accidentally become zero
|
|
109
|
+
* or exceed U128's value range
|
|
110
|
+
*
|
|
111
|
+
* @param x Decimal value to rescale
|
|
112
|
+
* @param decimals Number of decimal digits
|
|
113
|
+
* @returns Rescaled unsigned integer
|
|
114
|
+
*/
|
|
115
|
+
export const toScaledU128 = makeToScaledBigUint({
|
|
116
|
+
bits: 128,
|
|
117
|
+
precision: 40,
|
|
118
|
+
exponent: 56,
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* Decodes any protobuf message from a length-delimited format,
|
|
122
|
+
* i.e. prefixed with its length encoded as varint
|
|
123
|
+
*
|
|
124
|
+
* @param bytes Byte array with encoded message
|
|
125
|
+
* @param schema Message schema for decoding
|
|
126
|
+
* @returns Decoded message
|
|
127
|
+
*/
|
|
128
|
+
export function decodeLengthDelimited(bytes, schema) {
|
|
129
|
+
// use sizeDelimitedPeek to extract the message length and offset
|
|
130
|
+
const peekResult = sizeDelimitedPeek(bytes);
|
|
131
|
+
if (peekResult.size === null || peekResult.offset === null) {
|
|
132
|
+
throw new Error("Failed to parse size-delimited message");
|
|
133
|
+
}
|
|
134
|
+
if (peekResult.size > MAX_PAYLOAD_SIZE) {
|
|
135
|
+
throw new Error(`Encoded message size (${peekResult.size} bytes) is greater than max payload size (${MAX_PAYLOAD_SIZE} bytes).`);
|
|
136
|
+
}
|
|
137
|
+
if (peekResult.offset + peekResult.size > bytes.length) {
|
|
138
|
+
throw new Error(`Encoded message size (${peekResult.size} bytes) is greater than remaining buffer size (${bytes.length - peekResult.offset} bytes).`);
|
|
139
|
+
}
|
|
140
|
+
// decode the message using the offset and size from peek
|
|
141
|
+
return fromBinary(schema, bytes.slice(peekResult.offset, peekResult.offset + peekResult.size));
|
|
142
|
+
}
|
|
143
|
+
export function decodeHex(value) {
|
|
144
|
+
const hex = value.startsWith("0x") ? value.slice(2) : value;
|
|
145
|
+
return Uint8Array.from(Buffer.from(hex, "hex"));
|
|
146
|
+
}
|
|
147
|
+
export function findMarket(markets, marketId) {
|
|
148
|
+
if (marketId < 0 || markets.length - 1 < marketId) {
|
|
149
|
+
throw new Error(`The market with marketId=${marketId} not found`);
|
|
150
|
+
}
|
|
151
|
+
return markets[marketId];
|
|
152
|
+
}
|
|
153
|
+
export function findToken(tokens, tokenId) {
|
|
154
|
+
if (tokenId < 0 || tokens.length - 1 < tokenId) {
|
|
155
|
+
throw new Error(`The token with tokenId=${tokenId} not found`);
|
|
156
|
+
}
|
|
157
|
+
return tokens[tokenId];
|
|
158
|
+
}
|
|
159
|
+
export function keypairFromPrivateKey(privateKey) {
|
|
160
|
+
if (typeof privateKey === "string") {
|
|
161
|
+
if (!privateKey.startsWith("0x")) {
|
|
162
|
+
return Keypair.fromSecretKey(bs58.decode(privateKey));
|
|
163
|
+
}
|
|
164
|
+
const hex = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
|
|
165
|
+
const bytes = new Uint8Array(hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
166
|
+
return Keypair.fromSecretKey(bytes);
|
|
167
|
+
}
|
|
168
|
+
return Keypair.fromSecretKey(privateKey);
|
|
169
|
+
}
|
|
170
|
+
export async function signAdminPayload({ payload, user, signTransaction, }) {
|
|
171
|
+
const tx = new solana.Transaction({
|
|
172
|
+
blockhash: bs58.encode(new Uint8Array(32)),
|
|
173
|
+
lastValidBlockHeight: 0,
|
|
174
|
+
feePayer: user,
|
|
175
|
+
});
|
|
176
|
+
tx.add(new solana.TransactionInstruction({
|
|
177
|
+
keys: [],
|
|
178
|
+
programId: user,
|
|
179
|
+
data: Buffer.from(payload),
|
|
180
|
+
}));
|
|
181
|
+
const signedTx = await signTransaction(tx);
|
|
182
|
+
const sig = signedTx.signatures[0];
|
|
183
|
+
assert(sig !== undefined, //.
|
|
184
|
+
"signed transaction must have a signature");
|
|
185
|
+
assert(sig.signature !== null, "signature must be non-null; check your signTransaction function");
|
|
186
|
+
assert(sig.signature.length === 64, //.
|
|
187
|
+
"signature must be 64 bytes");
|
|
188
|
+
assert(sig.publicKey.equals(user), `signature is for ${sig.publicKey}, expected ${user}`);
|
|
189
|
+
return sig.signature;
|
|
190
|
+
}
|
|
191
|
+
export async function signUserPayload({ payload, signMessage, }) {
|
|
192
|
+
return await signMessage(new TextEncoder().encode(payload.toHex()));
|
|
193
|
+
}
|