@0xmonaco/core 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,7 +23,7 @@
23
23
  * console.log(`Deposit transaction: ${result.hash}`);
24
24
  * ```
25
25
  */
26
- import type { ApplicationsAPI, Balance, ProfileAPI, TransactionResult, VaultAPI } from "@0xmonaco/types";
26
+ import type { ApplicationsAPI, Balance, ProfileAPI, TransactionResult, VaultAPI, WithdrawResult } from "@0xmonaco/types";
27
27
  import { type Address, type Chain, type PublicClient, type WalletClient } from "viem";
28
28
  import { BaseAPI } from "../base";
29
29
  export declare class VaultAPIImpl extends BaseAPI implements VaultAPI {
@@ -148,41 +148,64 @@ export declare class VaultAPIImpl extends BaseAPI implements VaultAPI {
148
148
  */
149
149
  deposit(assetId: string, amount: bigint, autoWait?: boolean): Promise<TransactionResult>;
150
150
  /**
151
- * Withdraws tokens from the Monaco vault.
151
+ * Initiates a withdrawal through the API Gateway and submits the resulting
152
+ * pre-signed calldata on-chain via the connected wallet.
152
153
  *
153
- * Withdraws the specified amount of tokens from the vault contract back to the
154
- * user's wallet. The method obtains a signature from the API Gateway and then
155
- * executes the withdrawal transaction on-chain.
154
+ * The gateway allocates a `withdrawalIndex` via the matching engine and returns
155
+ * ABI-encoded calldata for `executeSignedWithdrawal(...)` signed by the
156
+ * server-side `WITHDRAWAL_SIGNER`. The user's wallet pays gas to submit it,
157
+ * but the contract authenticates against the embedded signature — not the
158
+ * `msg.sender`. The connected wallet's address is sent as the on-chain
159
+ * `destination`.
156
160
  *
157
161
  * @param assetId - The asset identifier (UUID) to withdraw
158
- * @param amount - The amount of tokens to withdraw (as bigint)
162
+ * @param amount - The raw token amount to withdraw (as bigint)
159
163
  * @param autoWait - Whether to automatically wait for transaction confirmation (defaults to true)
160
- * @returns Promise resolving to TransactionResult with transaction details
161
- * @throws {ContractError} When withdrawal fails
162
- * @throws {APIError} When signature retrieval fails or the asset is not found/assetId is invalid
164
+ * @returns Promise resolving to `{ withdrawalIndex, transaction }`
165
+ * @throws {APIError} When the asset is not found or the request is rejected
166
+ * @throws {ContractError} When the on-chain submission fails
163
167
  * @throws {InvalidConfigError} When wallet account is not available
164
168
  *
165
169
  * @example
166
170
  * ```typescript
167
- * // Withdraw 50 USDC from the vault (auto-waits by default)
171
+ * // Withdraw 50 USDC, wait for on-chain confirmation
168
172
  * const result = await vaultAPI.withdraw(
169
173
  * "123e4567-e89b-12d3-a456-426614174000",
170
- * parseUnits("50", 6)
174
+ * parseUnits("50", 6),
171
175
  * );
172
- * console.log(`Withdrawal transaction: ${result.hash}`);
173
- * console.log(`Status: ${result.status}`); // "confirmed" if successful
176
+ * console.log(`Withdrawal index: ${result.withdrawalIndex}`);
177
+ * console.log(`Tx hash: ${result.hash}, status: ${result.status}`);
174
178
  *
175
- * // Or skip auto-waiting
179
+ * // Or skip auto-waiting and finalise later
176
180
  * const result = await vaultAPI.withdraw(
177
181
  * "123e4567-e89b-12d3-a456-426614174000",
178
182
  * parseUnits("50", 6),
179
- * false
183
+ * false,
180
184
  * );
181
- * // Then manually wait later if needed
182
185
  * const receipt = await sdk.waitForTransaction(result.hash);
