@0xmonaco/core 0.8.5 → 0.8.7-develop.34bd452

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.
@@ -28,7 +28,7 @@
28
28
  * const backendAuth = await authAPI.authenticateBackend(secretKey);
29
29
  * ```
30
30
  */
31
- import type { AuthAPI, AuthState, BackendAuthResponse, ChallengeResponse, TokenRefreshResponse } from "@0xmonaco/types";
31
+ import type { AuthAPI, AuthState, BackendAuthResponse, ChallengeResponse, SessionCredentials, SessionRefreshResponse } from "@0xmonaco/types";
32
32
  import type { Chain, WalletClient } from "viem";
33
33
  import { BaseAPI } from "../base";
34
34
  export declare class AuthAPIImpl extends BaseAPI implements AuthAPI {
@@ -51,20 +51,24 @@ export declare class AuthAPIImpl extends BaseAPI implements AuthAPI {
51
51
  * Complete authentication flow for frontend applications.
52
52
  *
53
53
  * This method handles the entire authentication process:
54
- * 1. Creates a challenge
55
- * 2. Signs the challenge message
56
- * 3. Verifies the signature and returns JWT tokens
54
+ * 1. Generates a fresh ed25519 session keypair locally
55
+ * 2. Creates a challenge that commits to the session public key
56
+ * 3. Signs the challenge message with the wallet
57
+ * 4. Verifies the signature, registering the session public key
58
+ *
59
+ * The returned {@link AuthState} carries the session keypair; sign subsequent
60
+ * requests with the private key (the wallet is not used again until the
61
+ * session expires).
57
62
  *
58
63
  * @param clientId - Client ID of the application
59
- * @returns Promise resolving to the verification response with JWT tokens
64
+ * @returns Promise resolving to the authentication state (with the session keypair)
60
65
  * @throws {APIError} When authentication fails
61
66
  * @throws {InvalidConfigError} When wallet account is not available
62
67
  *
63
68
  * @example
64
69
  * ```typescript
65
- * // Complete authentication in one call
66
70
  * const authResult = await authAPI.authenticate("my-app-client-id");
67
- * console.log(`Access token: ${authResult.access_token}`);
71
+ * console.log(`Session public key: ${authResult.sessionPublicKey}`);
68
72
  * console.log(`User ID: ${authResult.user.id}`);
69
73
  * ```
70
74
  */
@@ -109,7 +113,7 @@ export declare class AuthAPIImpl extends BaseAPI implements AuthAPI {
109
113
  * console.log(`Nonce: ${challenge.nonce}`);
110
114
  * ```
111
115
  */
112
- createChallenge(address: string, clientId: string): Promise<ChallengeResponse>;
116
+ createChallenge(address: string, clientId: string, sessionPublicKey: string): Promise<ChallengeResponse>;
113
117
  /**
114
118
  * Verifies a signature for frontend authentication.
115
119
  *
@@ -117,33 +121,24 @@ export declare class AuthAPIImpl extends BaseAPI implements AuthAPI {
117
121
  * authenticated API access. This is the second step in the authentication flow.
118
122
  *
119
123
  * @param address - Wallet address of the user
120
- * @param signature - Signature of the challenge message
124
+ * @param signature - Wallet signature of the challenge message
121
125
  * @param nonce - Nonce from the challenge response
122
126
  * @param clientId - Client ID of the application
123
- * @returns Promise resolving to the verification response with JWT tokens
127
+ * @param session - The locally-generated session keypair (hex-encoded)
128
+ * @returns Promise resolving to the authentication state (with the session keypair)
124
129
  * @throws {APIError} When signature verification fails
125
130
  *
126
131
  * @example
127
132
  * ```typescript
128
- * // First create a challenge
129
- * const challenge = await authAPI.createChallenge(address, clientId);
130
- *
131
- * // User signs the challenge message with their wallet
133
+ * const keypair = generateSessionKeypair();
134
+ * const session = { publicKey: publicKeyHex(keypair), privateKey: privateKeyHex(keypair) };
135
+ * const challenge = await authAPI.createChallenge(address, clientId, session.publicKey);
132
136
  * const signature = await wallet.signMessage(challenge.message);
133
- *
134
- * // Verify the signature and get tokens
135
- * const authResult = await authAPI.verifySignature(
136
- * address,
137
- * signature,
138
- * challenge.nonce,
139
- * clientId
140
- * );
141
- *
142
- * console.log(`Access token: ${authResult.accessToken}`);
143
- * console.log(`User ID: ${authResult.user.id}`);
137
+ * const authResult = await authAPI.verifySignature(address, signature, challenge.nonce, clientId, session);
138
+ * console.log(`Session public key: ${authResult.sessionPublicKey}`);
144
139
  * ```
145
140
  */
146
- verifySignature(address: string, signature: string, nonce: string, clientId: string): Promise<AuthState>;
141
+ verifySignature(address: string, signature: string, nonce: string, clientId: string, session: SessionCredentials): Promise<AuthState>;
147
142
  /**
148
143
  * Authenticates a backend service using a secret key.
149
144
  *
@@ -163,44 +158,36 @@ export declare class AuthAPIImpl extends BaseAPI implements AuthAPI {
163
158
  */
164
159
  authenticateBackend(secretKey: string): Promise<BackendAuthResponse>;
165
160
  /**
166
- * Refreshes an access token using a refresh token.
161
+ * Extends the current session's expiry.
167
162
  *
168
- * Obtains a new access token using a valid refresh token. This is useful for
169
- * maintaining long-term authentication without requiring the user to sign
170
- * a new challenge.
163
+ * The request is signed with the active session key (set via
164
+ * {@link setSessionKeypair}); the server bumps the session's `expires_at`.
165
+ * No new credential is issued — the same keypair keeps working.
171
166
  *
172
- * @param refreshToken - The refresh token to use
173
- * @returns Promise resolving to new access and refresh tokens
174
- * @throws {APIError} When token refresh fails
167
+ * @returns Promise resolving to the new expiry
168
+ * @throws {APIError} When refresh fails (e.g. session expired or revoked)
175
169
  *
176
170
  * @example
177
171
  * ```typescript
178
- * const newTokens = await authAPI.refreshToken(refreshToken);
179
- * console.log(`New access token: ${newTokens.accessToken}`);
180
- * console.log(`Expires at: ${new Date(newTokens.expiresAt * 1000)}`);
172
+ * const { expiresAt } = await authAPI.refreshSession();
173
+ * console.log(`Session now expires at: ${new Date(expiresAt * 1000)}`);
181
174
  * ```
182
175
  */
