@0xmonaco/core 0.8.5 → 0.8.7-develop.0f12336

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +3 -12
  2. package/dist/api/applications/api.d.ts +61 -8
  3. package/dist/api/applications/api.js +75 -11
  4. package/dist/api/auth/api.d.ts +44 -76
  5. package/dist/api/auth/api.js +68 -111
  6. package/dist/api/base.d.ts +50 -9
  7. package/dist/api/base.js +97 -14
  8. package/dist/api/delegated-agents/api.d.ts +2 -1
  9. package/dist/api/delegated-agents/api.js +15 -11
  10. package/dist/api/faucet/api.d.ts +25 -0
  11. package/dist/api/faucet/api.js +29 -0
  12. package/dist/api/faucet/index.d.ts +1 -0
  13. package/dist/api/faucet/index.js +1 -0
  14. package/dist/api/fees/api.d.ts +13 -13
  15. package/dist/api/fees/api.js +18 -18
  16. package/dist/api/index.d.ts +4 -0
  17. package/dist/api/index.js +4 -0
  18. package/dist/api/margin-accounts/api.d.ts +9 -5
  19. package/dist/api/margin-accounts/api.js +70 -33
  20. package/dist/api/market/api.d.ts +5 -2
  21. package/dist/api/market/api.js +32 -16
  22. package/dist/api/orderbook/api.js +13 -12
  23. package/dist/api/perp/routes.d.ts +113 -37
  24. package/dist/api/perp/routes.js +37 -6
  25. package/dist/api/positions/api.d.ts +2 -1
  26. package/dist/api/positions/api.js +30 -18
  27. package/dist/api/profile/api.d.ts +40 -23
  28. package/dist/api/profile/api.js +78 -38
  29. package/dist/api/sub-accounts/api.d.ts +62 -0
  30. package/dist/api/sub-accounts/api.js +80 -0
  31. package/dist/api/sub-accounts/index.d.ts +1 -0
  32. package/dist/api/sub-accounts/index.js +1 -0
  33. package/dist/api/trades/api.d.ts +21 -10
  34. package/dist/api/trades/api.js +22 -10
  35. package/dist/api/trading/api.d.ts +11 -8
  36. package/dist/api/trading/api.js +76 -90
  37. package/dist/api/vault/api.d.ts +96 -29
  38. package/dist/api/vault/api.js +172 -43
  39. package/dist/api/vault/index.d.ts +1 -1
  40. package/dist/api/vault/index.js +1 -1
  41. package/dist/api/websocket/types.d.ts +5 -5
  42. package/dist/api/websocket/websocket.js +43 -22
  43. package/dist/api/whitelist/api.d.ts +27 -0
  44. package/dist/api/whitelist/api.js +32 -0
  45. package/dist/api/whitelist/index.d.ts +1 -0
  46. package/dist/api/whitelist/index.js +1 -0
  47. package/dist/api/withdrawals/api.d.ts +18 -0
  48. package/dist/api/withdrawals/api.js +35 -0
  49. package/dist/api/withdrawals/index.d.ts +1 -0
  50. package/dist/api/withdrawals/index.js +1 -0
  51. package/dist/coverage.d.ts +93 -0
  52. package/dist/coverage.js +93 -0
  53. package/dist/crypto/session.d.ts +40 -0
  54. package/dist/crypto/session.js +60 -0
  55. package/dist/sdk.d.ts +58 -20
  56. package/dist/sdk.js +156 -53
  57. package/package.json +5 -3
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Auth API Implementation
3
3
  *
4
- * Handles authentication operations including challenge creation, signature verification,
5
- * and backend authentication. All operations go through the API Gateway.
4
+ * Handles wallet authentication operations including challenge creation, signature
5
+ * verification, and session lifecycle. All operations go through the API Gateway.
6
6
  *