183
186
  * ```
184
187
  */
185
- withdraw(assetId: string, amount: bigint, autoWait?: boolean): Promise<TransactionResult>;
188
+ withdraw(assetId: string, amount: bigint, autoWait?: boolean): Promise<WithdrawResult>;
189
+ /**
190
+ * Retries a previously-initiated withdrawal whose on-chain submission never
191
+ * landed — e.g. the wallet rejected the tx, the page reloaded before the
192
+ * receipt came back, or a stuck mempool entry needs resending.
193
+ *
194
+ * Re-fetches the same `executeSignedWithdrawal` calldata the gateway
195
+ * generated when the withdrawal was initiated, then submits it through the
196
+ * connected wallet. Does NOT initiate a new withdrawal — the matching engine
197
+ * already debited the balance and allocated the index. The contract is
198
+ * idempotent against double-submission of a settled withdrawal: it will
199
+ * revert once the index is consumed on-chain.
200
+ *
201
+ * @param withdrawalIndex - The index returned by the original `withdraw()` call
202
+ * @param autoWait - Whether to await on-chain confirmation (defaults to true)
203
+ * @returns Promise resolving to `{ withdrawalIndex, ...transaction }`
204
+ * @throws {APIError} When the index doesn't exist
205
+ * @throws {ContractError} When the on-chain submission fails
206
+ * @throws {InvalidConfigError} When wallet account is not available
207
+ */
208
+ retryWithdrawal(withdrawalIndex: number, autoWait?: boolean): Promise<WithdrawResult>;
186
209
  /**
187
210
  * Retrieves the user's token balance in the vault.
188
211
  *
@@ -234,19 +257,5 @@ export declare class VaultAPIImpl extends BaseAPI implements VaultAPI {
234
257
  * ```
235
258
  */
236
259
  needsApproval(assetId: string, amount: bigint): Promise<boolean>;
237
- /**
238
- * Retrieves a withdrawal signature from the API Gateway.
239
- *
240
- * Internal method that communicates with the Monaco API Gateway to obtain
241
- * the cryptographic signature required for withdrawal transactions. The signature
242
- * validates the withdrawal request and ensures proper authorization.
243
- *
244
- * @param assetId - The asset identifier (UUID) to withdraw
245
- * @param amount - The amount to withdraw (as bigint)
246
- * @returns Promise resolving to object containing seed and signature
247
- * @throws {APIError} When signature retrieval fails
248
- * @private
249
- */
250
- private getWithdrawSignature;
251
260
  private waitForTransaction;
252
261
  }
@@ -275,37 +275,40 @@ export class VaultAPIImpl extends BaseAPI {
275
275
  return await this.waitForTransaction(txResult, autoWait);
276
276
  }
277
277
  /**
278
- * Withdraws tokens from the Monaco vault.
278
+ * Initiates a withdrawal through the API Gateway and submits the resulting
279
+ * pre-signed calldata on-chain via the connected wallet.
279
280
  *
280
- * Withdraws the specified amount of tokens from the vault contract back to the
281
- * user's wallet. The method obtains a signature from the API Gateway and then
282
- * executes the withdrawal transaction on-chain.
281
+ * The gateway allocates a `withdrawalIndex` via the matching engine and returns
282
+ * ABI-encoded calldata for `executeSignedWithdrawal(...)` signed by the
283
+ * server-side `WITHDRAWAL_SIGNER`. The user's wallet pays gas to submit it,
284
+ * but the contract authenticates against the embedded signature — not the
285
+ * `msg.sender`. The connected wallet's address is sent as the on-chain
286
+ * `destination`.
283
287
  *
284
288
  * @param assetId - The asset identifier (UUID) to withdraw
285
- * @param amount - The amount of tokens to withdraw (as bigint)
289
+ * @param amount - The raw token amount to withdraw (as bigint)
286
290
  * @param autoWait - Whether to automatically wait for transaction confirmation (defaults to true)
287
- * @returns Promise resolving to TransactionResult with transaction details
288
- * @throws {ContractError} When withdrawal fails
289
- * @throws {APIError} When signature retrieval fails or the asset is not found/assetId is invalid
291
+ * @returns Promise resolving to `{ withdrawalIndex, transaction }`
292
+ * @throws {APIError} When the asset is not found or the request is rejected
293
+ * @throws {ContractError} When the on-chain submission fails
290
294
  * @throws {InvalidConfigError} When wallet account is not available
291
295
  *
292
296
  * @example
293
297
  * ```typescript
294
- * // Withdraw 50 USDC from the vault (auto-waits by default)
298
+ * // Withdraw 50 USDC, wait for on-chain confirmation
295
299
  * const result = await vaultAPI.withdraw(
296
300
  * "123e4567-e89b-12d3-a456-426614174000",
297
- * parseUnits("50", 6)
301
+ * parseUnits("50", 6),
298
302
  * );
299
- * console.log(`Withdrawal transaction: ${result.hash}`);
300
- * console.log(`Status: ${result.status}`); // "confirmed" if successful
303
+ * console.log(`Withdrawal index: ${result.withdrawalIndex}`);
304
+ * console.log(`Tx hash: ${result.hash}, status: ${result.status}`);
301
305
  *
302
- * // Or skip auto-waiting
306
+ * // Or skip auto-waiting and finalise later
303
307
  * const result = await vaultAPI.withdraw(
304
308
  * "123e4567-e89b-12d3-a456-426614174000",
305
309
  * parseUnits("50", 6),
306
- * false
310
+ * false,
307
311
  * );
308
- * // Then manually wait later if needed
309
312
  * const receipt = await sdk.waitForTransaction(result.hash);
310
313
  * ```
311
314
  */