183
- refreshToken(refreshToken: string): Promise<TokenRefreshResponse>;
176
+ refreshSession(): Promise<SessionRefreshResponse>;
184
177
  /**
185
- * Revokes the current session's refresh token.
186
- *
187
- * Invalidates the refresh token associated with the current access token,
188
- * preventing it from being used to obtain new access tokens. This is useful
189
- * for logout functionality or when a token has been compromised.
178
+ * Revokes the current session.
190
179
  *
191
- * The server identifies the token to revoke from the access token in the
192
- * Authorization header no request body is needed.
180
+ * The request is signed with the active session key; the server deletes the
181
+ * matching session row. Used for logout or when a key may be compromised.
193
182
  *
194
- * @returns Promise resolving when the token is revoked
195
- * @throws {APIError} When token revocation fails
183
+ * @returns Promise resolving when the session is revoked
184
+ * @throws {APIError} When revocation fails
196
185
  *
197
186
  * @example
198
187
  * ```typescript
199
- * // After authentication
200
- * const authResult = await authAPI.authenticate(clientId);
201
- * await authAPI.revokeToken();
202
- * console.log("Token revoked successfully");
188
+ * await authAPI.revokeSession();
189
+ * console.log("Session revoked successfully");
203
190
  * ```
204
191
  */
205
- revokeToken(): Promise<void>;
192
+ revokeSession(): Promise<void>;
206
193
  }
@@ -28,6 +28,7 @@
28
28
  * const backendAuth = await authAPI.authenticateBackend(secretKey);
29
29
  * ```
30
30
  */
31
+ import { generateSessionKeypair, privateKeyHex, publicKeyHex } from "../../crypto/session";
31
32
  import { InvalidConfigError } from "../../errors";
32
33
  import { BaseAPI } from "../base";
33
34
  export class AuthAPIImpl extends BaseAPI {
@@ -56,20 +57,24 @@ export class AuthAPIImpl extends BaseAPI {
56
57
  * Complete authentication flow for frontend applications.
57
58
  *
58
59
  * This method handles the entire authentication process:
59
- * 1. Creates a challenge
60
- * 2. Signs the challenge message
61
- * 3. Verifies the signature and returns JWT tokens
60
+ * 1. Generates a fresh ed25519 session keypair locally
61
+ * 2. Creates a challenge that commits to the session public key
62
+ * 3. Signs the challenge message with the wallet
63
+ * 4. Verifies the signature, registering the session public key
64
+ *
65
+ * The returned {@link AuthState} carries the session keypair; sign subsequent
66
+ * requests with the private key (the wallet is not used again until the
67
+ * session expires).
62
68
  *
63
69
  * @param clientId - Client ID of the application
64
- * @returns Promise resolving to the verification response with JWT tokens
70
+ * @returns Promise resolving to the authentication state (with the session keypair)
65
71
  * @throws {APIError} When authentication fails
66
72
  * @throws {InvalidConfigError} When wallet account is not available
67
73
  *
68
74
  * @example
69
75
  * ```typescript
70
- * // Complete authentication in one call
71
76
  * const authResult = await authAPI.authenticate("my-app-client-id");
72
- * console.log(`Access token: ${authResult.access_token}`);
77
+ * console.log(`Session public key: ${authResult.sessionPublicKey}`);
73
78
  * console.log(`User ID: ${authResult.user.id}`);
74
79
  * ```
75
80
  */
@@ -81,12 +86,19 @@ export class AuthAPIImpl extends BaseAPI {
81
86
  if (!account) {
82
87
  throw new InvalidConfigError("No account available in wallet client", "account");
83
88
  }
84
- // 1. Create challenge
85
- const challenge = await this.createChallenge(account.address, clientId);
86
- // 2. Sign the challenge message
89
+ // 1. Generate a fresh session keypair locally — the server never sees the
90
+ // private key.
91
+ const keypair = generateSessionKeypair();
92
+ const session = {
93
+ publicKey: publicKeyHex(keypair),
94
+ privateKey: privateKeyHex(keypair),
95
+ };
96
+ // 2. Create challenge (binds the session public key into the signed message)
97
+ const challenge = await this.createChallenge(account.address, clientId, session.publicKey);
98
+ // 3. Sign the challenge message with the wallet
87
99
  const signature = await this.signChallenge(challenge.message);
88
- // 3. Verify signature and get tokens
89
- return await this.verifySignature(account.address, signature, challenge.nonce, clientId);
100
+ // 4. Verify signature, registering the session public key
101
+ return await this.verifySignature(account.address, signature, challenge.nonce, clientId, session);
90
102
  }
91
103
  /**
92
104
  * Signs a challenge message using the wallet client.
@@ -141,13 +153,14 @@ export class AuthAPIImpl extends BaseAPI {
141
153
  * console.log(`Nonce: ${challenge.nonce}`);
142
154
  * ```
143
155
  */
