@sentico-labs/sdk 0.1.0-preview.1

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.
@@ -0,0 +1,88 @@
1
+ import type { ApiResponse, HexAddress, MarketCandlesOptions, MarketOrderbookOptions, MarketTradesOptions, NonceReservationOptions, NormalizedSenticoreSdkConfig, PrivateWsTokenOptions, RateLimitInfo, RequestOptions, SenticoreSdkConfig, WsSessionTokenResponse } from "./types.js";
2
+ export declare class SenticoreHttpTransport {
3
+ readonly config: NormalizedSenticoreSdkConfig;
4
+ constructor(config: SenticoreSdkConfig | NormalizedSenticoreSdkConfig);
5
+ request<T = unknown>(method: string, baseUrl: string, path: string, options?: RequestOptions & {
6
+ query?: Record<string, string | number | boolean | undefined>;
7
+ body?: unknown;
8
+ content?: BodyInit;
9
+ }): Promise<ApiResponse<T>>;
10
+ private headers;
11
+ private toApiResponse;
12
+ private url;
13
+ private sleepBeforeRetry;
14
+ }
15
+ export declare class SenticorePublicHttpClient {
16
+ private readonly transport;
17
+ constructor(transport: SenticoreHttpTransport);
18
+ listMarkets(): Promise<ApiResponse<unknown>>;
19
+ getMarket(marketId: number): Promise<ApiResponse<unknown>>;
20
+ getMarketOracle(marketId: number): Promise<ApiResponse<unknown>>;
21
+ getMarketSnapshot(marketId: number, options?: {
22
+ depth?: number;
23
+ limit?: number;
24
+ }): Promise<ApiResponse<unknown>>;
25
+ getMarketOrderbook(marketId: number, options?: MarketOrderbookOptions): Promise<ApiResponse<unknown>>;
26
+ listMarketTrades(marketId: number, options?: MarketTradesOptions): Promise<ApiResponse<unknown>>;
27
+ getMarketCandles(marketId: number, options?: MarketCandlesOptions): Promise<ApiResponse<unknown>>;
28
+ getMarketTicker(marketId: number): Promise<ApiResponse<unknown>>;
29
+ listTickers(): Promise<ApiResponse<unknown>>;
30
+ getTime(): Promise<ApiResponse<unknown>>;
31
+ getStatus(): Promise<ApiResponse<unknown>>;
32
+ private get;
33
+ }
34
+ export declare class SenticoreTradingHttpClient {
35
+ private readonly transport;
36
+ constructor(transport: SenticoreHttpTransport);
37
+ getAccount(account: HexAddress, options?: RequestOptions): Promise<ApiResponse<unknown>>;
38
+ getAccountBalances(account: HexAddress, options?: RequestOptions): Promise<ApiResponse<unknown>>;
39
+ getAccountPositions(account: HexAddress, options?: RequestOptions): Promise<ApiResponse<unknown>>;
40
+ getAccountOrders(account: HexAddress, options?: RequestOptions & {
41
+ limit?: number;
42
+ }): Promise<ApiResponse<unknown>>;
43
+ getAccountFills(account: HexAddress, options?: RequestOptions & {
44
+ limit?: number;
45
+ }): Promise<ApiResponse<unknown>>;
46
+ issuePrivateWsToken(account: HexAddress, options?: RequestOptions & PrivateWsTokenOptions): Promise<ApiResponse<WsSessionTokenResponse>>;
47
+ reserveNonce(account: HexAddress, options?: NonceReservationOptions): Promise<ApiResponse<unknown>>;
48
+ getActionSigningHash(payload: unknown, options?: RequestOptions): Promise<ApiResponse<unknown>>;
49
+ submitSignedAction(payload: unknown, options?: RequestOptions): Promise<ApiResponse<unknown>>;
50
+ placeOrder(order: unknown, options?: RequestOptions): Promise<ApiResponse<unknown>>;
51
+ submitOrderBatch(batch: unknown, options?: RequestOptions): Promise<ApiResponse<unknown>>;
52
+ cancelOrder(orderId: string, options?: RequestOptions): Promise<ApiResponse<unknown>>;
53
+ private get;
54
+ private post;
55
+ }
56
+ export declare class SenticoreRawHttpClient {
57
+ private readonly transport;
58
+ constructor(transport: SenticoreHttpTransport);
59
+ request<T = unknown>(method: string, path: string, options?: RequestOptions & {
60
+ baseUrl?: string;
61
+ query?: Record<string, string | number | boolean | undefined>;
62
+ body?: unknown;
63
+ content?: BodyInit;
64
+ }): Promise<ApiResponse<T>>;
65
+ }
66
+ export declare class SenticoreHttpClient {
67
+ private readonly publicClient;
68
+ private readonly tradingClient;
69
+ constructor(config: SenticoreSdkConfig);
70
+ getMarkets(): Promise<unknown>;
71
+ getMarket(marketId: number): Promise<unknown>;
72
+ getTickers(): Promise<unknown>;
73
+ getMarketTicker(marketId: number): Promise<unknown>;
74
+ getMarketOrderbook(marketId: number, options?: MarketOrderbookOptions): Promise<unknown>;
75
+ getMarketTrades(marketId: number, options?: MarketTradesOptions): Promise<unknown>;
76
+ getMarketCandles(marketId: number, options?: MarketCandlesOptions): Promise<unknown>;
77
+ getAccount(account: HexAddress, bearerToken: string): Promise<unknown>;
78
+ getAccountBalances(account: HexAddress, bearerToken: string): Promise<unknown>;
79
+ getAccountPositions(account: HexAddress, bearerToken: string): Promise<unknown>;
80
+ issuePrivateWsToken(account: HexAddress, bearerToken: string, options?: PrivateWsTokenOptions): Promise<WsSessionTokenResponse>;
81
+ submitSignedAction(payload: unknown, bearerToken?: string): Promise<unknown>;
82
+ getActionSigningHash(payload: unknown, bearerToken?: string): Promise<unknown>;
83
+ reserveNonce(account: HexAddress, payload: {
84
+ count?: number;
85
+ ttlMs?: number;
86
+ }, bearerToken?: string): Promise<unknown>;
87
+ }
88
+ export declare function rateLimitFromHeaders(headers: Headers): RateLimitInfo | undefined;
@@ -0,0 +1,313 @@
1
+ import { normalizeConfig } from "./config.js";
2
+ import { apiErrorFromStatus, requestIdFromHeaders, SenticoreApiError, TransportError } from "./errors.js";
3
+ export class SenticoreHttpTransport {
4
+ config;
5
+ constructor(config) {
6
+ this.config = normalizeConfig(config);
7
+ }
8
+ async request(method, baseUrl, path, options = {}) {
9
+ const methodUpper = method.toUpperCase();
10
+ const retry = options.retry ?? (methodUpper === "GET" || Boolean(options.idempotencyKey));
11
+ const attempts = retry ? this.config.maxRetries + 1 : 1;
12
+ const url = this.url(baseUrl, path, options.query);
13
+ let lastError;
14
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
15
+ const controller = new AbortController();
16
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
17
+ try {
18
+ const response = await this.config.fetch(url, {
19
+ method: methodUpper,
20
+ headers: this.headers(options),
21
+ body: options.content ?? (options.body === undefined ? undefined : JSON.stringify(options.body)),
22
+ signal: controller.signal
23
+ });
24
+ clearTimeout(timeout);
25
+ if (shouldRetry(response.status) && attempt + 1 < attempts) {
26
+ await this.sleepBeforeRetry(attempt, response);
27
+ continue;
28
+ }
29
+ return await this.toApiResponse(response);
30
+ }
31
+ catch (error) {
32
+ clearTimeout(timeout);
33
+ if (error instanceof SenticoreApiError) {
34
+ throw error;
35
+ }
36
+ lastError = error;
37
+ if (attempt + 1 >= attempts) {
38
+ throw new TransportError(error instanceof Error ? error.message : "request failed", error);
39
+ }
40
+ await this.sleepBeforeRetry(attempt);
41
+ }
42
+ }
43
+ throw new TransportError("request failed", lastError);
44
+ }
45
+ headers(options) {
46
+ const headers = new Headers({
47
+ accept: "application/json",
48
+ "user-agent": this.config.userAgent,
49
+ ...this.config.headers,
50
+ ...options.headers
51
+ });
52
+ const token = options.bearerToken ?? this.config.bearerToken;
53
+ if (token) {
54
+ headers.set("authorization", token.startsWith("Bearer ") ? token : `Bearer ${token}`);
55
+ }
56
+ if (options.idempotencyKey) {
57
+ headers.set("idempotency-key", options.idempotencyKey);
58
+ }
59
+ return headers;
60
+ }
61
+ async toApiResponse(response) {
62
+ const data = (await decodeBody(response));
63
+ if (!response.ok) {
64
+ throw apiErrorFromStatus(response, data);
65
+ }
66
+ return {
67
+ data,
68
+ status: response.status,
69
+ headers: response.headers,
70
+ requestId: requestIdFromHeaders(response.headers),
71
+ rateLimit: rateLimitFromHeaders(response.headers)
72
+ };
73
+ }
74
+ url(baseUrl, path, query) {
75
+ const url = new URL(path, `${baseUrl}/`);
76
+ for (const [key, value] of Object.entries(query ?? {})) {
77
+ if (value !== undefined)
78
+ url.searchParams.set(key, String(value));
79
+ }
80
+ return url.toString();
81
+ }
82
+ async sleepBeforeRetry(attempt, response) {
83
+ const retryAfter = response?.headers.get("retry-after");
84
+ const parsedRetryAfter = retryAfter ? Number(retryAfter) : undefined;
85
+ const ms = parsedRetryAfter && Number.isFinite(parsedRetryAfter)
86
+ ? parsedRetryAfter * 1000
87
+ : this.config.retryBackoffMs * 2 ** attempt;
88
+ if (ms > 0)
89
+ await new Promise((resolve) => setTimeout(resolve, ms));
90
+ }
91
+ }
92
+ export class SenticorePublicHttpClient {
93
+ transport;
94
+ constructor(transport) {
95
+ this.transport = transport;
96
+ }
97
+ listMarkets() {
98
+ return this.get("/api/v1/public/markets");
99
+ }
100
+ getMarket(marketId) {
101
+ return this.get(`/api/v1/public/markets/${marketId}`);
102
+ }
103
+ getMarketOracle(marketId) {
104
+ return this.get(`/api/v1/public/markets/${marketId}/oracle`);
105
+ }
106
+ getMarketSnapshot(marketId, options = {}) {
107
+ return this.get(`/api/v1/public/markets/${marketId}/snapshot`, options);
108
+ }
109
+ getMarketOrderbook(marketId, options = {}) {
110
+ return this.get(`/api/v1/public/markets/${marketId}/orderbook`, {
111
+ book: options.book,
112
+ depth: options.depth
113
+ });
114
+ }
115
+ listMarketTrades(marketId, options = {}) {
116
+ return this.get(`/api/v1/public/markets/${marketId}/trades`, {
117
+ limit: options.limit
118
+ });
119
+ }
120
+ getMarketCandles(marketId, options = {}) {
121
+ return this.get(`/api/v1/public/markets/${marketId}/candles`, {
122
+ tf: options.tf,
123
+ beforeTs: options.beforeTs,
124
+ countBack: options.countBack
125
+ });
126
+ }
127
+ getMarketTicker(marketId) {
128
+ return this.get(`/api/v1/public/markets/${marketId}/ticker`);
129
+ }
130
+ listTickers() {
131
+ return this.get("/api/v1/public/tickers");
132
+ }
133
+ getTime() {
134
+ return this.get("/api/v1/public/time");
135
+ }
136
+ getStatus() {
137
+ return this.get("/api/v1/public/status");
138
+ }
139
+ get(path, query) {
140
+ return this.transport.request("GET", this.transport.config.publicHttpBaseUrl, path, { query });
141
+ }
142
+ }
143
+ export class SenticoreTradingHttpClient {
144
+ transport;
145
+ constructor(transport) {
146
+ this.transport = transport;
147
+ }
148
+ getAccount(account, options = {}) {
149
+ return this.get(`/api/v1/accounts/${account}`, options);
150
+ }
151
+ getAccountBalances(account, options = {}) {
152
+ return this.get(`/api/v1/accounts/${account}/balances`, options);
153
+ }
154
+ getAccountPositions(account, options = {}) {
155
+ return this.get(`/api/v1/accounts/${account}/positions`, options);
156
+ }
157
+ getAccountOrders(account, options = {}) {
158
+ return this.get(`/api/v1/accounts/${account}/orders`, options, { limit: options.limit });
159
+ }
160
+ getAccountFills(account, options = {}) {
161
+ return this.get(`/api/v1/accounts/${account}/fills`, options, { limit: options.limit });
162
+ }
163
+ issuePrivateWsToken(account, options = {}) {
164
+ return this.post("/api/v1/ws/token", {
165
+ account,
166
+ ttlMs: options.ttlMs,
167
+ clientId: options.clientId
168
+ }, options);
169
+ }
170
+ reserveNonce(account, options = {}) {
171
+ return this.post(`/api/v1/trading/nonce-reservations/${account}`, {
172
+ count: options.count ?? 1,
173
+ ttlMs: options.ttlMs ?? 30_000
174
+ }, options);
175
+ }
176
+ getActionSigningHash(payload, options = {}) {
177
+ return this.post("/api/v1/trading/actions/hash", payload, options);
178
+ }
179
+ submitSignedAction(payload, options = {}) {
180
+ return this.post("/api/v1/trading/actions", payload, options);
181
+ }
182
+ placeOrder(order, options = {}) {
183
+ return this.post("/api/v1/trading/orders", order, options);
184
+ }
185
+ submitOrderBatch(batch, options = {}) {
186
+ return this.post("/api/v1/trading/orders/batch", batch, options);
187
+ }
188
+ cancelOrder(orderId, options = {}) {
189
+ return this.transport.request("DELETE", this.transport.config.tradingHttpBaseUrl, `/api/v1/trading/orders/${orderId}`, options);
190
+ }
191
+ get(path, options, query) {
192
+ return this.transport.request("GET", this.transport.config.tradingHttpBaseUrl, path, {
193
+ ...options,
194
+ query
195
+ });
196
+ }
197
+ post(path, body, options) {
198
+ return this.transport.request("POST", this.transport.config.tradingHttpBaseUrl, path, {
199
+ ...options,
200
+ body: dropUndefined(body),
201
+ retry: options.retry ?? Boolean(options.idempotencyKey)
202
+ });
203
+ }
204
+ }
205
+ export class SenticoreRawHttpClient {
206
+ transport;
207
+ constructor(transport) {
208
+ this.transport = transport;
209
+ }
210
+ request(method, path, options = {}) {
211
+ return this.transport.request(method, options.baseUrl ?? this.transport.config.tradingHttpBaseUrl, path, options);
212
+ }
213
+ }
214
+ export class SenticoreHttpClient {
215
+ publicClient;
216
+ tradingClient;
217
+ constructor(config) {
218
+ const transport = new SenticoreHttpTransport(config);
219
+ this.publicClient = new SenticorePublicHttpClient(transport);
220
+ this.tradingClient = new SenticoreTradingHttpClient(transport);
221
+ }
222
+ async getMarkets() {
223
+ return (await this.publicClient.listMarkets()).data;
224
+ }
225
+ async getMarket(marketId) {
226
+ return (await this.publicClient.getMarket(marketId)).data;
227
+ }
228
+ async getTickers() {
229
+ return (await this.publicClient.listTickers()).data;
230
+ }
231
+ async getMarketTicker(marketId) {
232
+ return (await this.publicClient.getMarketTicker(marketId)).data;
233
+ }
234
+ async getMarketOrderbook(marketId, options) {
235
+ return (await this.publicClient.getMarketOrderbook(marketId, options)).data;
236
+ }
237
+ async getMarketTrades(marketId, options) {
238
+ return (await this.publicClient.listMarketTrades(marketId, options)).data;
239
+ }
240
+ async getMarketCandles(marketId, options) {
241
+ return (await this.publicClient.getMarketCandles(marketId, options)).data;
242
+ }
243
+ async getAccount(account, bearerToken) {
244
+ return (await this.tradingClient.getAccount(account, { bearerToken })).data;
245
+ }
246
+ async getAccountBalances(account, bearerToken) {
247
+ return (await this.tradingClient.getAccountBalances(account, { bearerToken })).data;
248
+ }
249
+ async getAccountPositions(account, bearerToken) {
250
+ return (await this.tradingClient.getAccountPositions(account, { bearerToken })).data;
251
+ }
252
+ async issuePrivateWsToken(account, bearerToken, options) {
253
+ return (await this.tradingClient.issuePrivateWsToken(account, {
254
+ ...options,
255
+ bearerToken
256
+ })).data;
257
+ }
258
+ async submitSignedAction(payload, bearerToken) {
259
+ return (await this.tradingClient.submitSignedAction(payload, { bearerToken })).data;
260
+ }
261
+ async getActionSigningHash(payload, bearerToken) {
262
+ return (await this.tradingClient.getActionSigningHash(payload, { bearerToken })).data;
263
+ }
264
+ async reserveNonce(account, payload, bearerToken) {
265
+ return (await this.tradingClient.reserveNonce(account, {
266
+ count: payload.count,
267
+ ttlMs: payload.ttlMs,
268
+ bearerToken
269
+ })).data;
270
+ }
271
+ }
272
+ export function rateLimitFromHeaders(headers) {
273
+ const raw = {};
274
+ for (const [key, value] of headers.entries()) {
275
+ const lower = key.toLowerCase();
276
+ if (lower.startsWith("x-ratelimit") || lower === "retry-after") {
277
+ raw[lower] = value;
278
+ }
279
+ }
280
+ if (Object.keys(raw).length === 0)
281
+ return undefined;
282
+ return {
283
+ limit: raw["x-ratelimit-limit"],
284
+ remaining: raw["x-ratelimit-remaining"],
285
+ reset: raw["x-ratelimit-reset"],
286
+ retryAfter: raw["retry-after"],
287
+ raw
288
+ };
289
+ }
290
+ async function decodeBody(response) {
291
+ const text = await response.text();
292
+ if (!text)
293
+ return null;
294
+ try {
295
+ return JSON.parse(text);
296
+ }
297
+ catch {
298
+ return text;
299
+ }
300
+ }
301
+ function shouldRetry(status) {
302
+ return [408, 429, 500, 502, 503, 504].includes(status);
303
+ }
304
+ function dropUndefined(value) {
305
+ if (Array.isArray(value))
306
+ return value.map(dropUndefined);
307
+ if (value && typeof value === "object") {
308
+ return Object.fromEntries(Object.entries(value)
309
+ .filter(([, entry]) => entry !== undefined)
310
+ .map(([key, entry]) => [key, dropUndefined(entry)]));
311
+ }
312
+ return value;
313
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./client.js";
2
+ export * from "./config.js";
3
+ export * from "./errors.js";
4
+ export * from "./http.js";
5
+ export * from "./mm.js";
6
+ export * from "./signing.js";
7
+ export * from "./types.js";
8
+ export * from "./ws.js";
@@ -0,0 +1,8 @@
1
+ export * from "./client.js";
2
+ export * from "./config.js";
3
+ export * from "./errors.js";
4
+ export * from "./http.js";
5
+ export * from "./mm.js";
6
+ export * from "./signing.js";
7
+ export * from "./types.js";
8
+ export * from "./ws.js";
@@ -0,0 +1,32 @@
1
+ import type { ApiResponse, MmActionBatch, MmActionBatchResponse, RequestOptions, SignedAction } from "./types.js";
2
+ import { SenticoreHttpTransport } from "./http.js";
3
+ export declare class SenticoreOrderEntryClient {
4
+ private readonly transport;
5
+ constructor(transport: SenticoreHttpTransport);
6
+ buildBatch(actions: SignedAction[], idempotencyKey?: string): MmActionBatch;
7
+ encodeBatch(batch: MmActionBatch): Uint8Array;
8
+ submitActions(actions: SignedAction[], options?: RequestOptions & {
9
+ orderEntryApiKey?: string;
10
+ idempotencyKey?: string;
11
+ /** @deprecated Use orderEntryApiKey. */
12
+ mmApiKey?: string;
13
+ responseMode?: "summary" | "detailed" | "full" | "compact" | "conflated";
14
+ }): Promise<ApiResponse<MmActionBatchResponse>>;
15
+ submitBatch(batch: MmActionBatch, options?: RequestOptions & {
16
+ orderEntryApiKey?: string;
17
+ /** @deprecated Use orderEntryApiKey. */
18
+ mmApiKey?: string;
19
+ responseMode?: "summary" | "detailed" | "full" | "compact" | "conflated";
20
+ }): Promise<ApiResponse<MmActionBatchResponse>>;
21
+ submitEncoded(payload: BodyInit, options?: RequestOptions & {
22
+ orderEntryApiKey?: string;
23
+ /** @deprecated Use orderEntryApiKey. */
24
+ mmApiKey?: string;
25
+ responseMode?: "summary" | "detailed" | "full" | "compact" | "conflated";
26
+ }): Promise<ApiResponse<MmActionBatchResponse>>;
27
+ }
28
+ /** @deprecated Use SenticoreOrderEntryClient. */
29
+ export declare class SenticoreMarketMakerClient extends SenticoreOrderEntryClient {
30
+ }
31
+ export declare function encodeMmActionBatch(batch: MmActionBatch): Uint8Array;
32
+ export declare const encodeOrderEntryActionBatch: typeof encodeMmActionBatch;
package/dist/src/mm.js ADDED
@@ -0,0 +1,74 @@
1
+ const ORDER_ENTRY_BATCH_CONTENT_TYPE = "application/x-senticore-order-entry-batch";
2
+ const ORDER_ENTRY_BATCH_RESPONSE_CONTENT_TYPE = "application/x-senticore-order-entry-batch-response";
3
+ const LEGACY_MM_BATCH_RESPONSE_CONTENT_TYPE = "application/x-senticore-mm-batch-response";
4
+ export class SenticoreOrderEntryClient {
5
+ transport;
6
+ constructor(transport) {
7
+ this.transport = transport;
8
+ }
9
+ buildBatch(actions, idempotencyKey) {
10
+ return {
11
+ version: 1,
12
+ actions,
13
+ ...(idempotencyKey ? { idempotencyKey } : {})
14
+ };
15
+ }
16
+ encodeBatch(batch) {
17
+ return encodeMmActionBatch(batch);
18
+ }
19
+ submitActions(actions, options = {}) {
20
+ return this.submitBatch(this.buildBatch(actions, options.idempotencyKey), options);
21
+ }
22
+ async submitBatch(batch, options = {}) {
23
+ return this.submitEncoded(encodeMmActionBatch(batch), {
24
+ ...options,
25
+ idempotencyKey: options.idempotencyKey ?? batch.idempotencyKey
26
+ });
27
+ }
28
+ submitEncoded(payload, options = {}) {
29
+ const headers = {
30
+ "content-type": ORDER_ENTRY_BATCH_CONTENT_TYPE,
31
+ accept: `${ORDER_ENTRY_BATCH_RESPONSE_CONTENT_TYPE}, ${LEGACY_MM_BATCH_RESPONSE_CONTENT_TYPE}, application/json`,
32
+ ...options.headers
33
+ };
34
+ const orderEntryApiKey = options.orderEntryApiKey ?? options.mmApiKey ?? this.transport.config.orderEntryApiKey;
35
+ if (orderEntryApiKey)
36
+ headers["x-senticore-order-entry-key"] = orderEntryApiKey;
37
+ if (options.responseMode)
38
+ headers["x-senticore-response-mode"] = options.responseMode;
39
+ return this.transport.request("POST", this.transport.config.orderEntryHttpBaseUrl, this.transport.config.orderEntryBinaryPath, {
40
+ ...options,
41
+ headers,
42
+ content: payload,
43
+ retry: options.retry ?? Boolean(options.idempotencyKey)
44
+ });
45
+ }
46
+ }
47
+ /** @deprecated Use SenticoreOrderEntryClient. */
48
+ export class SenticoreMarketMakerClient extends SenticoreOrderEntryClient {
49
+ }
50
+ export function encodeMmActionBatch(batch) {
51
+ const normalized = {
52
+ version: batch.version,
53
+ actions: batch.actions.map(normalizeSignedAction),
54
+ ...(batch.idempotencyKey ? { idempotencyKey: batch.idempotencyKey } : {})
55
+ };
56
+ return new TextEncoder().encode(JSON.stringify(normalized));
57
+ }
58
+ export const encodeOrderEntryActionBatch = encodeMmActionBatch;
59
+ function normalizeSignedAction(action) {
60
+ return {
61
+ payload: action.payload,
62
+ signature: {
63
+ scheme: action.signature.scheme,
64
+ bytes: normalizeSignatureBytes(action.signature.bytes)
65
+ }
66
+ };
67
+ }
68
+ function normalizeSignatureBytes(bytes) {
69
+ if (typeof bytes === "string")
70
+ return bytes;
71
+ if (bytes instanceof Uint8Array)
72
+ return [...bytes];
73
+ return bytes;
74
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Local (client-side) canonical encoding and signing-hash computation for
3
+ * Senticore trading actions.
4
+ *
5
+ * This removes the `POST /api/v1/trading/actions/hash` roundtrip from the
6
+ * order hot path: the canonical bytes and the blake3 signing hash are
7
+ * computed locally, signed locally, and submitted in a single HTTP call.
8
+ *
9
+ * CANONICAL ENCODING RULES (must match `senticore-types` byte-for-byte):
10
+ * - JSON with NO whitespace.
11
+ * - Object keys in Rust struct DECLARATION order (NOT alphabetical). The
12
+ * `canonicalPayload` echoed by the server /hash endpoint is alphabetically
13
+ * sorted for display — hashing that JSON gives a DIFFERENT hash. Always
14
+ * hash the declaration-order encoding produced here.
15
+ * - Field names in snake_case (the internal wire names).
16
+ * - Optional fields are ALWAYS serialized (as null when absent).
17
+ * - Account ids are 0x-prefixed lowercase hex (20 bytes), order ids
18
+ * 0x-prefixed lowercase hex (32 bytes).
19
+ * - Enums: Side "Bid"/"Ask"; Book "YES"/"NO"; time_in_force / stp_mode /
20
+ * trigger_direction / conditional_kind in snake_case.
21
+ * - The outcome variant is serialized with its INTERNAL tag "PlaceOrder"
22
+ * (the public alias "OutcomePlaceOrder" is an input-side name only).
23
+ * - hash = blake3("SENTICORE/ACTION_PAYLOAD/v1" || canonical_bytes).
24
+ *
25
+ * Golden vectors pinning compatibility with the backend live in
26
+ * `tests/signing.test.ts` and in `senticore-types::tests::signing_hash_golden_vectors`.
27
+ */
28
+ export declare const ACTION_PAYLOAD_DOMAIN_V1 = "SENTICORE/ACTION_PAYLOAD/v1";
29
+ export type UintLike = number | bigint;
30
+ export type LocalSide = "Bid" | "Ask";
31
+ export type LocalBook = "YES" | "NO";
32
+ export type LocalTimeInForce = "gtc" | "ioc" | "fok" | "post_only";
33
+ export type LocalStpMode = "cancel_maker" | "cancel_taker" | "reject" | "skip_self";
34
+ export interface LocalOrderFlags {
35
+ stpMode?: LocalStpMode | null;
36
+ timeInForce?: LocalTimeInForce | null;
37
+ isMarket?: boolean;
38
+ reduceOnly?: boolean;
39
+ expiresAt?: UintLike | null;
40
+ }
41
+ export interface LocalSpotPlaceOrder extends LocalOrderFlags {
42
+ kind: "SpotPlaceOrder";
43
+ market: UintLike;
44
+ side: LocalSide;
45
+ price: UintLike;
46
+ qty: UintLike;
47
+ }
48
+ export interface LocalOutcomePlaceOrder extends LocalOrderFlags {
49
+ kind: "OutcomePlaceOrder";
50
+ market: UintLike;
51
+ book: LocalBook;
52
+ side: LocalSide;
53
+ price: UintLike;
54
+ qty: UintLike;
55
+ }
56
+ export interface LocalCancel {
57
+ kind: "Cancel";
58
+ orderId: string;
59
+ }
60
+ export interface LocalAmendOrder {
61
+ kind: "AmendOrder";
62
+ orderId: string;
63
+ /** AmendOrder is quantity-only by design (keeps time priority). */
64
+ newQty: UintLike;
65
+ }
66
+ export interface LocalSpotQuoteReplaceLeg extends LocalOrderFlags {
67
+ cancelOrderId?: string | null;
68
+ side: LocalSide;
69
+ price: UintLike;
70
+ qty: UintLike;
71
+ }
72
+ export interface LocalSpotQuoteReplace {
73
+ kind: "SpotQuoteReplace";
74
+ market: UintLike;
75
+ legs: LocalSpotQuoteReplaceLeg[];
76
+ }
77
+ export interface LocalQuoteReplaceLeg extends LocalSpotQuoteReplaceLeg {
78
+ book: LocalBook;
79
+ }
80
+ export interface LocalQuoteReplace {
81
+ kind: "QuoteReplace";
82
+ market: UintLike;
83
+ legs: LocalQuoteReplaceLeg[];
84
+ }
85
+ export type LocalAction = LocalSpotPlaceOrder | LocalOutcomePlaceOrder | LocalCancel | LocalAmendOrder | LocalSpotQuoteReplace | LocalQuoteReplace;
86
+ export interface LocalActionPayload {
87
+ account: string;
88
+ nonce: UintLike;
89
+ nonceReservationId?: string | null;
90
+ ts: UintLike;
91
+ action: LocalAction;
92
+ }
93
+ /**
94
+ * Canonical declaration-order JSON for an action payload — the EXACT bytes
95
+ * the backend hashes and persists.
96
+ */
97
+ export declare function canonicalActionPayloadJson(payload: LocalActionPayload): string;
98
+ /** blake3(domain || canonical_bytes) as raw bytes. */
99
+ export declare function actionSigningHash(payload: LocalActionPayload): Uint8Array;
100
+ /** blake3 signing hash as 0x-prefixed lowercase hex. */
101
+ export declare function actionSigningHashHex(payload: LocalActionPayload): `0x${string}`;