@@ -313,45 +316,95 @@ export class VaultAPIImpl extends BaseAPI {
313
316
  if (!this.walletClient) {
314
317
  throw new InvalidConfigError("Wallet client not set. Connect a wallet first.", "walletClient");
315
318
  }
316
- validate(WithdrawSchema, { assetId, amount, autoWait });
317
- const vaultAddress = await this.getVaultAddress();
318
- const { tokenAddress, isNativeToken } = await this.resolveAsset(assetId);
319
- // Get signature from backend API using asset_id
320
- const { seed, signature } = await this.getWithdrawSignature(assetId, amount);
321
319
  const walletAccount = this.walletClient.account;
322
320
  if (!walletAccount) {
323
321
  throw new InvalidConfigError("No account available in wallet client", "account");
324
322
  }
325
- // For native SEI (zero address), use withdrawNative; for ERC20 tokens, use withdraw
323
+ const destination = walletAccount.address.toLowerCase();
324
+ validate(WithdrawSchema, { assetId, amount, destination, autoWait });
325
+ // 1. Gateway allocates withdrawal_index + returns pre-signed
326
+ // executeSignedWithdrawal calldata along with the vault address to send
327
+ // it to. Sourcing the vault address from this response (instead of
328
+ // GET /applications/config) keeps the calldata + target contract in
329
+ // lockstep and avoids an extra round-trip.
330
+ const { withdrawal_index: withdrawalIndex, vault_address: vaultAddress, calldata, } = await this.makeAuthenticatedRequest("/api/v1/withdrawals", {
331
+ method: "POST",
332
+ body: JSON.stringify({
333
+ asset_id: assetId,
334
+ amount: amount.toString(),
335
+ destination,
336
+ }),
337
+ });
338
+ // 2. Submit the calldata on-chain through the connected wallet.
326
339
  let hash;
327
- if (isNativeToken) {
328
- hash = await this.walletClient.writeContract({
329
- address: vaultAddress,
330
- abi: CONTRACT_ABIS.vault,
331
- functionName: "withdrawNative",
332
- args: [amount, seed, signature],
340
+ try {
341
+ hash = await this.walletClient.sendTransaction({
342
+ to: vaultAddress,
343
+ data: calldata,
333
344
  account: walletAccount,
334
345
  chain: this.chain,
335
346
  });
336
347
  }
337
- else {
338
- hash = await this.walletClient.writeContract({
339
- address: vaultAddress,
340
- abi: CONTRACT_ABIS.vault,
341
- functionName: "withdraw",
342
- args: [tokenAddress, amount, seed, signature],
348
+ catch (error) {
349
+ throw new ContractError(`Failed to submit executeSignedWithdrawal for withdrawal ${withdrawalIndex}: ${error instanceof Error ? error.message : "unknown error"}`, { cause: error instanceof Error ? error : undefined });
350
+ }
351
+ const nonce = walletAccount.getNonce ? await walletAccount.getNonce() : 0n;
352
+ const txResult = {
353
+ hash,
354
+ status: "pending",
355
+ nonce,
356
+ };
357
+ const settled = await this.waitForTransaction(txResult, autoWait);
358
+ return { ...settled, withdrawalIndex };
359
+ }
360
+ /**
361
+ * Retries a previously-initiated withdrawal whose on-chain submission never
362
+ * landed — e.g. the wallet rejected the tx, the page reloaded before the
363
+ * receipt came back, or a stuck mempool entry needs resending.
364
+ *
365
+ * Re-fetches the same `executeSignedWithdrawal` calldata the gateway
366
+ * generated when the withdrawal was initiated, then submits it through the
367
+ * connected wallet. Does NOT initiate a new withdrawal — the matching engine
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.
371
+ *
372
+ * @param withdrawalIndex - The index returned by the original `withdraw()` call
373
+ * @param autoWait - Whether to await on-chain confirmation (defaults to true)
374
+ * @returns Promise resolving to `{ withdrawalIndex, ...transaction }`
375
+ * @throws {APIError} When the index doesn't exist
376
+ * @throws {ContractError} When the on-chain submission fails
377
+ * @throws {InvalidConfigError} When wallet account is not available
378
+ */
379
+ async retryWithdrawal(withdrawalIndex, autoWait = true) {
380
+ if (!this.walletClient) {
381
+ throw new InvalidConfigError("Wallet client not set. Connect a wallet first.", "walletClient");
382
+ }
383
+ const walletAccount = this.walletClient.account;
384
+ if (!walletAccount) {
385
+ throw new InvalidConfigError("No account available in wallet client", "account");
386
+ }
387
+ const { vault_address: vaultAddress, calldata } = await this.makePublicRequest(`/api/v1/withdrawals/${withdrawalIndex}`);
388
+ let hash;
389
+ try {
390
+ hash = await this.walletClient.sendTransaction({
391
+ to: vaultAddress,
392
+ data: calldata,
343
393
  account: walletAccount,
344
394
  chain: this.chain,
345
395
  });
346
396
  }
397
+ catch (error) {
398
+ throw new ContractError(`Failed to resubmit executeSignedWithdrawal for withdrawal ${withdrawalIndex}: ${error instanceof Error ? error.message : "unknown error"}`, { cause: error instanceof Error ? error : undefined });
399
+ }
347
400
  const nonce = walletAccount.getNonce ? await walletAccount.getNonce() : 0n;
348
401
  const txResult = {
349
402
  hash,
350
403
  status: "pending",
351
404
  nonce,
352
405
  };
353
- // Handle auto-waiting for transaction confirmation
354
- return await this.waitForTransaction(txResult, autoWait);
406
+ const settled = await this.waitForTransaction(txResult, autoWait);
407
+ return { ...settled, withdrawalIndex };
355
408
  }
356
409
  /**
357
410
  * Retrieves the user's token balance in the vault.
@@ -426,32 +479,6 @@ export class VaultAPIImpl extends BaseAPI {
426
479
  const allowance = await this.getAllowance(assetId);
427
480
  return allowance < amount;
428
481
  }
429
- /**
430
- * Retrieves a withdrawal signature from the API Gateway.
431
- *
432
- * Internal method that communicates with the Monaco API Gateway to obtain
433
- * the cryptographic signature required for withdrawal transactions. The signature
434
- * validates the withdrawal request and ensures proper authorization.
435
- *
436
- * @param assetId - The asset identifier (UUID) to withdraw
437
- * @param amount - The amount to withdraw (as bigint)
438
- * @returns Promise resolving to object containing seed and signature
439
- * @throws {APIError} When signature retrieval fails
440
- * @private
441
- */
442
- async getWithdrawSignature(assetId, amount) {
443
- const data = await this.makeAuthenticatedRequest("/api/v1/withdraw/signature", {
444
- method: "POST",
445
- body: JSON.stringify({
446
- asset_id: assetId,
447
- amount: amount.toString(),
448
- }),
449
- });
450
- return {
451
- seed: data.seed,
452
- signature: data.signature,
453
- };
454
- }
455
482
  async waitForTransaction(txResult, autoWait = true, options = {}) {
456
483
  if (!autoWait) {
457
484
  return txResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xmonaco/core",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",