144
- async createChallenge(address, clientId) {
156
+ async createChallenge(address, clientId, sessionPublicKey) {
145
157
  const responseBody = await this.makePublicRequest("/api/v1/auth/challenge", {
146
158
  method: "POST",
147
159
  body: JSON.stringify({
148
160
  address,
149
161
  client_id: clientId,
150
162
  chain_id: this.chain.id,
163
+ session_public_key: sessionPublicKey,
151
164
  }),
152
165
  });
153
166
  return {
@@ -163,33 +176,24 @@ export class AuthAPIImpl extends BaseAPI {
163
176
  * authenticated API access. This is the second step in the authentication flow.
164
177
  *
165
178
  * @param address - Wallet address of the user
166
- * @param signature - Signature of the challenge message
179
+ * @param signature - Wallet signature of the challenge message
167
180
  * @param nonce - Nonce from the challenge response
168
181
  * @param clientId - Client ID of the application
169
- * @returns Promise resolving to the verification response with JWT tokens
182
+ * @param session - The locally-generated session keypair (hex-encoded)
183
+ * @returns Promise resolving to the authentication state (with the session keypair)
170
184
  * @throws {APIError} When signature verification fails
171
185
  *
172
186
  * @example
173
187
  * ```typescript
174
- * // First create a challenge
175
- * const challenge = await authAPI.createChallenge(address, clientId);
176
- *
177
- * // User signs the challenge message with their wallet
188
+ * const keypair = generateSessionKeypair();
189
+ * const session = { publicKey: publicKeyHex(keypair), privateKey: privateKeyHex(keypair) };
190
+ * const challenge = await authAPI.createChallenge(address, clientId, session.publicKey);
178
191
  * const signature = await wallet.signMessage(challenge.message);
179
- *
180
- * // Verify the signature and get tokens
181
- * const authResult = await authAPI.verifySignature(
182
- * address,
183
- * signature,
184
- * challenge.nonce,
185
- * clientId
186
- * );
187
- *
188
- * console.log(`Access token: ${authResult.accessToken}`);
189
- * console.log(`User ID: ${authResult.user.id}`);
192
+ * const authResult = await authAPI.verifySignature(address, signature, challenge.nonce, clientId, session);
193
+ * console.log(`Session public key: ${authResult.sessionPublicKey}`);
190
194
  * ```
191
195
  */
192
- async verifySignature(address, signature, nonce, clientId) {
196
+ async verifySignature(address, signature, nonce, clientId, session) {
193
197
  const responseBody = await this.makePublicRequest("/api/v1/auth/verify", {
194
198
  method: "POST",
195
199
  body: JSON.stringify({
@@ -198,11 +202,12 @@ export class AuthAPIImpl extends BaseAPI {
198
202
  nonce,
199
203
  client_id: clientId,
200
204
  chain_id: this.chain.id,
205
+ session_public_key: session.publicKey,
201
206
  }),
202
207
  });
203
208
  return {
204
- accessToken: responseBody.access_token,
205
- refreshToken: responseBody.refresh_token,
209
+ sessionPublicKey: session.publicKey,
210
+ sessionPrivateKey: session.privateKey,
206
211
  expiresAt: responseBody.expires_at,
207
212
  user: {
208
213
  id: responseBody.user.id,
@@ -247,57 +252,45 @@ export class AuthAPIImpl extends BaseAPI {
247
252
  };
248
253
  }
249
254
  /**
250
- * Refreshes an access token using a refresh token.
255
+ * Extends the current session's expiry.
251
256
  *
252
- * Obtains a new access token using a valid refresh token. This is useful for
253
- * maintaining long-term authentication without requiring the user to sign
254
- * a new challenge.
257
+ * The request is signed with the active session key (set via
258
+ * {@link setSessionKeypair}); the server bumps the session's `expires_at`.
259
+ * No new credential is issued — the same keypair keeps working.
255
260
  *
256
- * @param refreshToken - The refresh token to use
257
- * @returns Promise resolving to new access and refresh tokens
258
- * @throws {APIError} When token refresh fails
261
+ * @returns Promise resolving to the new expiry
262
+ * @throws {APIError} When refresh fails (e.g. session expired or revoked)
259
263
  *
260
264
  * @example
261
265
  * ```typescript
262
- * const newTokens = await authAPI.refreshToken(refreshToken);
263
- * console.log(`New access token: ${newTokens.accessToken}`);
264
- * console.log(`Expires at: ${new Date(newTokens.expiresAt * 1000)}`);
266
+ * const { expiresAt } = await authAPI.refreshSession();
267
+ * console.log(`Session now expires at: ${new Date(expiresAt * 1000)}`);
265
268
  * ```
266
269
  */
267
- async refreshToken(refreshToken) {
268
- const responseBody = await this.makePublicRequest("/api/v1/auth/refresh", {
270
+ async refreshSession() {
271
+ const responseBody = await this.makeAuthenticatedRequest("/api/v1/auth/refresh", {
269
272
  method: "POST",
270
- body: JSON.stringify({
271
- refresh_token: refreshToken,
272
- }),
273
273
  });
274
274
  return {
275
- accessToken: responseBody.access_token,
276
275
  expiresAt: responseBody.expires_at,
277
276
  };
278
277
  }
279
278
  /**
280
- * Revokes the current session's refresh token.
281
- *
282
- * Invalidates the refresh token associated with the current access token,
283
- * preventing it from being used to obtain new access tokens. This is useful
284
- * for logout functionality or when a token has been compromised.
279
+ * Revokes the current session.
285
280
  *
286
- * The server identifies the token to revoke from the access token in the
287
- * Authorization header no request body is needed.
281
+ * The request is signed with the active session key; the server deletes the
282
+ * matching session row. Used for logout or when a key may be compromised.
288
283
  *
289
- * @returns Promise resolving when the token is revoked
290
- * @throws {APIError} When token revocation fails
284
+ * @returns Promise resolving when the session is revoked
285
+ * @throws {APIError} When revocation fails
291
286
  *
292
287
  * @example
293
288
  * ```typescript
294
- * // After authentication
295
- * const authResult = await authAPI.authenticate(clientId);
296
- * await authAPI.revokeToken();
297
- * console.log("Token revoked successfully");
289
+ * await authAPI.revokeSession();
290
+ * console.log("Session revoked successfully");
298
291
  * ```
299
292
  */
300
- async revokeToken() {
293
+ async revokeSession() {
301
294
  await this.makeAuthenticatedRequest("/api/v1/auth/revoke", {
302
295
  method: "POST",
303
296
  });
@@ -20,13 +20,16 @@
20
20
  * }
21
21
  * ```
22
22
  */
23
+ import type { SessionCredentials } from "@0xmonaco/types";
24
+ import { type SessionKeypair } from "../crypto/session";
23
25
  export interface RetryOptions {
24
26
  maxRetries?: number;
25
27
  baseDelayMs?: number;
26
28
  }
27
29
  export declare abstract class BaseAPI {
28
30
  protected readonly apiUrl: string;
29
- protected accessToken?: string;
31
+ /** Active session keypair (raw bytes) used to sign authenticated requests. */
32
+ protected sessionKeypair?: SessionKeypair;
30
33
  protected retryOptions: Required<RetryOptions>;
31
34
  /**
32
35
  * Creates a new BaseAPI instance.
@@ -36,17 +39,11 @@ export declare abstract class BaseAPI {
36
39
  */
37
40
  constructor(apiUrl: string, retryOptions?: RetryOptions);
38
41
  /**
39
- * Set the access token for authenticated requests.
42
+ * Set (or clear) the session keypair used to sign authenticated requests.
40
43
  *
41
- * @param token - JWT access token
44
+ * @param credentials - Hex-encoded session keypair, or `undefined` to clear.
42
45
  */
43
- setAccessToken(token: string): void;
44
- /**
45
- * Get the current access token.
46
- *
47
- * @returns The current access token or undefined if not set
48
- */
49
- protected getAccessToken(): string | undefined;
46
+ setSessionKeypair(credentials: SessionCredentials | undefined): void;
50
47
  /**
51
48
  * Parse request body for error logging
52
49
  *
@@ -93,6 +90,15 @@ export declare abstract class BaseAPI {
93
90
  * ```
94
91
  */
95
92
  protected makeAuthenticatedRequest<T>(endpoint: string, options?: RequestInit): Promise<T>;
93
+ /**
94
+ * Compute the per-request ed25519 signature headers.
95
+ *
96
+ * The signed payload is `METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX`,
97
+ * matching the server (`handlers::auth::compose_signing_string`). `endpoint`
98
+ * is the path-with-query exactly as sent; the body hash covers the raw bytes
99
+ * so a request can't be replayed with a different body.
100
+ */
101
+ private buildSignatureHeaders;
96
102
  /**
97
103
  * Makes an unauthenticated API request.
98
104
  *
package/dist/api/base.js CHANGED
@@ -20,11 +20,14 @@
20
20
  * }
21
21
  * ```
22
22
  */
23
+ import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
23
24
  import { StatusCodes } from "http-status-codes";
25
+ import { composeSigningString, keypairFromHex, sha256Hex, signMessage } from "../crypto/session";
24
26
  import { APIError } from "../errors";
25
27
  export class BaseAPI {
26
28
  apiUrl;
27
- accessToken;
29
+ /** Active session keypair (raw bytes) used to sign authenticated requests. */
30
+ sessionKeypair;
28
31
  retryOptions;
29
32
  /**
30
33
  * Creates a new BaseAPI instance.
@@ -40,20 +43,12 @@ export class BaseAPI {
40
43
  };
41
44
  }
42
45
  /**
43
- * Set the access token for authenticated requests.
46
+ * Set (or clear) the session keypair used to sign authenticated requests.
44
47
  *
45
- * @param token - JWT access token
48
+ * @param credentials - Hex-encoded session keypair, or `undefined` to clear.
46
49
  */
47
- setAccessToken(token) {
48
- this.accessToken = token;
49
- }
50
- /**
51
- * Get the current access token.
52
- *
53
- * @returns The current access token or undefined if not set
54
- */
55
- getAccessToken() {
56
- return this.accessToken;
50
+ setSessionKeypair(credentials) {
51
+ this.sessionKeypair = credentials ? keypairFromHex(credentials.publicKey, credentials.privateKey) : undefined;
57
52
  }
58
53
  /**
59
54
  * Parse request body for error logging
@@ -229,23 +224,51 @@ export class BaseAPI {
229
224
  * ```
230
225
  */
231
226
  async makeAuthenticatedRequest(endpoint, options = {}) {
232
- if (!this.accessToken) {
233
- throw new APIError("Access token not set. Call setAccessToken() first.", {
227
+ if (!this.sessionKeypair) {
228
+ throw new APIError("Session keypair not set. Authenticate (login) first.", {
234
229
  endpoint: `${this.apiUrl}${endpoint}`,
235
230
  statusCode: StatusCodes.UNAUTHORIZED,
236
231
  });
237
232
  }
238
233
  const url = `${this.apiUrl}${endpoint}`;
239
234
  const requestBody = this.parseRequestBody(options.body);
235
+ const signatureHeaders = this.buildSignatureHeaders(endpoint, options);
240
236
  return this.executeRequest(url, endpoint, {
241
237
  ...options,
242
238
  headers: {
243
239
  "Content-Type": "application/json",
244
- Authorization: `Bearer ${this.accessToken}`,
240
+ ...signatureHeaders,
245
241
  ...options.headers,
246
242
  },
247
243
  }, requestBody);
248
244
  }
245
+ /**
246
+ * Compute the per-request ed25519 signature headers.
247
+ *
248
+ * The signed payload is `METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX`,
249
+ * matching the server (`handlers::auth::compose_signing_string`). `endpoint`
250
+ * is the path-with-query exactly as sent; the body hash covers the raw bytes
251
+ * so a request can't be replayed with a different body.
252
+ */
253
+ buildSignatureHeaders(endpoint, options) {
254
+ if (!this.sessionKeypair) {
255
+ throw new APIError("Session keypair not set. Authenticate (login) first.", {
256
+ endpoint: `${this.apiUrl}${endpoint}`,
257
+ statusCode: StatusCodes.UNAUTHORIZED,
258
+ });
259
+ }
260
+ const method = (options.method ?? "GET").toUpperCase();
261
+ const timestampMs = Date.now();
262
+ const bodyBytes = typeof options.body === "string" ? utf8ToBytes(options.body) : new Uint8Array(0);
263
+ const bodyHash = sha256Hex(bodyBytes);
264
+ const signingString = composeSigningString(method, endpoint, timestampMs, bodyHash);
265
+ const signature = signMessage(this.sessionKeypair.privateKey, signingString);
266
+ return {
267
+ "X-Monaco-PublicKey": bytesToHex(this.sessionKeypair.publicKey),
268
+ "X-Monaco-Timestamp": String(timestampMs),
269
+ "X-Monaco-Signature": signature,
270
+ };
271
+ }
249
272
  /**
250
273
  * Makes an unauthenticated API request.
251
274
  *
@@ -12,7 +12,8 @@ export class OrderbookAPIImpl extends BaseAPI {
12
12
  params.set("denomination", denomination.toLowerCase());
13
13
  const response = await this.makePublicRequest(`/api/v1/orderbook/${encodeURIComponent(tradingPairId)}?${params.toString()}`);
14
14
  return {
15
- tradingPairId: response.symbol,
15
+ // `trading_pair_id` is the pair UUID; `symbol` is the display string.
16
+ tradingPairId: response.trading_pair_id,
16
17
  tradingMode: response.trading_mode,
17
18
  bids: response.data.bids.map((level) => ({
18
19
  price: level.price,
@@ -493,8 +493,11 @@ export class TradingAPIImpl extends BaseAPI {
493
493
  * ```
494
494
  */
495
495
  async getOrder(orderId) {
496
- return await this.makeAuthenticatedRequest(perpRoutes.orders.get(orderId), {
497
- method: "GET",
498
- });
496
+ // The REST endpoint returns the order fields at the top level (a flat
497
+ // `Order`), not wrapped in `{ order, status }`. Wrap it here so the SDK
498
+ // honors its declared `GetOrderResponse` contract. A non-2xx response
499
+ // throws in `makeAuthenticatedRequest`, so reaching here means success.
500
+ const order = await this.makeAuthenticatedRequest(perpRoutes.orders.get(orderId), { method: "GET" });
501
+ return { order, status: "SUCCESS" };
499
502
  }
500
503
  }
@@ -1,9 +1,9 @@
1
- import type { ConditionalOrderEvent, Interval, OHLCVEvent, OrderbookEvent, OrderbookQuotationMode, OrderEvent, TradeEvent, TradingMode, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
1
+ import type { ConditionalOrderEvent, Interval, OHLCVEvent, OrderbookEvent, OrderbookQuotationMode, OrderEvent, SessionCredentials, TradeEvent, TradingMode, UserBalanceEvent, UserMovementEvent, WebSocketStatus } from "@0xmonaco/types";
2
2
  export type StatusHandler = (status: WebSocketStatus) => void;
3
3
  export type MessageHandler<T> = (data: T) => void;
4
4
  export interface MonacoWebSocketOptions {
5
- /** JWT access token for authenticated channels (orders) */
6
- token?: string;
5
+ /** Session keypair for authenticated channels (orders, balances, etc.) */
6
+ session?: SessionCredentials;
7
7
  /** Enable auto-reconnect on disconnect (default: true) */
8
8
  autoReconnect?: boolean;
9
9
  /** Maximum reconnection attempts (default: 5) */
@@ -20,8 +20,8 @@ export interface MonacoWebSocket {
20
20
  isConnected: () => boolean;
21
21
  /** Get current websocket connection status */
22
22
  getStatus: () => WebSocketStatus;
23
- /** Update the access token (for re-auth) */
24
- setToken: (token: string) => void;
23
+ /** Set (or clear) the session keypair used to authenticate the connection */
24
+ setSessionKeypair: (credentials: SessionCredentials | undefined) => void;
25
25
  /** Subscribe to order events (requires authentication) */
26
26
  orders: (tradingPairId: string, tradingMode: TradingMode, handler: MessageHandler<OrderEvent>) => () => void;
27
27
  /** Subscribe to orderbook events (public) */
@@ -1,3 +1,5 @@
1
+ import { hexToBytes } from "@noble/hashes/utils";
2
+ import { composeWsSigningString, signMessage } from "../../crypto/session";
1
3
  import { ALL_MAGNITUDES } from "../../utils";
2
4
  import { keysToCamelCase } from "./utils";
3
5
  // Connection constants
@@ -107,7 +109,7 @@ function parseConditionalOrderEvent(rawData) {
107
109
  */
108
110
  export function createMonacoWebSocket(baseUrl, options = {}) {
109
111
  let ws = null;
110
- let token = options.token;
112
+ let session = options.session;
111
113
  let reconnectAttempts = 0;
112
114
  let reconnectTimer = null;
113
115
  let heartbeatTimer = null;
@@ -127,11 +129,24 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
127
129
  return "disconnected";
128
130
  }
129
131
  };
130
- const getUrl = () => {
131
- const url = new URL(baseUrl);
132
- if (token)
133
- url.searchParams.set("token", token);
134
- return url.toString();
132
+ const getUrl = () => baseUrl;
133
+ /**
134
+ * Send the signed auth handshake as the first message after the socket
135
+ * opens. The client signs `WS-AUTH\n<pubkey>\n<ts>` with the session private
136
+ * key; the server binds the connection to the session. No-op if no session
137
+ * is set (public channels work unauthenticated).
138
+ */
139
+ const sendAuthenticate = () => {
140
+ if (!session || ws?.readyState !== WebSocket.OPEN)
141
+ return;
142
+ const timestamp = Date.now();
143
+ const signature = signMessage(hexToBytes(session.privateKey), composeWsSigningString(session.publicKey, timestamp));
144
+ ws.send(JSON.stringify({
145
+ type: "Authenticate",
146
+ public_key: session.publicKey,
147
+ timestamp,
148
+ signature,
149
+ }));
135
150
  };
136
151
  const stopHeartbeat = () => {
137
152
  if (heartbeatTimer) {
@@ -217,6 +232,9 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
217
232
  }
218
233
  reconnectAttempts = 0;
219
234
  startHeartbeat();
235
+ // Authenticate before resubscribing so user-specific channels
236
+ // (orders, balances, …) are authorized when the Subscribe arrives.
237
+ sendAuthenticate();
220
238
  resubscribeAll();
221
239
  options.onStatusChange?.("connected");
222
240
  resolve();
@@ -324,7 +342,9 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
324
342
  const data = rawData;
325
343
  const orderbookData = data.data;
326
344
  const event = {
327
- tradingPairId: data.symbol,
345
+ // `trading_pair_id` is the pair UUID; `symbol` is the display string
346
+ // (e.g. "AMZN/USDC"). `tradingPairId` must carry the UUID.
347
+ tradingPairId: data.trading_pair_id,
328
348
  tradingMode: data.trading_mode,
329
349
  bids: (orderbookData?.bids || []).map((level) => ({
330
350
  price: level.price,
@@ -368,7 +388,9 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
368
388
  const data = rawData;
369
389
  const ohlcvData = data.data;
370
390
  const event = {
371
- tradingPairId: data.symbol,
391
+ // `trading_pair_id` is the pair UUID; `symbol` is the display string.
392
+ // `tradingPairId` must carry the UUID (the symbol lives in `candlestick.s`).
393
+ tradingPairId: data.trading_pair_id,
372
394
  tradingMode: data.trading_mode,
373
395
  interval: data.interval,
374
396
  candlestick: {
@@ -532,21 +554,20 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
532
554
  disconnect,
533
555
  isConnected: () => ws?.readyState === WebSocket.OPEN,
534
556
  getStatus,
535
- setToken: (newToken) => {
536
- token = newToken || undefined;
537
- stopReconnect();
538
- const currentSocket = ws;
539
- if (currentSocket?.readyState === WebSocket.OPEN ||
540
- currentSocket?.readyState === WebSocket.CONNECTING ||
541
- currentSocket?.readyState === WebSocket.CLOSING) {
542
- currentSocket.close(1000, newToken ? "Token updated, reconnecting." : "Token cleared.");
543
- ws = null;
557
+ setSessionKeypair: (credentials) => {
558
+ session = credentials ?? undefined;
559
+ // If already connected, (re)send the signed handshake so the connection
560
+ // picks up the new session without tearing down. If not yet connected,
561
+ // onopen will send it. Clearing the session leaves the connection up for
562
+ // public channels; logout calls disconnect() to fully tear down.
563
+ if (session && ws?.readyState === WebSocket.OPEN) {
564
+ sendAuthenticate();
565
+ }
566
+ else if (session && (!ws || ws.readyState === WebSocket.CLOSED)) {
567
+ connect().catch((err) => {
568
+ console.warn("WebSocket: Failed to connect after session update:", err);
569
+ });
544
570
  }
545
- if (!newToken)
546
- return;
547
- connect().catch((err) => {
548
- console.warn("WebSocket: Failed to reconnect after token update:", err);
549
- });
550
571
  },
551
572
  orders: subscribeOrders,
552
573
  orderbook: subscribeOrderbook,
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Session keypair crypto for noncustodial request signing.
3
+ *
4
+ * On login the SDK generates a random ed25519 keypair locally. The public key
5
+ * is registered with the server (bound by the wallet's signature on the
6
+ * challenge); every subsequent authenticated request is signed with the
7
+ * private key. The server verifies the signature against the stored public
8
+ * key — it never sees the private key, so it cannot mint requests as the user.
9
+ */
10
+ /** A locally-generated ed25519 session keypair (raw 32-byte values). */
11
+ export interface SessionKeypair {
12
+ /** 32-byte ed25519 public key */
13
+ publicKey: Uint8Array;
14
+ /** 32-byte ed25519 private key (seed) */
15
+ privateKey: Uint8Array;
16
+ }
17
+ /**
18
+ * Generate a fresh ed25519 session keypair.
19
+ *
20
+ * `@noble/curves` sources entropy from `crypto.getRandomValues`. Environments
21
+ * without it (very old Node without webcrypto, locked-down sandboxes) will
22
+ * throw — we surface a clear error rather than producing a weak key.
23
+ */
24
+ export declare function generateSessionKeypair(): SessionKeypair;
25
+ export declare function publicKeyHex(keypair: SessionKeypair): string;
26
+ export declare function privateKeyHex(keypair: SessionKeypair): string;
27
+ /** Reconstruct a keypair from its hex-encoded halves (e.g. restored from storage). */
28
+ export declare function keypairFromHex(publicKeyHex: string, privateKeyHex: string): SessionKeypair;
29
+ /** Lowercase-hex sha256 of the given bytes. Use over an empty array for no-body requests. */
30
+ export declare function sha256Hex(data: Uint8Array): string;
31
+ /**
32
+ * Canonical per-request signing string. Mirrors the server
33
+ * (`handlers::auth::compose_signing_string`):
34
+ * `METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX`.
35
+ */
36
+ export declare function composeSigningString(method: string, pathWithQuery: string, timestampMs: number, bodySha256Hex: string): string;
37
+ /** Canonical WebSocket handshake signing string: `WS-AUTH\n<pubkey-hex>\n<ts>`. */
38
+ export declare function composeWsSigningString(publicKeyHex: string, timestampMs: number): string;
39
+ /** Sign an arbitrary string with the session private key, returning lowercase hex. */
40
+ export declare function signMessage(privateKey: Uint8Array, message: string): string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Session keypair crypto for noncustodial request signing.
3
+ *
4
+ * On login the SDK generates a random ed25519 keypair locally. The public key
5
+ * is registered with the server (bound by the wallet's signature on the
6
+ * challenge); every subsequent authenticated request is signed with the
7
+ * private key. The server verifies the signature against the stored public
8
+ * key — it never sees the private key, so it cannot mint requests as the user.
9
+ */
10
+ import { ed25519 } from "@noble/curves/ed25519";
11
+ import { sha256 } from "@noble/hashes/sha2";
12
+ import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils";
13
+ /**
14
+ * Generate a fresh ed25519 session keypair.
15
+ *
16
+ * `@noble/curves` sources entropy from `crypto.getRandomValues`. Environments
17
+ * without it (very old Node without webcrypto, locked-down sandboxes) will
18
+ * throw — we surface a clear error rather than producing a weak key.
19
+ */
20
+ export function generateSessionKeypair() {
21
+ if (typeof globalThis.crypto?.getRandomValues !== "function") {
22
+ throw new Error("Secure randomness (crypto.getRandomValues) is unavailable; cannot generate a session keypair.");
23
+ }
24
+ const privateKey = ed25519.utils.randomPrivateKey();
25
+ const publicKey = ed25519.getPublicKey(privateKey);
26
+ return { publicKey, privateKey };
27
+ }
28
+ export function publicKeyHex(keypair) {
29
+ return bytesToHex(keypair.publicKey);
30
+ }
31
+ export function privateKeyHex(keypair) {
32
+ return bytesToHex(keypair.privateKey);
33
+ }
34
+ /** Reconstruct a keypair from its hex-encoded halves (e.g. restored from storage). */
35
+ export function keypairFromHex(publicKeyHex, privateKeyHex) {
36
+ return {
37
+ publicKey: hexToBytes(publicKeyHex),
38
+ privateKey: hexToBytes(privateKeyHex),
39
+ };
40
+ }
41
+ /** Lowercase-hex sha256 of the given bytes. Use over an empty array for no-body requests. */
42
+ export function sha256Hex(data) {
43
+ return bytesToHex(sha256(data));
44
+ }
45
+ /**
46
+ * Canonical per-request signing string. Mirrors the server
47
+ * (`handlers::auth::compose_signing_string`):
48
+ * `METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX`.
49
+ */
50
+ export function composeSigningString(method, pathWithQuery, timestampMs, bodySha256Hex) {
51
+ return `${method.toUpperCase()}\n${pathWithQuery}\n${timestampMs}\n${bodySha256Hex}`;
52
+ }
53
+ /** Canonical WebSocket handshake signing string: `WS-AUTH\n<pubkey-hex>\n<ts>`. */
54
+ export function composeWsSigningString(publicKeyHex, timestampMs) {
55
+ return `WS-AUTH\n${publicKeyHex}\n${timestampMs}`;
56
+ }
57
+ /** Sign an arbitrary string with the session private key, returning lowercase hex. */
58
+ export function signMessage(privateKey, message) {
59
+ return bytesToHex(ed25519.sign(utf8ToBytes(message), privateKey));
60
+ }
package/dist/sdk.d.ts CHANGED
@@ -21,21 +21,26 @@ export declare class MonacoSDKImpl implements MonacoSDK {
21
21
  private readonly network;
22
22
  private readonly chain;
23
23
  /**
24
- * Propagate the access token to all APIs and WebSocket
24
+ * Propagate the session keypair (or `undefined` to clear) to all APIs and
25
+ * the WebSocket client.
25
26
  */
26
- private propagateAccessToken;
27
+ private propagateSession;
28
+ /** Extract the session keypair from an auth state. */
29
+ private sessionFromAuthState;
27
30
  constructor(cfg: SDKConfig);
28
31
  /**
29
32
  * Authenticate the user
30
33
  *
31
- * Returns an AuthState object containing:
32
- * - `accessToken`: For making authenticated API requests
33
- * - `refreshToken`: For refreshing tokens AND revoking (logout)
34
- * - `expiresAt`: When the access token expires
34
+ * Generates a session keypair, has the wallet authorize it, and returns an
35
+ * AuthState object containing:
36
+ * - `sessionPublicKey` / `sessionPrivateKey`: the session keypair used to
37
+ * sign subsequent requests (the private key is the credential persist it
38
+ * to survive reloads without re-prompting the wallet)
39
+ * - `expiresAt`: When the session expires
35
40
  * - `user`: User information
36
41
  *
37
- * Note: Use `sdk.logout()` to revoke the token and clean up, or call
38
- * `sdk.auth.revokeToken()` directly to just revoke.
42
+ * Note: Use `sdk.logout()` to revoke the session and clean up, or call
43
+ * `sdk.auth.revokeSession()` directly to just revoke.
39
44
  *
40
45
  * @param clientId - The client ID for authentication
41
46
  * @param options - Optional configuration
@@ -49,13 +54,10 @@ export declare class MonacoSDKImpl implements MonacoSDK {
49
54
  * // Login and auto-connect WebSocket
50
55
  * const authState = await sdk.login(clientId, { connectWebSocket: true });
51
56
  *
52
- * // Manual WebSocket connection
53
- * await sdk.ws.connect();
54
- *
55
57
  * // Later, to revoke:
56
- * await sdk.auth.revokeToken(); // ✅
58
+ * await sdk.auth.revokeSession(); // ✅
57
59
  * // Or revoke and disconnect WebSocket:
58
- * await sdk.logout(); // ✅ Calls revokeToken internally and disconnects WebSocket
60
+ * await sdk.logout(); // ✅ Calls revokeSession internally and disconnects WebSocket
59
61
  * ```
60
62
  */
61
63
  login(clientId: string, options?: {
@@ -77,19 +79,24 @@ export declare class MonacoSDKImpl implements MonacoSDK {
77
79
  /**
78
80
  * Log the user out
79
81
  *
80
- * This method revokes the refresh token (if available), disconnects all authenticated
82
+ * This method revokes the session (if authenticated), disconnects all authenticated
81
83
  * WebSocket channels, and clears the local auth state.
82
- * It internally calls `auth.revokeToken()` to invalidate the token on the server.
84
+ * It internally calls `auth.revokeSession()` to invalidate the session on the server.
83
85
  *
84
86
  * @example
85
87
  * ```typescript
86
88
  * await sdk.logout();
87
- * // Token is revoked, authenticated WebSockets disconnected, and local state cleared
89
+ * // Session is revoked, authenticated WebSockets disconnected, and local state cleared
88
90
  * ```
89
91
  */
90
92
  logout(): Promise<void>;
91
93
  /**
92
- * Refresh the access token
94
+ * Refresh the current session, extending its expiry.
95
+ *
96
+ * Signs a refresh request with the active session key and updates the local
97
+ * `expiresAt`. The session keypair is unchanged. If no session is active, or
98
+ * the session has expired/been revoked, this throws.
99
+ *
93
100
  * @returns The updated authentication state
94
101
  */
95
102
  refreshAuth(): Promise<AuthState>;
package/dist/sdk.js CHANGED
@@ -13,6 +13,16 @@ import { TradingAPIImpl } from "./api/trading";
13
13
  import { VaultAPIImpl } from "./api/vault";
14
14
  import { APIError, InvalidConfigError, InvalidStateError } from "./errors";
15
15
  import { resolveApiUrl, resolveWsUrl } from "./networks";
16
+ /** Validate a user-supplied URL override, returning it unchanged when valid. */
17
+ function validateUrl(value, field) {
18
+ try {
19
+ new URL(value);
20
+ }
21
+ catch (_e) {
22
+ throw new InvalidConfigError(`${field} must be a valid URL, got: ${value}`, field);
23
+ }
24
+ return value;
25
+ }
16
26
  export class MonacoSDKImpl {
17
27
  auth;
18
28
  delegatedAgents;
@@ -33,22 +43,30 @@ export class MonacoSDKImpl {
33
43
  network;
34
44
  chain;
35
45
  /**
36
- * Propagate the access token to all APIs and WebSocket
46
+ * Propagate the session keypair (or `undefined` to clear) to all APIs and
47
+ * the WebSocket client.
37
48
  */
38
- propagateAccessToken(accessToken) {
39
- this.auth.setAccessToken(accessToken);
40
- this.delegatedAgents.setAccessToken(accessToken);
41
- this.applications.setAccessToken(accessToken);
42
- this.fees.setAccessToken(accessToken);
43
- this.vault.setAccessToken(accessToken);
44
- this.trading.setAccessToken(accessToken);
45
- this.market.setAccessToken(accessToken);
46
- this.marginAccounts.setAccessToken(accessToken);
47
- this.positions.setAccessToken(accessToken);
48
- this.profile.setAccessToken(accessToken);
49
- this.orderbook.setAccessToken(accessToken);
50
- this.trades.setAccessToken(accessToken);
51
- this.ws.setToken(accessToken);
49
+ propagateSession(credentials) {
50
+ this.auth.setSessionKeypair(credentials);
51
+ this.delegatedAgents.setSessionKeypair(credentials);
52
+ this.applications.setSessionKeypair(credentials);
53
+ this.fees.setSessionKeypair(credentials);
54
+ this.vault.setSessionKeypair(credentials);
55
+ this.trading.setSessionKeypair(credentials);
56
+ this.market.setSessionKeypair(credentials);
57
+ this.marginAccounts.setSessionKeypair(credentials);
58
+ this.positions.setSessionKeypair(credentials);
59
+ this.profile.setSessionKeypair(credentials);
60
+ this.orderbook.setSessionKeypair(credentials);
61
+ this.trades.setSessionKeypair(credentials);
62
+ this.ws.setSessionKeypair(credentials);
63
+ }
64
+ /** Extract the session keypair from an auth state. */
65
+ sessionFromAuthState(authState) {
66
+ return {
67
+ publicKey: authState.sessionPublicKey,
68
+ privateKey: authState.sessionPrivateKey,
69
+ };
52
70
  }
53
71
  constructor(cfg) {
54
72
  // Validate network - must be a preset
@@ -67,8 +85,8 @@ export class MonacoSDKImpl {
67
85
  catch (_e) {
68
86
  throw new InvalidConfigError(`seiRpcUrl must be a valid URL, got: ${cfg.seiRpcUrl}`, "seiRpcUrl");
69
87
  }
70
- // Infer WebSocket URL from network
71
- const wsUrl = resolveWsUrl(this.network);
88
+ // Resolve the WebSocket URL: explicit override (validated) or network preset.
89
+ const wsUrl = cfg.wsUrl ? validateUrl(cfg.wsUrl, "wsUrl") : resolveWsUrl(this.network);
72
90
  // Use Sei mainnet chain only for "mainnet" network, testnet for everything else
73
91
  const chain = this.network === "mainnet" ? sei : seiTestnet;
74
92
  this.chain = chain;
@@ -78,8 +96,8 @@ export class MonacoSDKImpl {
78
96
  chain,
79
97
  transport,
80
98
  });
81
- // Resolve the API URL (from preset or custom URL)
82
- const apiUrl = resolveApiUrl(this.network);
99
+ // Resolve the API URL: explicit override (validated) or network preset.
100
+ const apiUrl = cfg.apiUrl ? validateUrl(cfg.apiUrl, "apiUrl") : resolveApiUrl(this.network);
83
101
  // Validate wallet client chain if provided
84
102
  if (cfg.walletClient) {
85
103
  if (cfg.walletClient.chain?.id !== chain.id) {
@@ -107,14 +125,16 @@ export class MonacoSDKImpl {
107
125
  /**
108
126
  * Authenticate the user
109
127
  *
110
- * Returns an AuthState object containing:
111
- * - `accessToken`: For making authenticated API requests
112
- * - `refreshToken`: For refreshing tokens AND revoking (logout)
113
- * - `expiresAt`: When the access token expires
128
+ * Generates a session keypair, has the wallet authorize it, and returns an
129
+ * AuthState object containing:
130
+ * - `sessionPublicKey` / `sessionPrivateKey`: the session keypair used to
131
+ * sign subsequent requests (the private key is the credential persist it
132
+ * to survive reloads without re-prompting the wallet)
133
+ * - `expiresAt`: When the session expires
114
134
  * - `user`: User information
115
135
  *
116
- * Note: Use `sdk.logout()` to revoke the token and clean up, or call
117
- * `sdk.auth.revokeToken()` directly to just revoke.
136
+ * Note: Use `sdk.logout()` to revoke the session and clean up, or call
137
+ * `sdk.auth.revokeSession()` directly to just revoke.
118
138
  *
119
139
  * @param clientId - The client ID for authentication
120
140
  * @param options - Optional configuration
@@ -128,24 +148,15 @@ export class MonacoSDKImpl {
128
148
  * // Login and auto-connect WebSocket
129
149
  * const authState = await sdk.login(clientId, { connectWebSocket: true });
130
150
  *
131
- * // Manual WebSocket connection
132
- * await sdk.ws.connect();
133
- *
134
151
  * // Later, to revoke:
135
- * await sdk.auth.revokeToken(); // ✅
152
+ * await sdk.auth.revokeSession(); // ✅
136
153
  * // Or revoke and disconnect WebSocket:
137
- * await sdk.logout(); // ✅ Calls revokeToken internally and disconnects WebSocket
154
+ * await sdk.logout(); // ✅ Calls revokeSession internally and disconnects WebSocket
138
155
  * ```
139
156
  */
140
157
  async login(clientId, options) {
141
- const response = await this.auth.authenticate(clientId);
142
- this.authState = {
143
- accessToken: response.accessToken,
144
- refreshToken: response.refreshToken,
145
- expiresAt: response.expiresAt,
146
- user: response.user,
147
- };
148
- this.propagateAccessToken(this.authState.accessToken);
158
+ this.authState = await this.auth.authenticate(clientId);
159
+ this.propagateSession(this.sessionFromAuthState(this.authState));
149
160
  // Auto-connect WebSocket if requested
150
161
  if (options?.connectWebSocket) {
151
162
  await this.ws.connect();
@@ -168,59 +179,63 @@ export class MonacoSDKImpl {
168
179
  */
169
180
  setAuthState(authState) {
170
181
  this.authState = authState;
171
- this.propagateAccessToken(authState.accessToken);
182
+ this.propagateSession(this.sessionFromAuthState(authState));
172
183
  }
173
184
  /**
174
185
  * Log the user out
175
186
  *
176
- * This method revokes the refresh token (if available), disconnects all authenticated
187
+ * This method revokes the session (if authenticated), disconnects all authenticated
177
188
  * WebSocket channels, and clears the local auth state.
178
- * It internally calls `auth.revokeToken()` to invalidate the token on the server.
189
+ * It internally calls `auth.revokeSession()` to invalidate the session on the server.
179
190
  *
180
191
  * @example
181
192
  * ```typescript
182
193
  * await sdk.logout();
183
- * // Token is revoked, authenticated WebSockets disconnected, and local state cleared
194
+ * // Session is revoked, authenticated WebSockets disconnected, and local state cleared
184
195
  * ```
185
196
  */
186
197
  async logout() {
187
- if (this.authState?.refreshToken) {
198
+ if (this.authState) {
188
199
  try {
189
- await this.auth.revokeToken();
200
+ await this.auth.revokeSession();
190
201
  }
191
202
  catch (error) {
192
203
  // Log but don't throw - we want to clear the local state regardless
193
- console.warn("Failed to revoke token on logout:", error);
204
+ console.warn("Failed to revoke session on logout:", error);
194
205
  }
195
206
  }
196
207
  this.authState = undefined;
197
- this.propagateAccessToken("");
208
+ this.propagateSession(undefined);
198
209
  this.ws.disconnect();
199
210
  }
200
211
  /**
201
- * Refresh the access token
212
+ * Refresh the current session, extending its expiry.
213
+ *
214
+ * Signs a refresh request with the active session key and updates the local
215
+ * `expiresAt`. The session keypair is unchanged. If no session is active, or
216
+ * the session has expired/been revoked, this throws.
217
+ *
202
218
  * @returns The updated authentication state
203
219
  */
204
220
  async refreshAuth() {
205
- if (!this.authState?.refreshToken) {
206
- throw new APIError("No refresh token available", {
221
+ if (!this.authState) {
222
+ throw new APIError("No active session to refresh", {
207
223
  endpoint: "auth/refresh",
208
224
  statusCode: StatusCodes.UNAUTHORIZED,
209
225
  });
210
226
  }
211
227
  try {
212
- const response = await this.auth.refreshToken(this.authState.refreshToken);
228
+ const response = await this.auth.refreshSession();
213
229
  this.authState = {
214
230
  ...this.authState,
215
- accessToken: response.accessToken,
216
231
  expiresAt: response.expiresAt,
217
232
  };
218
- this.propagateAccessToken(this.authState.accessToken);
219
233
  return this.authState;
220
234
  }
221
235
  catch (error) {
222
- // If refresh fails, set the auth state to undefined
236
+ // If refresh fails, the session is no longer usable.
223
237
  this.authState = undefined;
238
+ this.propagateSession(undefined);
224
239
  throw error;
225
240
  }
226
241
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmonaco/core",
3
- "version": "0.8.5",
3
+ "version": "0.8.7-develop.34bd452",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,8 +23,10 @@
23
23
  "viem": "^2.45.2"
24
24
  },
25
25
  "dependencies": {
26
- "@0xmonaco/contracts": "0.8.5",
27
- "@0xmonaco/types": "0.8.5",
26
+ "@0xmonaco/contracts": "0.8.7-develop.34bd452",
27
+ "@0xmonaco/types": "0.8.7-develop.34bd452",
28
+ "@noble/curves": "^1.9.1",
29
+ "@noble/hashes": "^1.8.0",
28
30
  "http-status-codes": "^2.3.0"
29
31
  },
30
32
  "devDependencies": {