7
- * This class provides a complete interface for authentication on the Monaco protocol,
8
- * including frontend wallet authentication and backend service authentication.
7
+ * This class provides a complete interface for wallet-based authentication on the
8
+ * Monaco protocol. Backend services authenticate separately by setting their
9
+ * application secret key (`sk_...`) via {@link BaseAPI.setServerKey}, which is sent
10
+ * in the `x-server-key` header on backend requests — there is no token exchange.
9
11
  *
10
12
  * @example
11
13
  * ```typescript
@@ -23,11 +25,9 @@
23
25
  * challenge.nonce,
24
26
  * clientId
25
27
  * );
26
- *
27
- * // Authenticate backend service
28
- * const backendAuth = await authAPI.authenticateBackend(secretKey);
29
28
  * ```
30
29
  */
30
+ import { generateSessionKeypair, privateKeyHex, publicKeyHex } from "../../crypto/session";
31
31
  import { InvalidConfigError } from "../../errors";
32
32
  import { BaseAPI } from "../base";
33
33
  export class AuthAPIImpl extends BaseAPI {
@@ -56,20 +56,24 @@ export class AuthAPIImpl extends BaseAPI {
56
56
  * Complete authentication flow for frontend applications.
57
57
  *
58
58
  * 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
59
+ * 1. Generates a fresh ed25519 session keypair locally
60
+ * 2. Creates a challenge that commits to the session public key
61
+ * 3. Signs the challenge message with the wallet
62
+ * 4. Verifies the signature, registering the session public key
63
+ *
64
+ * The returned {@link AuthState} carries the session keypair; sign subsequent
65
+ * requests with the private key (the wallet is not used again until the
66
+ * session expires).
62
67
  *
63
68
  * @param clientId - Client ID of the application
64
- * @returns Promise resolving to the verification response with JWT tokens
69
+ * @returns Promise resolving to the authentication state (with the session keypair)
65
70
  * @throws {APIError} When authentication fails
66
71
  * @throws {InvalidConfigError} When wallet account is not available
67
72
  *
68
73
  * @example
69
74
  * ```typescript
70
- * // Complete authentication in one call
71
75
  * const authResult = await authAPI.authenticate("my-app-client-id");
72
- * console.log(`Access token: ${authResult.access_token}`);
76
+ * console.log(`Session public key: ${authResult.sessionPublicKey}`);
73
77
  * console.log(`User ID: ${authResult.user.id}`);
74
78
  * ```
75
79
  */
@@ -81,12 +85,19 @@ export class AuthAPIImpl extends BaseAPI {
81
85
  if (!account) {
82
86
  throw new InvalidConfigError("No account available in wallet client", "account");
83
87
  }
84
- // 1. Create challenge
85
- const challenge = await this.createChallenge(account.address, clientId);
86
- // 2. Sign the challenge message
88
+ // 1. Generate a fresh session keypair locally — the server never sees the
89
+ // private key.
90
+ const keypair = generateSessionKeypair();
91
+ const session = {
92
+ publicKey: publicKeyHex(keypair),
93
+ privateKey: privateKeyHex(keypair),
94
+ };
95
+ // 2. Create challenge (binds the session public key into the signed message)
96
+ const challenge = await this.createChallenge(account.address, clientId, session.publicKey);
97
+ // 3. Sign the challenge message with the wallet
87
98
  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);
99
+ // 4. Verify signature, registering the session public key
100
+ return await this.verifySignature(account.address, signature, challenge.nonce, clientId, session);
90
101
  }
91
102
  /**
92
103
  * Signs a challenge message using the wallet client.
@@ -141,19 +152,20 @@ export class AuthAPIImpl extends BaseAPI {
141
152
  * console.log(`Nonce: ${challenge.nonce}`);
142
153
  * ```
143
154
  */
144
- async createChallenge(address, clientId) {
155
+ async createChallenge(address, clientId, sessionPublicKey) {
145
156
  const responseBody = await this.makePublicRequest("/api/v1/auth/challenge", {
146
157
  method: "POST",
147
158
  body: JSON.stringify({
148
159
  address,
149
- client_id: clientId,
150
- chain_id: this.chain.id,
160
+ clientId: clientId,
161
+ chainId: this.chain.id,
162
+ sessionPublicKey: sessionPublicKey,
151
163
  }),
152
164
  });
153
165
  return {
154
166
  nonce: responseBody.nonce,
155
167
  message: responseBody.message,
156
- expiresAt: responseBody.expires_at,
168
+ expiresAt: responseBody.expiresAt,
157
169
  };
158
170
  }
159
171
  /**
@@ -163,47 +175,39 @@ export class AuthAPIImpl extends BaseAPI {
163
175
  * authenticated API access. This is the second step in the authentication flow.
164
176
  *
165
177
  * @param address - Wallet address of the user
166
- * @param signature - Signature of the challenge message
178
+ * @param signature - Wallet signature of the challenge message
167
179
  * @param nonce - Nonce from the challenge response
168
180
  * @param clientId - Client ID of the application
169
- * @returns Promise resolving to the verification response with JWT tokens
181
+ * @param session - The locally-generated session keypair (hex-encoded)
182
+ * @returns Promise resolving to the authentication state (with the session keypair)
170
183
  * @throws {APIError} When signature verification fails
171
184
  *
172
185
  * @example
173
186
  * ```typescript
174
- * // First create a challenge
175
- * const challenge = await authAPI.createChallenge(address, clientId);
176
- *
177
- * // User signs the challenge message with their wallet
187
+ * const keypair = generateSessionKeypair();
188
+ * const session = { publicKey: publicKeyHex(keypair), privateKey: privateKeyHex(keypair) };
189
+ * const challenge = await authAPI.createChallenge(address, clientId, session.publicKey);
178
190
  * 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}`);
191
+ * const authResult = await authAPI.verifySignature(address, signature, challenge.nonce, clientId, session);
192
+ * console.log(`Session public key: ${authResult.sessionPublicKey}`);
190
193
  * ```
191
194
  */
192
- async verifySignature(address, signature, nonce, clientId) {
195
+ async verifySignature(address, signature, nonce, clientId, session) {
193
196
  const responseBody = await this.makePublicRequest("/api/v1/auth/verify", {
194
197
  method: "POST",
195
198
  body: JSON.stringify({
196
199
  address,
197
200
  signature,
198
201
  nonce,
199
- client_id: clientId,
200
- chain_id: this.chain.id,
202
+ clientId: clientId,
203
+ chainId: this.chain.id,
204
+ sessionPublicKey: session.publicKey,
201
205
  }),
202
206
  });
203
207
  return {
204
- accessToken: responseBody.access_token,
205
- refreshToken: responseBody.refresh_token,
206
- expiresAt: responseBody.expires_at,
208
+ sessionPublicKey: session.publicKey,
209
+ sessionPrivateKey: session.privateKey,
210
+ expiresAt: responseBody.expiresAt,
207
211
  user: {
208
212
  id: responseBody.user.id,
209
213
  address: responseBody.user.address,
@@ -212,92 +216,45 @@ export class AuthAPIImpl extends BaseAPI {
212
216
  };
213
217
  }
214
218
  /**
215
- * Authenticates a backend service using a secret key.
216
- *
217
- * Returns JWT tokens for API access. This method is used for backend services
218
- * that need to authenticate with the Monaco API Gateway.
219
- *
220
- * @param secretKey - Secret key of the application
221
- * @returns Promise resolving to the backend auth response with JWT tokens
222
- * @throws {APIError} When backend authentication fails
223
- *
224
- * @example
225
- * ```typescript
226
- * const backendAuth = await authAPI.authenticateBackend("my-secret-key");
227
- * console.log(`Backend access token: ${backendAuth.accessToken}`);
228
- * console.log(`Application: ${backendAuth.application.name}`);
229
- * ```
230
- */
231
- async authenticateBackend(secretKey) {
232
- const responseBody = await this.makePublicRequest("/api/v1/auth/backend", {
233
- method: "POST",
234
- body: JSON.stringify({
235
- secret_key: secretKey,
236
- chain_id: this.chain.id,
237
- }),
238
- });
239
- return {
240
- accessToken: responseBody.access_token,
241
- expiresAt: responseBody.expires_at,
242
- application: {
243
- id: responseBody.application.id,
244
- name: responseBody.application.name,
245
- clientId: responseBody.application.client_id,
246
- },
247
- };
248
- }
249
- /**
250
- * Refreshes an access token using a refresh token.
219
+ * Extends the current session's expiry.
251
220
  *
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.
221
+ * The request is signed with the active session key (set via
222
+ * {@link setSessionKeypair}); the server bumps the session's `expiresAt`.
223
+ * No new credential is issued — the same keypair keeps working.
255
224
  *
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
225
+ * @returns Promise resolving to the new expiry
226
+ * @throws {APIError} When refresh fails (e.g. session expired or revoked)
259
227
  *
260
228
  * @example
261
229
  * ```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)}`);
230
+ * const { expiresAt } = await authAPI.refreshSession();
231
+ * console.log(`Session now expires at: ${new Date(expiresAt * 1000)}`);
265
232
  * ```
266
233
  */
267
- async refreshToken(refreshToken) {
268
- const responseBody = await this.makePublicRequest("/api/v1/auth/refresh", {
234
+ async refreshSession() {
235
+ const responseBody = await this.makeAuthenticatedRequest("/api/v1/auth/refresh", {
269
236
  method: "POST",
270
- body: JSON.stringify({
271
- refresh_token: refreshToken,
272
- }),
273
237
  });
274
238
  return {
275
- accessToken: responseBody.access_token,
276
- expiresAt: responseBody.expires_at,
239
+ expiresAt: responseBody.expiresAt,
277
240
  };
278
241
  }
279
242
  /**
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.
243
+ * Revokes the current session.
285
244
  *
286
- * The server identifies the token to revoke from the access token in the
287
- * Authorization header — no request body is needed.
245
+ * The request is signed with the active session key; the server deletes the
246
+ * matching session row. Used for logout or when a key may be compromised.
288
247
  *
289
- * @returns Promise resolving when the token is revoked
290
- * @throws {APIError} When token revocation fails
248
+ * @returns Promise resolving when the session is revoked
249
+ * @throws {APIError} When revocation fails
291
250
  *
292
251
  * @example
293
252
  * ```typescript
294
- * // After authentication
295
- * const authResult = await authAPI.authenticate(clientId);
296
- * await authAPI.revokeToken();
297
- * console.log("Token revoked successfully");
253
+ * await authAPI.revokeSession();
254
+ * console.log("Session revoked successfully");
298
255
  * ```
299
256
  */
300
- async revokeToken() {
257
+ async revokeSession() {
301
258
  await this.makeAuthenticatedRequest("/api/v1/auth/revoke", {
302
259
  method: "POST",
303
260
  });
@@ -20,13 +20,18 @@
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;
33
+ /** Application secret key (`sk_...`) sent in the `x-server-key` header for backend-authenticated requests. */
34
+ protected serverKey?: string;
30
35
  protected retryOptions: Required<RetryOptions>;
31
36
  /**
32
37
  * Creates a new BaseAPI instance.
@@ -36,17 +41,24 @@ export declare abstract class BaseAPI {
36
41
  */
37
42
  constructor(apiUrl: string, retryOptions?: RetryOptions);
38
43
  /**
39
- * Set the access token for authenticated requests.
44
+ * Set (or clear) the session keypair used to sign authenticated requests.
40
45
  *
41
- * @param token - JWT access token
46
+ * @param credentials - Hex-encoded session keypair, or `undefined` to clear.
42
47
  */
43
- setAccessToken(token: string): void;
48
+ setSessionKeypair(credentials: SessionCredentials | undefined): void;
44
49
  /**
45
- * Get the current access token.
50
+ * Set (or clear) the application secret key used for backend-authenticated
51
+ * requests.
46
52
  *
47
- * @returns The current access token or undefined if not set
53
+ * Backend endpoints (those annotated `#[require_backend]` on the gateway)
54
+ * authenticate with a static application secret key rather than a session
55
+ * signature. When set, the raw key is sent verbatim in the `x-server-key`
56
+ * header on every {@link makeBackendRequest}. This is independent of the
57
+ * session keypair — a client may hold both.
58
+ *
59
+ * @param serverKey - The application secret key (`sk_...`), or `undefined` to clear.
48
60
  */
49
- protected getAccessToken(): string | undefined;
61
+ setServerKey(serverKey: string | undefined): void;
50
62
  /**
51
63
  * Parse request body for error logging
52
64
  *
@@ -87,12 +99,41 @@ export declare abstract class BaseAPI {
87
99
  * '/api/v1/orders',
88
100
  * {
89
101
  * method: 'POST',
90
- * body: JSON.stringify({ trading_pair: 'ETH-USD', side: 'BUY' })
102
+ * body: JSON.stringify({ tradingPair: 'ETH-USD', side: 'BUY' })
91
103
  * }
92
104
  * );
93
105
  * ```
94
106
  */
95
107
  protected makeAuthenticatedRequest<T>(endpoint: string, options?: RequestInit): Promise<T>;
108
+ /**
109
+ * Compute the per-request ed25519 signature headers.
110
+ *
111
+ * The signed payload is `METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX`,
112
+ * matching the server (`handlers::auth::compose_signing_string`). `endpoint`
113
+ * is the path-with-query exactly as sent; the body hash covers the raw bytes
114
+ * so a request can't be replayed with a different body.
115
+ */
116
+ private buildSignatureHeaders;
117
+ /**
118
+ * Makes a backend-authenticated API request using the application secret key.
119
+ *
120
+ * Sends the raw `sk_...` key (set via {@link setServerKey}) in the
121
+ * `x-server-key` header. The gateway hashes and matches it against the
122
+ * application on every request — there is no signing and no token exchange.
123
+ * Use this for endpoints annotated `#[require_backend]`.
124
+ *
125
+ * @param endpoint - The API endpoint to call (should start with /)
126
+ * @param options - Request options (method, body, headers, etc.)
127
+ * @returns Promise resolving to the response data
128
+ * @throws {APIError} When the server key is not set or the API request fails
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * this.setServerKey("sk_live_...");
133
+ * const orders = await this.makeBackendRequest<ListAppOrdersResponse>('/api/v1/applications/orders');
134
+ * ```
135
+ */
136
+ protected makeBackendRequest<T>(endpoint: string, options?: RequestInit): Promise<T>;
96
137
  /**
97
138
  * Makes an unauthenticated API request.
98
139
  *
@@ -114,7 +155,7 @@ export declare abstract class BaseAPI {
114
155
  * '/api/v1/auth/challenge',
115
156
  * {
116
157
  * method: 'POST',
117
- * body: JSON.stringify({ address, client_id: clientId })
158
+ * body: JSON.stringify({ address, clientId: clientId })
118
159
  * }
119
160
  * );
120
161
  * ```
package/dist/api/base.js CHANGED
@@ -20,11 +20,16 @@
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;
31
+ /** Application secret key (`sk_...`) sent in the `x-server-key` header for backend-authenticated requests. */
32
+ serverKey;
28
33
  retryOptions;
29
34
  /**
30
35
  * Creates a new BaseAPI instance.
@@ -40,20 +45,27 @@ export class BaseAPI {
40
45
  };
41
46
  }
42
47
  /**
43
- * Set the access token for authenticated requests.
48
+ * Set (or clear) the session keypair used to sign authenticated requests.
44
49
  *
45
- * @param token - JWT access token
50
+ * @param credentials - Hex-encoded session keypair, or `undefined` to clear.
46
51
  */
47
- setAccessToken(token) {
48
- this.accessToken = token;
52
+ setSessionKeypair(credentials) {
53
+ this.sessionKeypair = credentials ? keypairFromHex(credentials.publicKey, credentials.privateKey) : undefined;
49
54
  }
50
55
  /**
51
- * Get the current access token.
56
+ * Set (or clear) the application secret key used for backend-authenticated
57
+ * requests.
52
58
  *
53
- * @returns The current access token or undefined if not set
59
+ * Backend endpoints (those annotated `#[require_backend]` on the gateway)
60
+ * authenticate with a static application secret key rather than a session
61
+ * signature. When set, the raw key is sent verbatim in the `x-server-key`
62
+ * header on every {@link makeBackendRequest}. This is independent of the
63
+ * session keypair — a client may hold both.
64
+ *
65
+ * @param serverKey - The application secret key (`sk_...`), or `undefined` to clear.
54
66
  */
55
- getAccessToken() {
56
- return this.accessToken;
67
+ setServerKey(serverKey) {
68
+ this.serverKey = serverKey;
57
69
  }
58
70
  /**
59
71
  * Parse request body for error logging
@@ -175,6 +187,12 @@ export class BaseAPI {
175
187
  retryAfter,
176
188
  });
177
189
  }
190
+ // A successful response with an empty body (e.g. 204 No Content from a
191
+ // DELETE) is not an error — there is simply nothing to parse. Return
192
+ // `undefined` so void-returning methods resolve cleanly.
193
+ if (responseText.trim() === "") {
194
+ return undefined;
195
+ }
178
196
  // If response is OK but not JSON, throw a more specific error
179
197
  throw new APIError(`Expected JSON response but received ${contentType}`, {
180
198
  endpoint: url,
@@ -223,14 +241,79 @@ export class BaseAPI {
223
241
  * '/api/v1/orders',
224
242
  * {
225
243
  * method: 'POST',
226
- * body: JSON.stringify({ trading_pair: 'ETH-USD', side: 'BUY' })
244
+ * body: JSON.stringify({ tradingPair: 'ETH-USD', side: 'BUY' })
227
245
  * }
228
246
  * );
229
247
  * ```
230
248
  */
231
249
  async makeAuthenticatedRequest(endpoint, options = {}) {
232
- if (!this.accessToken) {
233
- throw new APIError("Access token not set. Call setAccessToken() first.", {
250
+ if (!this.sessionKeypair) {
251
+ throw new APIError("Session keypair not set. Authenticate (login) first.", {
252
+ endpoint: `${this.apiUrl}${endpoint}`,
253
+ statusCode: StatusCodes.UNAUTHORIZED,
254
+ });
255
+ }
256
+ const url = `${this.apiUrl}${endpoint}`;
257
+ const requestBody = this.parseRequestBody(options.body);
258
+ const signatureHeaders = this.buildSignatureHeaders(endpoint, options);
259
+ return this.executeRequest(url, endpoint, {
260
+ ...options,
261
+ headers: {
262
+ "Content-Type": "application/json",
263
+ ...signatureHeaders,
264
+ ...options.headers,
265
+ },
266
+ }, requestBody);
267
+ }
268
+ /**
269
+ * Compute the per-request ed25519 signature headers.
270
+ *
271
+ * The signed payload is `METHOD\nPATH_WITH_QUERY\nTIMESTAMP_MS\nSHA256_BODY_HEX`,
272
+ * matching the server (`handlers::auth::compose_signing_string`). `endpoint`
273
+ * is the path-with-query exactly as sent; the body hash covers the raw bytes
274
+ * so a request can't be replayed with a different body.
275
+ */
276
+ buildSignatureHeaders(endpoint, options) {
277
+ if (!this.sessionKeypair) {
278
+ throw new APIError("Session keypair not set. Authenticate (login) first.", {
279
+ endpoint: `${this.apiUrl}${endpoint}`,
280
+ statusCode: StatusCodes.UNAUTHORIZED,
281
+ });
282
+ }
283
+ const method = (options.method ?? "GET").toUpperCase();
284
+ const timestampMs = Date.now();
285
+ const bodyBytes = typeof options.body === "string" ? utf8ToBytes(options.body) : new Uint8Array(0);
286
+ const bodyHash = sha256Hex(bodyBytes);
287
+ const signingString = composeSigningString(method, endpoint, timestampMs, bodyHash);
288
+ const signature = signMessage(this.sessionKeypair.privateKey, signingString);
289
+ return {
290
+ "X-Monaco-PublicKey": bytesToHex(this.sessionKeypair.publicKey),
291
+ "X-Monaco-Timestamp": String(timestampMs),
292
+ "X-Monaco-Signature": signature,
293
+ };
294
+ }
295
+ /**
296
+ * Makes a backend-authenticated API request using the application secret key.
297
+ *
298
+ * Sends the raw `sk_...` key (set via {@link setServerKey}) in the
299
+ * `x-server-key` header. The gateway hashes and matches it against the
300
+ * application on every request — there is no signing and no token exchange.
301
+ * Use this for endpoints annotated `#[require_backend]`.
302
+ *
303
+ * @param endpoint - The API endpoint to call (should start with /)
304
+ * @param options - Request options (method, body, headers, etc.)
305
+ * @returns Promise resolving to the response data
306
+ * @throws {APIError} When the server key is not set or the API request fails
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * this.setServerKey("sk_live_...");
311
+ * const orders = await this.makeBackendRequest<ListAppOrdersResponse>('/api/v1/applications/orders');
312
+ * ```
313
+ */
314
+ async makeBackendRequest(endpoint, options = {}) {
315
+ if (!this.serverKey) {
316
+ throw new APIError("Server key not set. Call setServerKey(sk_...) first.", {
234
317
  endpoint: `${this.apiUrl}${endpoint}`,
235
318
  statusCode: StatusCodes.UNAUTHORIZED,
236
319
  });
@@ -241,7 +324,7 @@ export class BaseAPI {
241
324
  ...options,
242
325
  headers: {
243
326
  "Content-Type": "application/json",
244
- Authorization: `Bearer ${this.accessToken}`,
327
+ "x-server-key": this.serverKey,
245
328
  ...options.headers,
246
329
  },
247
330
  }, requestBody);
@@ -267,7 +350,7 @@ export class BaseAPI {
267
350
  * '/api/v1/auth/challenge',
268
351
  * {
269
352
  * method: 'POST',
270
- * body: JSON.stringify({ address, client_id: clientId })
353
+ * body: JSON.stringify({ address, clientId: clientId })
271
354
  * }
272
355
  * );
273
356
  * ```
@@ -1,8 +1,9 @@
1
- import type { CreateDelegatedSessionRequest, CreateDelegatedSessionResponse, DelegatedAgent, DelegatedAgentsAPI, ListDelegatedAgentsResponse, UpsertDelegatedAgentRequest } from "@0xmonaco/types";
1
+ import type { CreateDelegatedSessionRequest, CreateDelegatedSessionResponse, DelegatedAgent, DelegatedAgentsAPI, ListDelegatedAgentsResponse, ListDelegatedOwnersResponse, UpsertDelegatedAgentRequest } from "@0xmonaco/types";
2
2
  import { BaseAPI } from "../base";
3
3
  export declare class DelegatedAgentsAPIImpl extends BaseAPI implements DelegatedAgentsAPI {
4
4
  upsertDelegatedAgent(request: UpsertDelegatedAgentRequest): Promise<DelegatedAgent>;
5
5
  listDelegatedAgents(): Promise<ListDelegatedAgentsResponse>;
6
+ listDelegatedOwners(): Promise<ListDelegatedOwnersResponse>;
6
7
  revokeDelegatedAgent(delegatedAgentId: string): Promise<{
7
8
  status: "REVOKED";
8
9
  }>;
@@ -5,23 +5,26 @@ export class DelegatedAgentsAPIImpl extends BaseAPI {
5
5
  return this.makeAuthenticatedRequest(perpRoutes.delegatedAgents.list(), {
6
6
  method: "POST",
7
7
  body: JSON.stringify({
8
- agent_address: request.agentAddress,
8
+ agentAddress: request.agentAddress,
9
9
  name: request.name,
10
- expires_at: request.expiresAt,
11
- allowed_actions: request.allowedActions,
12
- allowed_trading_pair_ids: request.allowedTradingPairIds,
13
- allowed_margin_account_ids: request.allowedMarginAccountIds,
14
- allowed_order_types: request.allowedOrderTypes,
15
- allowed_time_in_force: request.allowedTimeInForce,
16
- max_leverage: request.maxLeverage,
17
- max_order_notional: request.maxOrderNotional,
18
- max_open_orders: request.maxOpenOrders,
10
+ expiresAt: request.expiresAt,
11
+ allowedActions: request.allowedActions,
12
+ allowedTradingPairIds: request.allowedTradingPairIds,
13
+ allowedMarginAccountIds: request.allowedMarginAccountIds,
14
+ allowedOrderTypes: request.allowedOrderTypes,
15
+ allowedTimeInForce: request.allowedTimeInForce,
16
+ maxLeverage: request.maxLeverage,
17
+ maxOrderNotional: request.maxOrderNotional,
18
+ maxOpenOrders: request.maxOpenOrders,
19
19
  }),
20
20
  });
21
21
  }
22
22
  async listDelegatedAgents() {
23
23
  return this.makeAuthenticatedRequest(perpRoutes.delegatedAgents.list());
24
24
  }
25
+ async listDelegatedOwners() {
26
+ return this.makeAuthenticatedRequest(perpRoutes.delegatedAgents.owners());
27
+ }
25
28
  async revokeDelegatedAgent(delegatedAgentId) {
26
29
  return this.makeAuthenticatedRequest(perpRoutes.delegatedAgents.byId(delegatedAgentId), {
27
30
  method: "DELETE",
@@ -31,7 +34,8 @@ export class DelegatedAgentsAPIImpl extends BaseAPI {
31
34
  return this.makeAuthenticatedRequest(perpRoutes.delegatedAgents.sessions(), {
32
35
  method: "POST",
33
36
  body: JSON.stringify({
34
- owner_user_id: request.ownerUserId,
37
+ ownerUserId: request.ownerUserId,
38
+ sessionPublicKey: request.sessionPublicKey,
35
39
  }),
36
40
  });
37
41
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Faucet API Implementation
3
+ *
4
+ * Testnet token faucet. Session-authenticated. Performs real on-chain mints
5
+ * from a backend-operator-funded signer and is rate-limited per user
6
+ * (default 1 request / 24h). It is unavailable if the gateway operator has not
7
+ * configured a signer — the SDK caller has no control over that.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const { minted, failed, remainingRequests24h } = await sdk.faucet.mint();
12
+ * ```
13
+ */
14
+ import type { FaucetAPI, MintTokensResponse } from "@0xmonaco/types";
15
+ import { BaseAPI } from "../base";
16
+ export declare class FaucetAPIImpl extends BaseAPI implements FaucetAPI {
17
+ /**
18
+ * Mints the full set of testnet tokens to the authenticated user.
19
+ *
20
+ * Partial success is possible — inspect `minted` and `failed` in the response.
21
+ *
22
+ * @returns Promise resolving to the minted/failed tokens and remaining quota
23
+ */
24
+ mint(): Promise<MintTokensResponse>;
25
+ }