@silentswap/sdk 0.0.68 → 0.0.69

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/dist/bridge.js CHANGED
@@ -247,10 +247,29 @@ export async function fetchRelayQuote(params, signal) {
247
247
  });
248
248
  if (!response.ok) {
249
249
  const text = await response.text();
250
- throw new Error(`HTTP ${response.status}: ${text}`);
250
+ throw parseQuoteApiErrorResponse(response.status, text);
251
251
  }
252
252
  return response.json();
253
253
  }
254
+ /**
255
+ * Parse quote API error response (relay.link or deBridge) and return a user-friendly Error.
256
+ * Handles INSUFFICIENT_FUNDS / "No UTXOs available to spend" and other known error shapes.
257
+ */
258
+ function parseQuoteApiErrorResponse(status, text) {
259
+ try {
260
+ const body = JSON.parse(text);
261
+ if (body.errorCode === 'INSUFFICIENT_FUNDS' || (body.message && body.message.includes('UTXO'))) {
262
+ return new Error('Insufficient funds');
263
+ }
264
+ if (body.message && typeof body.message === 'string') {
265
+ return new Error(body.message);
266
+ }
267
+ }
268
+ catch {
269
+ // not JSON or invalid
270
+ }
271
+ return new Error(`HTTP ${status}: ${text}`);
272
+ }
254
273
  /**
255
274
  * Fetch a deBridge order with detailed response
256
275
  * Uses create-tx endpoint for cross-chain swaps
@@ -294,7 +313,7 @@ export async function fetchDebridgeOrder(params, signal) {
294
313
  const response = await fetch(`https://deswap.debridge.finance/v1.0/dln/order/create-tx?${queryParams}`, { signal });
295
314
  if (!response.ok) {
296
315
  const text = await response.text();
297
- throw new Error(`HTTP ${response.status}: ${text}`);
316
+ throw parseQuoteApiErrorResponse(response.status, text);
298
317
  }
299
318
  return response.json();
300
319
  }
@@ -323,7 +342,7 @@ async function fetchDebridgeSingleChainOrder(params, signal) {
323
342
  const response = await fetch(`https://deswap.debridge.finance/v1.0/chain/transaction?${queryParams}`, { signal });
324
343
  if (!response.ok) {
325
344
  const text = await response.text();
326
- throw new Error(`HTTP ${response.status}: ${text}`);
345
+ throw parseQuoteApiErrorResponse(response.status, text);
327
346
  }
328
347
  const data = await response.json();
329
348
  // Normalize single-chain response to match DeBridgeOrderResponse structure
@@ -66,9 +66,13 @@ export declare function calculateRelayMetrics(relayQuote: RelayQuoteResponse): Q
66
66
  */
67
67
  export declare function calculateDebridgeMetrics(debridgeQuote: DeBridgeOrderResponse): QuoteMetrics;
68
68
  /**
69
- * Select the best quote from multiple providers based on retention rate
69
+ * Select the best quote from multiple providers based on retention rate.
70
+ * A quote is viable if it has transaction data (for execution) or estimation data (for display).
71
+ * When both providers return estimation but no tx, we still pick the best so the app can show
72
+ * output and refetch on amount change instead of throwing and blocking further quotes.
73
+ * When both fail, uses relayRejection/debridgeRejection to throw a specific message (e.g. "Insufficient funds").
70
74
  */
71
- export declare function selectBestQuote(relayQuote: RelayQuoteResponse | null, debridgeQuote: DeBridgeOrderResponse | null, srcAmount: string): BridgeQuoteResult;
75
+ export declare function selectBestQuote(relayQuote: RelayQuoteResponse | null, debridgeQuote: DeBridgeOrderResponse | null, srcAmount: string, relayRejection?: unknown, debridgeRejection?: unknown): BridgeQuoteResult;
72
76
  /**
73
77
  * Interpolate retention rate from samples
74
78
  */
@@ -113,18 +113,74 @@ function debridgeHasTransactionData(quote) {
113
113
  return !!(quote.tx && (quote.tx.to || quote.tx.data));
114
114
  }
115
115
  /**
116
- * Select the best quote from multiple providers based on retention rate
116
+ * Check if a relay quote has estimation data (output amount) even without tx.
117
+ * Allows selecting a quote for display when API returned estimation but no tx.
117
118
  */
