@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.
- package/README.md +3 -12
- package/dist/api/applications/api.d.ts +61 -8
- package/dist/api/applications/api.js +75 -11
- package/dist/api/auth/api.d.ts +44 -76
- package/dist/api/auth/api.js +68 -111
- package/dist/api/base.d.ts +50 -9
- package/dist/api/base.js +97 -14
- package/dist/api/delegated-agents/api.d.ts +2 -1
- package/dist/api/delegated-agents/api.js +15 -11
- package/dist/api/faucet/api.d.ts +25 -0
- package/dist/api/faucet/api.js +29 -0
- package/dist/api/faucet/index.d.ts +1 -0
- package/dist/api/faucet/index.js +1 -0
- package/dist/api/fees/api.d.ts +13 -13
- package/dist/api/fees/api.js +18 -18
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.js +4 -0
- package/dist/api/margin-accounts/api.d.ts +9 -5
- package/dist/api/margin-accounts/api.js +70 -33
- package/dist/api/market/api.d.ts +5 -2
- package/dist/api/market/api.js +32 -16
- package/dist/api/orderbook/api.js +13 -12
- package/dist/api/perp/routes.d.ts +113 -37
- package/dist/api/perp/routes.js +37 -6
- package/dist/api/positions/api.d.ts +2 -1
- package/dist/api/positions/api.js +30 -18
- package/dist/api/profile/api.d.ts +40 -23
- package/dist/api/profile/api.js +78 -38
- package/dist/api/sub-accounts/api.d.ts +62 -0
- package/dist/api/sub-accounts/api.js +80 -0
- package/dist/api/sub-accounts/index.d.ts +1 -0
- package/dist/api/sub-accounts/index.js +1 -0
- package/dist/api/trades/api.d.ts +21 -10
- package/dist/api/trades/api.js +22 -10
- package/dist/api/trading/api.d.ts +11 -8
- package/dist/api/trading/api.js +76 -90
- package/dist/api/vault/api.d.ts +96 -29
- package/dist/api/vault/api.js +172 -43
- package/dist/api/vault/index.d.ts +1 -1
- package/dist/api/vault/index.js +1 -1
- package/dist/api/websocket/types.d.ts +5 -5
- package/dist/api/websocket/websocket.js +43 -22
- package/dist/api/whitelist/api.d.ts +27 -0
- package/dist/api/whitelist/api.js +32 -0
- package/dist/api/whitelist/index.d.ts +1 -0
- package/dist/api/whitelist/index.js +1 -0
- package/dist/api/withdrawals/api.d.ts +18 -0
- package/dist/api/withdrawals/api.js +35 -0
- package/dist/api/withdrawals/index.d.ts +1 -0
- package/dist/api/withdrawals/index.js +1 -0
- package/dist/coverage.d.ts +93 -0
- package/dist/coverage.js +93 -0
- package/dist/crypto/session.d.ts +40 -0
- package/dist/crypto/session.js +60 -0
- package/dist/sdk.d.ts +58 -20
- package/dist/sdk.js +156 -53
- package/package.json +5 -3
package/dist/api/vault/api.js
CHANGED
|
@@ -28,6 +28,31 @@ import { ApproveTokenSchema, DepositSchema, validate, WithdrawSchema } from "@0x
|
|
|
28
28
|
import { erc20Abi, zeroAddress } from "viem";
|
|
29
29
|
import { APIError, ContractError, InvalidConfigError } from "../../errors";
|
|
30
30
|
import { BaseAPI } from "../base";
|
|
31
|
+
/** Default delay between polls while waiting for a withdrawal's proof. */
|
|
32
|
+
const DEFAULT_WITHDRAWAL_POLL_INTERVAL_MS = 5_000;
|
|
33
|
+
/**
|
|
34
|
+
* Default total wait for a withdrawal's proof (5 minutes). A withdrawal's merkle
|
|
35
|
+
* proof only lands after its root is confirmed on-chain, which on a healthy
|
|
36
|
+
* pipeline takes ~20s but can lag behind a brief proposer hiccup; the previous
|
|
37
|
+
* 60s tripped too eagerly. On timeout the SDK returns `awaiting_proof` rather
|
|
38
|
+
* than throwing, so the caller never loses the index — it just retries later.
|
|
39
|
+
*/
|
|
40
|
+
const DEFAULT_WITHDRAWAL_TIMEOUT_MS = 300_000;
|
|
41
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
|
+
/**
|
|
43
|
+
* Encode the on-chain `applicationData` for a deposit.
|
|
44
|
+
*
|
|
45
|
+
* For `"spot"` (the default) this stays the bare `clientId` string — byte-for-byte
|
|
46
|
+
* identical to the legacy encoding, so existing deposits are unchanged. For
|
|
47
|
+
* `"margin"` it emits a small JSON routing hint the indexer decodes to credit the
|
|
48
|
+
* deposit to margin collateral.
|
|
49
|
+
*
|
|
50
|
+
* MUST stay in sync with the indexer decoder in
|
|
51
|
+
* `indexer/src/listeners/deposit_routing.rs`.
|
|
52
|
+
*/
|
|
53
|
+
export function encodeDepositApplicationData(clientId, target) {
|
|
54
|
+
return target === "margin" ? JSON.stringify({ clientId, depositTarget: "MARGIN" }) : clientId;
|
|
55
|
+
}
|
|
31
56
|
export class VaultAPIImpl extends BaseAPI {
|
|
32
57
|
publicClient;
|
|
33
58
|
chain;
|
|
@@ -196,6 +221,10 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
196
221
|
* @param assetId - The asset identifier (UUID) to deposit
|
|
197
222
|
* @param amount - The amount of tokens to deposit (as bigint)
|
|
198
223
|
* @param autoWait - Whether to automatically wait for transaction confirmation (defaults to true)
|
|
224
|
+
* @param target - Destination ledger: `"spot"` (default) credits the spot
|
|
225
|
+
* wallet; `"margin"` routes the deposit into the parent margin account's
|
|
226
|
+
* collateral (auto-creating the account if needed). Margin deposits that
|
|
227
|
+
* cannot be routed safely fall back to spot.
|
|
199
228
|
* @returns Promise resolving to TransactionResult with transaction details
|
|
200
229
|
* @throws {ContractError} When deposit fails or approval is insufficient
|
|
201
230
|
* @throws {APIError} When the asset is not found or the assetId is invalid
|
|
@@ -211,6 +240,14 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
211
240
|
* console.log(`Deposit transaction: ${result.hash}`);
|
|
212
241
|
* console.log(`Status: ${result.status}`); // "confirmed" if successful
|
|
213
242
|
*
|
|
243
|
+
* // Deposit straight into margin collateral
|
|
244
|
+
* const marginDeposit = await vaultAPI.deposit(
|
|
245
|
+
* "123e4567-e89b-12d3-a456-426614174000",
|
|
246
|
+
* parseUnits("100", 6),
|
|
247
|
+
* true,
|
|
248
|
+
* "margin"
|
|
249
|
+
* );
|
|
250
|
+
*
|
|
214
251
|
* // Or skip auto-waiting
|
|
215
252
|
* const result = await vaultAPI.deposit(
|
|
216
253
|
* "123e4567-e89b-12d3-a456-426614174000",
|
|
@@ -221,14 +258,17 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
221
258
|
* const receipt = await sdk.waitForTransaction(result.hash);
|
|
222
259
|
* ```
|
|
223
260
|
*/
|
|
224
|
-
async deposit(assetId, amount, autoWait = true) {
|
|
261
|
+
async deposit(assetId, amount, autoWait = true, target = "spot") {
|
|
225
262
|
if (!this.walletClient) {
|
|
226
263
|
throw new InvalidConfigError("Wallet client not set. Connect a wallet first.", "walletClient");
|
|
227
264
|
}
|
|
228
265
|
// Validate inputs
|
|
229
|
-
validate(DepositSchema, { assetId, amount, autoWait });
|
|
266
|
+
validate(DepositSchema, { assetId, amount, autoWait, target });
|
|
230
267
|
const vaultAddress = await this.getVaultAddress();
|
|
231
268
|
const clientId = await this.getClientId();
|
|
269
|
+
// The on-chain applicationData carries the client id plus, for margin
|
|
270
|
+
// deposits, the routing hint the indexer decodes.
|
|
271
|
+
const applicationData = encodeDepositApplicationData(clientId, target);
|
|
232
272
|
const { tokenAddress, isNativeToken } = await this.resolveAsset(assetId);
|
|
233
273
|
if (!isNativeToken) {
|
|
234
274
|
// Check if approval is needed before proceeding
|
|
@@ -250,7 +290,7 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
250
290
|
address: vaultAddress,
|
|
251
291
|
abi: CONTRACT_ABIS.vault,
|
|
252
292
|
functionName: "depositNative",
|
|
253
|
-
args: [walletAccount.address,
|
|
293
|
+
args: [walletAccount.address, applicationData],
|
|
254
294
|
account: walletAccount,
|
|
255
295
|
chain: this.chain,
|
|
256
296
|
value: amount,
|
|
@@ -261,7 +301,7 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
261
301
|
address: vaultAddress,
|
|
262
302
|
abi: CONTRACT_ABIS.vault,
|
|
263
303
|
functionName: "depositERC20",
|
|
264
|
-
args: [walletAccount.address,
|
|
304
|
+
args: [walletAccount.address, applicationData, tokenAddress, amount],
|
|
265
305
|
account: walletAccount,
|
|
266
306
|
chain: this.chain,
|
|
267
307
|
});
|
|
@@ -275,21 +315,80 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
275
315
|
return await this.waitForTransaction(txResult, autoWait);
|
|
276
316
|
}
|
|
277
317
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
318
|
+
* Polls `GET /api/v1/withdrawals/{index}` until the gateway can serve the
|
|
319
|
+
* `executeWithdrawal` calldata, then returns it with the vault address.
|
|
320
|
+
*
|
|
321
|
+
* The calldata only exists once the withdrawal's root is confirmed on-chain
|
|
322
|
+
* and its merkle proof is persisted. Until then the gateway returns 404 (the
|
|
323
|
+
* row is written asynchronously by the persistor after `initiate`) or 409
|
|
324
|
+
* (the proof is not confirmed yet); both are transient for a freshly-allocated
|
|
325
|
+
* index, so we retry on each until ready or the timeout elapses.
|
|
280
326
|
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
327
|
+
* Returns `null` when the timeout elapses before the proof is available — the
|
|
328
|
+
* index is still valid and the balance already debited, so callers surface
|
|
329
|
+
* this as `awaiting_proof` (with the index intact) rather than as a thrown
|
|
330
|
+
* error that would strand the index. Only non-transient errors (anything
|
|
331
|
+
* other than 404/409) throw.
|
|
332
|
+
*
|
|
333
|
+
* @throws {APIError} On non-transient errors while polling.
|
|
334
|
+
*/
|
|
335
|
+
async pollWithdrawalCalldata(withdrawalIndex, retry) {
|
|
336
|
+
const pollIntervalMs = retry?.pollIntervalMs ?? DEFAULT_WITHDRAWAL_POLL_INTERVAL_MS;
|
|
337
|
+
const timeoutMs = retry?.timeoutMs ?? DEFAULT_WITHDRAWAL_TIMEOUT_MS;
|
|
338
|
+
const deadline = Date.now() + timeoutMs;
|
|
339
|
+
for (;;) {
|
|
340
|
+
try {
|
|
341
|
+
const { vaultAddress, calldata } = await this.makePublicRequest(`/api/v1/withdrawals/${withdrawalIndex}`);
|
|
342
|
+
// Ready: non-empty calldata. (Defensively treat an empty/`0x` body as
|
|
343
|
+
// not-ready too, though the gateway returns an error in that case.)
|
|
344
|
+
if (calldata && calldata !== "0x") {
|
|
345
|
+
return { vaultAddress, calldata };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
// 404 (row not persisted yet) and 409 (proof not confirmed yet) are
|
|
350
|
+
// transient for a valid index — keep polling. Anything else is fatal.
|
|
351
|
+
const isTransient = error instanceof APIError && (error.statusCode === 404 || error.statusCode === 409);
|
|
352
|
+
if (!isTransient) {
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (Date.now() + pollIntervalMs > deadline) {
|
|
357
|
+
// Timed out waiting for the proof. Signal not-ready; the caller keeps the
|
|
358
|
+
// index and retries later rather than losing it inside a thrown error.
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
await sleep(pollIntervalMs);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Initiates a withdrawal through the API Gateway, waits for its on-chain
|
|
366
|
+
* confirmation, then submits the resulting `executeWithdrawal` calldata via
|
|
367
|
+
* the connected wallet.
|
|
368
|
+
*
|
|
369
|
+
* The gateway allocates a `withdrawalIndex` via the matching engine and debits
|
|
370
|
+
* the balance immediately, but the executable calldata needs the withdrawal's
|
|
371
|
+
* merkle proof, which only exists once its root is confirmed on-chain. This
|
|
372
|
+
* method polls until the calldata is available (see `retry`), then submits it.
|
|
373
|
+
* The connected wallet pays gas; the contract authorises the withdrawal
|
|
374
|
+
* against the merkle proof embedded in the calldata. The wallet's address is
|
|
375
|
+
* sent as the on-chain `destination`.
|
|
376
|
+
*
|
|
377
|
+
* If the proof does not become available within the poll window, this method
|
|
378
|
+
* does NOT throw — it returns `{ withdrawalIndex, status: "awaiting_proof" }`
|
|
379
|
+
* with nothing submitted on-chain. The balance is already debited, so losing
|
|
380
|
+
* the index would strand the funds; the caller keeps it and finishes the
|
|
381
|
+
* withdrawal later with `retryWithdrawal(withdrawalIndex)`.
|
|
287
382
|
*
|
|
288
383
|
* @param assetId - The asset identifier (UUID) to withdraw
|
|
289
384
|
* @param amount - The raw token amount to withdraw (as bigint)
|
|
290
385
|
* @param autoWait - Whether to automatically wait for transaction confirmation (defaults to true)
|
|
291
|
-
* @
|
|
292
|
-
* @
|
|
386
|
+
* @param sourceOrRetry - Source ledger (`"spot"` or `"margin"`) or legacy retry options
|
|
387
|
+
* @param retry - Polling cadence/timeout while waiting for the proof when a source is supplied
|
|
388
|
+
* @returns Promise resolving to a [`WithdrawResult`]: on success the submitted
|
|
389
|
+
* transaction plus `withdrawalIndex`; on poll-timeout `{ withdrawalIndex,
|
|
390
|
+
* status: "awaiting_proof" }`.
|
|
391
|
+
* @throws {APIError} When the asset is not found or the initiate request is rejected
|
|
293
392
|
* @throws {ContractError} When the on-chain submission fails
|
|
294
393
|
* @throws {InvalidConfigError} When wallet account is not available
|
|
295
394
|
*
|
|
@@ -301,18 +400,23 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
301
400
|
* parseUnits("50", 6),
|
|
302
401
|
* );
|
|
303
402
|
* console.log(`Withdrawal index: ${result.withdrawalIndex}`);
|
|
304
|
-
*
|
|
403
|
+
* if (result.status === "awaiting_proof") {
|
|
404
|
+
* // Proof not ready yet — balance already debited. Finish later:
|
|
405
|
+
* await vaultAPI.retryWithdrawal(result.withdrawalIndex);
|
|
406
|
+
* } else {
|
|
407
|
+
* console.log(`Tx hash: ${result.hash}, status: ${result.status}`);
|
|
408
|
+
* }
|
|
305
409
|
*
|
|
306
|
-
* //
|
|
410
|
+
* // Custom polling (check every 10s, wait up to an hour)
|
|
307
411
|
* const result = await vaultAPI.withdraw(
|
|
308
412
|
* "123e4567-e89b-12d3-a456-426614174000",
|
|
309
413
|
* parseUnits("50", 6),
|
|
310
|
-
*
|
|
414
|
+
* true,
|
|
415
|
+
* { pollIntervalMs: 10_000, timeoutMs: 3_600_000 },
|
|
311
416
|
* );
|
|
312
|
-
* const receipt = await sdk.waitForTransaction(result.hash);
|
|
313
417
|
* ```
|
|
314
418
|
*/
|
|
315
|
-
async withdraw(assetId, amount, autoWait = true) {
|
|
419
|
+
async withdraw(assetId, amount, autoWait = true, sourceOrRetry, retry) {
|
|
316
420
|
if (!this.walletClient) {
|
|
317
421
|
throw new InvalidConfigError("Wallet client not set. Connect a wallet first.", "walletClient");
|
|
318
422
|
}
|
|
@@ -321,21 +425,33 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
321
425
|
throw new InvalidConfigError("No account available in wallet client", "account");
|
|
322
426
|
}
|
|
323
427
|
const destination = walletAccount.address.toLowerCase();
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
const {
|
|
428
|
+
const source = typeof sourceOrRetry === "string" ? sourceOrRetry : "spot";
|
|
429
|
+
const retryOptions = typeof sourceOrRetry === "string" ? retry : sourceOrRetry;
|
|
430
|
+
validate(WithdrawSchema, { assetId, amount, destination, autoWait, source });
|
|
431
|
+
// 1. Gateway allocates the withdrawalIndex and debits the balance. No
|
|
432
|
+
// executable calldata yet — it needs the merkle proof, available only once
|
|
433
|
+
// the withdrawal root is confirmed on-chain.
|
|
434
|
+
const { withdrawalIndex } = await this.makeAuthenticatedRequest("/api/v1/withdrawals", {
|
|
331
435
|
method: "POST",
|
|
332
436
|
body: JSON.stringify({
|
|
333
|
-
|
|
437
|
+
assetId: assetId,
|
|
334
438
|
amount: amount.toString(),
|
|
335
439
|
destination,
|
|
440
|
+
source,
|
|
336
441
|
}),
|
|
337
442
|
});
|
|
338
|
-
// 2.
|
|
443
|
+
// 2. Poll for the executeWithdrawal calldata until the proof is confirmed.
|
|
444
|
+
// The vault address comes back with it, keeping calldata + target contract
|
|
445
|
+
// in lockstep.
|
|
446
|
+
const poll = await this.pollWithdrawalCalldata(withdrawalIndex, retryOptions);
|
|
447
|
+
if (!poll) {
|
|
448
|
+
// Proof not available within the poll window. The balance is already
|
|
449
|
+
// debited and the index is valid; return it (without submitting anything
|
|
450
|
+
// on-chain) so the caller can finish later via retryWithdrawal(index).
|
|
451
|
+
return { withdrawalIndex, status: "awaiting_proof" };
|
|
452
|
+
}
|
|
453
|
+
const { vaultAddress, calldata } = poll;
|
|
454
|
+
// 3. Submit the calldata on-chain through the connected wallet.
|
|
339
455
|
let hash;
|
|
340
456
|
try {
|
|
341
457
|
hash = await this.walletClient.sendTransaction({
|
|
@@ -346,7 +462,7 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
346
462
|
});
|
|
347
463
|
}
|
|
348
464
|
catch (error) {
|
|
349
|
-
throw new ContractError(`Failed to submit
|
|
465
|
+
throw new ContractError(`Failed to submit executeWithdrawal for withdrawal ${withdrawalIndex}: ${error instanceof Error ? error.message : "unknown error"}`, { cause: error instanceof Error ? error : undefined });
|
|
350
466
|
}
|
|
351
467
|
const nonce = walletAccount.getNonce ? await walletAccount.getNonce() : 0n;
|
|
352
468
|
const txResult = {
|
|
@@ -358,25 +474,32 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
358
474
|
return { ...settled, withdrawalIndex };
|
|
359
475
|
}
|
|
360
476
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
477
|
+
* Submits (or resubmits) a previously-initiated withdrawal on-chain.
|
|
478
|
+
*
|
|
479
|
+
* Use this when the original `withdraw()` could not complete — e.g. the proof
|
|
480
|
+
* was not yet confirmed within its polling window, the wallet rejected the tx,
|
|
481
|
+
* the page reloaded before the receipt came back, or a stuck mempool entry
|
|
482
|
+
* needs resending. Polls `GET /withdrawals/{index}` for the
|
|
483
|
+
* `executeWithdrawal` calldata (retrying while it is not yet confirmed), then
|
|
484
|
+
* submits it through the connected wallet. Does NOT initiate a new withdrawal
|
|
485
|
+
* — the matching engine already debited the balance and allocated the index.
|
|
486
|
+
* The contract is idempotent against double-submission of a settled
|
|
487
|
+
* withdrawal: it reverts once the index is consumed on-chain.
|
|
364
488
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* already debited the balance and allocated the index. The contract is
|
|
369
|
-
* idempotent against double-submission of a settled withdrawal: it will
|
|
370
|
-
* revert once the index is consumed on-chain.
|
|
489
|
+
* Like `withdraw()`, if the proof is still unavailable within the poll window
|
|
490
|
+
* this returns `{ withdrawalIndex, status: "awaiting_proof" }` instead of
|
|
491
|
+
* throwing, so the caller can simply retry again later.
|
|
371
492
|
*
|
|
372
493
|
* @param withdrawalIndex - The index returned by the original `withdraw()` call
|
|
373
494
|
* @param autoWait - Whether to await on-chain confirmation (defaults to true)
|
|
374
|
-
* @
|
|
375
|
-
* @
|
|
495
|
+
* @param retry - Polling cadence/timeout while waiting for the proof
|
|
496
|
+
* @returns Promise resolving to a [`WithdrawResult`]: the submitted transaction
|
|
497
|
+
* plus `withdrawalIndex`, or `{ withdrawalIndex, status: "awaiting_proof" }`
|
|
498
|
+
* if the proof did not arrive within the poll window.
|
|
376
499
|
* @throws {ContractError} When the on-chain submission fails
|
|
377
500
|
* @throws {InvalidConfigError} When wallet account is not available
|
|
378
501
|
*/
|
|
379
|
-
async retryWithdrawal(withdrawalIndex, autoWait = true) {
|
|
502
|
+
async retryWithdrawal(withdrawalIndex, autoWait = true, retry) {
|
|
380
503
|
if (!this.walletClient) {
|
|
381
504
|
throw new InvalidConfigError("Wallet client not set. Connect a wallet first.", "walletClient");
|
|
382
505
|
}
|
|
@@ -384,7 +507,13 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
384
507
|
if (!walletAccount) {
|
|
385
508
|
throw new InvalidConfigError("No account available in wallet client", "account");
|
|
386
509
|
}
|
|
387
|
-
const
|
|
510
|
+
const poll = await this.pollWithdrawalCalldata(withdrawalIndex, retry);
|
|
511
|
+
if (!poll) {
|
|
512
|
+
// Still not confirmed on-chain within the poll window — return the index
|
|
513
|
+
// so the caller can retry again later, rather than throwing.
|
|
514
|
+
return { withdrawalIndex, status: "awaiting_proof" };
|
|
515
|
+
}
|
|
516
|
+
const { vaultAddress, calldata } = poll;
|
|
388
517
|
let hash;
|
|
389
518
|
try {
|
|
390
519
|
hash = await this.walletClient.sendTransaction({
|
|
@@ -395,7 +524,7 @@ export class VaultAPIImpl extends BaseAPI {
|
|
|
395
524
|
});
|
|
396
525
|
}
|
|
397
526
|
catch (error) {
|
|
398
|
-
throw new ContractError(`Failed to resubmit
|
|
527
|
+
throw new ContractError(`Failed to resubmit executeWithdrawal for withdrawal ${withdrawalIndex}: ${error instanceof Error ? error.message : "unknown error"}`, { cause: error instanceof Error ? error : undefined });
|
|
399
528
|
}
|
|
400
529
|
const nonce = walletAccount.getNonce ? await walletAccount.getNonce() : 0n;
|
|
401
530
|
const txResult = {
|
package/dist/api/vault/index.js
CHANGED
|
@@ -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
|
-
/**
|
|
6
|
-
|
|
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
|
-
/**
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whitelist API Implementation
|
|
3
|
+
*
|
|
4
|
+
* Public (unauthenticated) whitelist/waitlist application submission. The
|
|
5
|
+
* server validates and de-duplicates by wallet address and email, creating an
|
|
6
|
+
* inactive user pending manual approval. This is an onboarding endpoint, not a
|
|
7
|
+
* trading operation.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const { message, userId } = await sdk.whitelist.submit({
|
|
12
|
+
* walletAddress: "0x...",
|
|
13
|
+
* email: "user@example.com",
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { SubmitWhitelistRequest, SubmitWhitelistResponse, WhitelistAPI } from "@0xmonaco/types";
|
|
18
|
+
import { BaseAPI } from "../base";
|
|
19
|
+
export declare class WhitelistAPIImpl extends BaseAPI implements WhitelistAPI {
|
|
20
|
+
/**
|
|
21
|
+
* Submits a whitelist (waitlist) application. Public — no auth required.
|
|
22
|
+
*
|
|
23
|
+
* @param body - Applicant details
|
|
24
|
+
* @returns Promise resolving to the status message and created user id
|
|
25
|
+
*/
|
|
26
|
+
submit(body: SubmitWhitelistRequest): Promise<SubmitWhitelistResponse>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whitelist API Implementation
|
|
3
|
+
*
|
|
4
|
+
* Public (unauthenticated) whitelist/waitlist application submission. The
|
|
5
|
+
* server validates and de-duplicates by wallet address and email, creating an
|
|
6
|
+
* inactive user pending manual approval. This is an onboarding endpoint, not a
|
|
7
|
+
* trading operation.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const { message, userId } = await sdk.whitelist.submit({
|
|
12
|
+
* walletAddress: "0x...",
|
|
13
|
+
* email: "user@example.com",
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { BaseAPI } from "../base";
|
|
18
|
+
import { perpRoutes } from "../perp/routes";
|
|
19
|
+
export class WhitelistAPIImpl extends BaseAPI {
|
|
20
|
+
/**
|
|
21
|
+
* Submits a whitelist (waitlist) application. Public — no auth required.
|
|
22
|
+
*
|
|
23
|
+
* @param body - Applicant details
|
|
24
|
+
* @returns Promise resolving to the status message and created user id
|
|
25
|
+
*/
|
|
26
|
+
async submit(body) {
|
|
27
|
+
return await this.makePublicRequest(perpRoutes.whitelist.submit(), {
|
|
28
|
+
method: "POST",
|
|
29
|
+
body: JSON.stringify(body),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WhitelistAPIImpl } from "./api";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WhitelistAPIImpl } from "./api";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InitiateWithdrawalRequest, ListPendingWithdrawalsParams, ListPendingWithdrawalsResponse, WithdrawalResponse, WithdrawalsAPI } from "@0xmonaco/types";
|
|
2
|
+
import { BaseAPI } from "../base";
|
|
3
|
+
/**
|
|
4
|
+
* Low-level withdrawals client.
|
|
5
|
+
*
|
|
6
|
+
* `initiateWithdrawal` debits the caller's balance via the matching engine and
|
|
7
|
+
* allocates a `withdrawalIndex` (its `calldata` is empty — the proof is not
|
|
8
|
+
* available until the withdrawal root is confirmed on-chain). `getWithdrawal`
|
|
9
|
+
* returns the `executeWithdrawal` calldata once that proof is ready, and throws
|
|
10
|
+
* an `APIError` (404 not-persisted-yet / 409 not-confirmed-yet) while it is not.
|
|
11
|
+
* Neither submits on-chain — use the high-level vault API, which polls for the
|
|
12
|
+
* calldata and broadcasts the transaction.
|
|
13
|
+
*/
|
|
14
|
+
export declare class WithdrawalsAPIImpl extends BaseAPI implements WithdrawalsAPI {
|
|
15
|
+
initiateWithdrawal(request: InitiateWithdrawalRequest): Promise<WithdrawalResponse>;
|
|
16
|
+
getWithdrawal(withdrawalIndex: number): Promise<WithdrawalResponse>;
|
|
17
|
+
listPendingWithdrawals(params?: ListPendingWithdrawalsParams): Promise<ListPendingWithdrawalsResponse>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BaseAPI } from "../base";
|
|
2
|
+
import { perpRoutes } from "../perp";
|
|
3
|
+
/**
|
|
4
|
+
* Low-level withdrawals client.
|
|
5
|
+
*
|
|
6
|
+
* `initiateWithdrawal` debits the caller's balance via the matching engine and
|
|
7
|
+
* allocates a `withdrawalIndex` (its `calldata` is empty — the proof is not
|
|
8
|
+
* available until the withdrawal root is confirmed on-chain). `getWithdrawal`
|
|
9
|
+
* returns the `executeWithdrawal` calldata once that proof is ready, and throws
|
|
10
|
+
* an `APIError` (404 not-persisted-yet / 409 not-confirmed-yet) while it is not.
|
|
11
|
+
* Neither submits on-chain — use the high-level vault API, which polls for the
|
|
12
|
+
* calldata and broadcasts the transaction.
|
|
13
|
+
*/
|
|
14
|
+
export class WithdrawalsAPIImpl extends BaseAPI {
|
|
15
|
+
async initiateWithdrawal(request) {
|
|
16
|
+
return this.makeAuthenticatedRequest(perpRoutes.withdrawals.initiate(), {
|
|
17
|
+
method: "POST",
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
assetId: request.assetId,
|
|
20
|
+
amount: request.amount,
|
|
21
|
+
destination: request.destination,
|
|
22
|
+
source: request.source ?? "spot",
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async getWithdrawal(withdrawalIndex) {
|
|
27
|
+
// Public lookup — no auth required.
|
|
28
|
+
return this.makePublicRequest(perpRoutes.withdrawals.byIndex(withdrawalIndex));
|
|
29
|
+
}
|
|
30
|
+
async listPendingWithdrawals(params) {
|
|
31
|
+
// The gateway serves camelCase (the REST wire format), which matches the
|
|
32
|
+
// public surface, so the response is returned directly with no remapping.
|
|
33
|
+
return this.makeAuthenticatedRequest(perpRoutes.withdrawals.list({ page: params?.page, pageSize: params?.pageSize }));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./api";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./api";
|