@silentswap/sdk 0.0.67 → 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.d.ts +6 -0
- package/dist/bridge.js +22 -3
- package/dist/quote-utils.d.ts +14 -3
- package/dist/quote-utils.js +97 -21
- package/package.json +1 -1
package/dist/bridge.d.ts
CHANGED
|
@@ -202,6 +202,12 @@ export interface DeBridgeOrderParams {
|
|
|
202
202
|
srcChainOrderAuthorityAddress?: string;
|
|
203
203
|
srcChainRefundAddress?: string;
|
|
204
204
|
dstChainOrderAuthorityAddress?: string;
|
|
205
|
+
referralCode?: number;
|
|
206
|
+
additionalTakerRewardBps?: number;
|
|
207
|
+
allowedTaker?: string;
|
|
208
|
+
deBridgeApp?: string;
|
|
209
|
+
ptp?: boolean;
|
|
210
|
+
srcChainPriorityLevel?: 'normal' | 'aggressive';
|
|
205
211
|
}
|
|
206
212
|
export interface DeBridgeOrderResponse {
|
|
207
213
|
tx: {
|
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
|
|
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
|
|
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
|
|
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
|
package/dist/quote-utils.d.ts
CHANGED
|
@@ -66,21 +66,32 @@ 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
|
*/
|
|
75
79
|
export declare function interpolateSamples(samples: EstimateSample[], usdValue: number, marginHi?: number): number;
|
|
80
|
+
/**
|
|
81
|
+
* When set, only the specified provider is used for quotes (for testing).
|
|
82
|
+
*/
|
|
83
|
+
export type ForceBridgeProvider = 'relay' | 'debridge';
|
|
76
84
|
/**
|
|
77
85
|
* Get quote for cross-chain swap from multiple providers
|
|
78
86
|
* This is the core logic shared between React and Vue
|
|
79
87
|
* @param recipientAddress - Optional recipient address (required for Solana destinations)
|
|
88
|
+
* @param sourceAddress - Optional source chain address (if different from userAddress)
|
|
89
|
+
* @param forceProvider - Optional: use only this provider (for testing; skips the other)
|
|
80
90
|
*/
|
|
81
91
|
export declare function getBridgeQuote(srcChainId: number, srcToken: string, srcAmount: string, dstChainId: number, dstToken: string, userAddress: `0x${string}` | string, // Can be EVM (0x) or Solana (base58) address
|
|
82
92
|
signal?: AbortSignal, recipientAddress?: string, // Optional recipient address (required for Solana destinations)
|
|
83
|
-
sourceAddress?: string)
|
|
93
|
+
sourceAddress?: string, // Optional source chain address (if different from userAddress)
|
|
94
|
+
forceProvider?: ForceBridgeProvider): Promise<BridgeQuoteResult>;
|
|
84
95
|
/**
|
|
85
96
|
* Convert BridgeQuoteResult to BridgeQuote with transaction data
|
|
86
97
|
* Extracts transactions from the raw response based on provider
|
package/dist/quote-utils.js
CHANGED
|
@@ -113,18 +113,74 @@ function debridgeHasTransactionData(quote) {
|
|
|
113
113
|
return !!(quote.tx && (quote.tx.to || quote.tx.data));
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
|
-
*
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
180
|
+
// Both providers failed or have no usable response (no tx and no estimation)
|
|
126
181
|
if (!viableRelay && !viableDebridge) {
|
|
127
|
-
|
|
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;
|
|
@@ -221,14 +277,13 @@ export function interpolateSamples(samples, usdValue, marginHi = Infinity) {
|
|
|
221
277
|
* Get quote for cross-chain swap from multiple providers
|
|
222
278
|
* This is the core logic shared between React and Vue
|
|
223
279
|
* @param recipientAddress - Optional recipient address (required for Solana destinations)
|
|
280
|
+
* @param sourceAddress - Optional source chain address (if different from userAddress)
|
|
281
|
+
* @param forceProvider - Optional: use only this provider (for testing; skips the other)
|
|
224
282
|
*/
|
|
225
283
|
export async function getBridgeQuote(srcChainId, srcToken, srcAmount, dstChainId, dstToken, userAddress, // Can be EVM (0x) or Solana (base58) address
|
|
226
284
|
signal, recipientAddress, // Optional recipient address (required for Solana destinations)
|
|
227
|
-
sourceAddress // Optional source chain address (if different from userAddress)
|
|
228
|
-
) {
|
|
229
|
-
console.log('recipientAddress', recipientAddress);
|
|
230
|
-
console.log('sourceAddress', sourceAddress);
|
|
231
|
-
console.log('userAddress', userAddress);
|
|
285
|
+
sourceAddress, // Optional source chain address (if different from userAddress)
|
|
286
|
+
forceProvider) {
|
|
232
287
|
// Normalize token addresses for bridge API (handles both EVM and Solana)
|
|
233
288
|
const srcTokenNorm = normalizeTokenAddressForQuote(srcChainId, srcToken);
|
|
234
289
|
const dstTokenNorm = normalizeTokenAddressForQuote(dstChainId, dstToken);
|
|
@@ -300,10 +355,14 @@ sourceAddress // Optional source chain address (if different from userAddress)
|
|
|
300
355
|
relayUserAddress = DEAD_ADDRESS;
|
|
301
356
|
}
|
|
302
357
|
}
|
|
303
|
-
// Fetch quotes from both providers in parallel
|
|
358
|
+
// Fetch quotes from both providers in parallel (skip one if forceProvider is set)
|
|
359
|
+
const useRelay = forceProvider !== 'debridge';
|
|
360
|
+
const useDebridge = forceProvider !== 'relay';
|
|
304
361
|
const [relayResult, debridgeResult] = await Promise.allSettled([
|
|
305
|
-
// Relay.link quote
|
|
362
|
+
// Relay.link quote (skipped when forceProvider === 'debridge')
|
|
306
363
|
(async () => {
|
|
364
|
+
if (!useRelay)
|
|
365
|
+
return null;
|
|
307
366
|
try {
|
|
308
367
|
// Map chain IDs to Relay chain IDs if needed
|
|
309
368
|
// For Solana, Relay.link uses N_RELAY_CHAIN_ID_SOLANA (792703809)
|
|
@@ -406,11 +465,14 @@ sourceAddress // Optional source chain address (if different from userAddress)
|
|
|
406
465
|
if (err instanceof Error && err.name !== 'AbortError') {
|
|
407
466
|
console.warn('Relay quote failed:', err);
|
|
408
467
|
}
|
|
409
|
-
|
|
468
|
+
// Rethrow so Promise.allSettled captures the reason (e.g. "Insufficient funds")
|
|
469
|
+
throw err;
|
|
410
470
|
}
|
|
411
471
|
})(),
|
|
412
|
-
// deBridge quote
|
|
472
|
+
// deBridge quote (skipped when forceProvider === 'relay')
|
|
413
473
|
(async () => {
|
|
474
|
+
if (!useDebridge)
|
|
475
|
+
return null;
|
|
414
476
|
try {
|
|
415
477
|
// Skip deBridge for Bitcoin - Bitcoin only supports relay
|
|
416
478
|
if (isSrcBitcoin || isDestBitcoin) {
|
|
@@ -462,9 +524,22 @@ sourceAddress // Optional source chain address (if different from userAddress)
|
|
|
462
524
|
}
|
|
463
525
|
}
|
|
464
526
|
else {
|
|
465
|
-
// Both source and destination are EVM
|
|
527
|
+
// Both source and destination are EVM
|
|
528
|
+
// API requires dstChainTokenOutRecipient, srcChainOrderAuthorityAddress, dstChainOrderAuthorityAddress for transaction construction
|
|
529
|
+
debridgeParams.dstChainTokenOutRecipient = userAddress;
|
|
530
|
+
debridgeParams.srcChainOrderAuthorityAddress = userAddress;
|
|
531
|
+
debridgeParams.dstChainOrderAuthorityAddress = userAddress;
|
|
532
|
+
debridgeParams.srcChainRefundAddress = userAddress;
|
|
533
|
+
debridgeParams.senderAddress = userAddress;
|
|
466
534
|
debridgeParams.dstChainTokenOutAmount = 'auto';
|
|
467
535
|
debridgeParams.enableEstimate = true;
|
|
536
|
+
// Optional params matching deSwap app for consistent API behavior
|
|
537
|
+
debridgeParams.additionalTakerRewardBps = 0;
|
|
538
|
+
debridgeParams.deBridgeApp = 'DESWAP';
|
|
539
|
+
debridgeParams.ptp = false;
|
|
540
|
+
debridgeParams.srcChainPriorityLevel = 'normal';
|
|
541
|
+
// Use standard API param names (senderAddress, dstChainTokenOutRecipient); omit account
|
|
542
|
+
// delete debridgeParams.account;
|
|
468
543
|
}
|
|
469
544
|
const quote = await fetchDebridgeOrder(debridgeParams, signal);
|
|
470
545
|
return quote;
|
|
@@ -473,15 +548,16 @@ sourceAddress // Optional source chain address (if different from userAddress)
|
|
|
473
548
|
if (err instanceof Error && err.name !== 'AbortError') {
|
|
474
549
|
console.warn('DeBridge quote failed:', err);
|
|
475
550
|
}
|
|
476
|
-
|
|
551
|
+
// Rethrow so Promise.allSettled captures the reason (e.g. "Insufficient funds")
|
|
552
|
+
throw err;
|
|
477
553
|
}
|
|
478
554
|
})(),
|
|
479
555
|
]);
|
|
480
556
|
const relayQuote = relayResult.status === 'fulfilled' ? relayResult.value : null;
|
|
481
557
|
const debridgeQuote = debridgeResult.status === 'fulfilled' ? debridgeResult.value : null;
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
return
|
|
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);
|
|
485
561
|
}
|
|
486
562
|
/**
|
|
487
563
|
* Convert BridgeQuoteResult to BridgeQuote with transaction data
|