118
- export function selectBestQuote(relayQuote, debridgeQuote, srcAmount) {
119
- // Only consider providers that have executable transaction data
120
- const viableRelay = relayQuote && relayHasTransactionData(relayQuote) ? relayQuote : null;
121
- const viableDebridge = debridgeQuote && debridgeHasTransactionData(debridgeQuote) ? debridgeQuote : null;
119
+ function relayHasEstimationData(quote) {
120
+ return !!(quote.details?.currencyOut?.amount != null && quote.details?.currencyOut?.amount !== '');
121
+ }
122
+ /**
123
+ * Check if a deBridge quote has estimation data (output amount) even without tx.
124
+ */
125
+ function debridgeHasEstimationData(quote) {
126
+ return !!(quote.estimation?.dstChainTokenOut?.amount != null &&
127
+ quote.estimation.dstChainTokenOut.amount !== '');
128
+ }
129
+ /**
130
+ * Derive a user-facing message from quote provider rejection reasons.
131
+ * When any provider returns INSUFFICIENT_FUNDS / "No UTXOs available to spend", return "Insufficient funds".
132
+ */
133
+ function getQuoteProviderErrorMessage(relayRejection, debridgeRejection) {
134
+ const check = (reason) => {
135
+ if (reason instanceof Error) {
136
+ const msg = reason.message;
137
+ if (msg.includes('Insufficient funds') ||
138
+ msg.includes('INSUFFICIENT_FUNDS') ||
139
+ msg.includes('No UTXOs available to spend')) {
140
+ return true;
141
+ }
142
+ // Parse JSON from "HTTP 400: {...}" style message
143
+ const jsonMatch = msg.match(/\{[\s\S]*\}/);
144
+ if (jsonMatch) {
145
+ try {
146
+ const body = JSON.parse(jsonMatch[0]);
147
+ if (body.errorCode === 'INSUFFICIENT_FUNDS' || (body.message && body.message.includes('UTXO'))) {
148
+ return true;
149
+ }
150
+ }
151
+ catch {
152
+ // ignore
153
+ }
154
+ }
155
+ }
156
+ return false;
157
+ };
158
+ if (check(relayRejection) || check(debridgeRejection)) {
159
+ return 'Insufficient funds';
160
+ }
161
+ return 'All quote providers failed';
162
+ }
163
+ /**
164
+ * Select the best quote from multiple providers based on retention rate.
165
+ * A quote is viable if it has transaction data (for execution) or estimation data (for display).
166
+ * When both providers return estimation but no tx, we still pick the best so the app can show
167
+ * output and refetch on amount change instead of throwing and blocking further quotes.
168
+ * When both fail, uses relayRejection/debridgeRejection to throw a specific message (e.g. "Insufficient funds").
169
+ */
170
+ export function selectBestQuote(relayQuote, debridgeQuote, srcAmount, relayRejection, debridgeRejection) {
171
+ const hasRelayTx = relayQuote && relayHasTransactionData(relayQuote);
172
+ const hasRelayEst = relayQuote && relayHasEstimationData(relayQuote);
173
+ const hasDebridgeTx = debridgeQuote && debridgeHasTransactionData(debridgeQuote);
174
+ const hasDebridgeEst = debridgeQuote && debridgeHasEstimationData(debridgeQuote);
175
+ const viableRelay = (hasRelayTx || hasRelayEst) ? relayQuote : null;
176
+ const viableDebridge = (hasDebridgeTx || hasDebridgeEst) ? debridgeQuote : null;
122
177
  // Calculate metrics only for viable providers
123
178
  const relayMetrics = viableRelay ? calculateRelayMetrics(viableRelay) : null;
124
179
  const debridgeMetrics = viableDebridge ? calculateDebridgeMetrics(viableDebridge) : null;
125
- // Both providers failed or have no executable transactions
180
+ // Both providers failed or have no usable response (no tx and no estimation)
126
181
  if (!viableRelay && !viableDebridge) {
127
- throw new Error('All quote providers failed');
182
+ const message = getQuoteProviderErrorMessage(relayRejection, debridgeRejection);
183
+ throw new Error(message);
128
184
  }
129
185
  // Determine best provider based on retention rate
130
186
  let bestProvider;
@@ -228,9 +284,6 @@ export async function getBridgeQuote(srcChainId, srcToken, srcAmount, dstChainId
228
284
  signal, recipientAddress, // Optional recipient address (required for Solana destinations)
229
285
  sourceAddress, // Optional source chain address (if different from userAddress)
230
286
  forceProvider) {
231
- console.log('recipientAddress', recipientAddress);
232
- console.log('sourceAddress', sourceAddress);
233
- console.log('userAddress', userAddress);
234
287
  // Normalize token addresses for bridge API (handles both EVM and Solana)
235
288
  const srcTokenNorm = normalizeTokenAddressForQuote(srcChainId, srcToken);
236
289
  const dstTokenNorm = normalizeTokenAddressForQuote(dstChainId, dstToken);
@@ -412,7 +465,8 @@ forceProvider) {
412
465
  if (err instanceof Error && err.name !== 'AbortError') {
413
466
  console.warn('Relay quote failed:', err);
414
467
  }
415
- return null;
468
+ // Rethrow so Promise.allSettled captures the reason (e.g. "Insufficient funds")
469
+ throw err;
416
470
  }
417
471
  })(),
418
472
  // deBridge quote (skipped when forceProvider === 'relay')
@@ -494,15 +548,16 @@ forceProvider) {
494
548
  if (err instanceof Error && err.name !== 'AbortError') {
495
549
  console.warn('DeBridge quote failed:', err);
496
550
  }
497
- return null;
551
+ // Rethrow so Promise.allSettled captures the reason (e.g. "Insufficient funds")
552
+ throw err;
498
553
  }
499
554
  })(),
500
555
  ]);
501
556
  const relayQuote = relayResult.status === 'fulfilled' ? relayResult.value : null;
502
557
  const debridgeQuote = debridgeResult.status === 'fulfilled' ? debridgeResult.value : null;
503
- // Select best quote
504
- const result = selectBestQuote(relayQuote, debridgeQuote, srcAmount);
505
- return result;
558
+ const relayRejection = relayResult.status === 'rejected' ? relayResult.reason : undefined;
559
+ const debridgeRejection = debridgeResult.status === 'rejected' ? debridgeResult.reason : undefined;
560
+ return selectBestQuote(relayQuote, debridgeQuote, srcAmount, relayRejection, debridgeRejection);
506
561
  }
507
562
  /**
508
563
  * Convert BridgeQuoteResult to BridgeQuote with transaction data
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@silentswap/sdk",
3
3
  "type": "module",
4
- "version": "0.0.68",
4
+ "version": "0.0.69",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
7
7
  "files": [