@silentswap/sdk 0.1.63 → 0.1.64
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 +47 -10
- package/dist/chain.js +11 -12
- package/dist/order.d.ts +5 -0
- package/dist/order.js +14 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +2 -2
- package/package.json +1 -1
package/dist/bridge.js
CHANGED
|
@@ -349,7 +349,6 @@ export async function fetchDebridgeOrder(params, signal) {
|
|
|
349
349
|
* Note: DLN does not support same-chain swaps via create-tx endpoint
|
|
350
350
|
*/
|
|
351
351
|
async function fetchDebridgeSingleChainOrder(params, signal) {
|
|
352
|
-
console.log('fetchDebridgeSingleChainOrder');
|
|
353
352
|
// Build query parameters for single-chain swap endpoint
|
|
354
353
|
const queryParams = new URLSearchParams({
|
|
355
354
|
chainId: params.srcChainId.toString(),
|
|
@@ -359,6 +358,16 @@ async function fetchDebridgeSingleChainOrder(params, signal) {
|
|
|
359
358
|
tokenOutRecipient: params.dstChainTokenOutRecipient || params.account || '',
|
|
360
359
|
senderAddress: params.senderAddress || params.account || '',
|
|
361
360
|
});
|
|
361
|
+
// EXACT_OUTPUT support: when caller passes a concrete dstChainTokenOutAmount
|
|
362
|
+
// (and srcChainTokenInAmount === 'auto'), forward it as `tokenOutAmount`.
|
|
363
|
+
// Without this the convergence loop in solveDebridgeSingleChainUsdcAmount
|
|
364
|
+
// sends `tokenInAmount=auto` with no target → deBridge returns
|
|
365
|
+
// INTERNAL_SERVER_ERROR on every iteration after the first, so the loop
|
|
366
|
+
// never finds the optimal price and surfaces as "Failed to find optimal
|
|
367
|
+
// bridge price".
|
|
368
|
+
if (params.dstChainTokenOutAmount && params.dstChainTokenOutAmount !== 'auto') {
|
|
369
|
+
queryParams.set('tokenOutAmount', params.dstChainTokenOutAmount);
|
|
370
|
+
}
|
|
362
371
|
// Add optional parameters if provided
|
|
363
372
|
if (params.prependOperatingExpenses !== undefined) {
|
|
364
373
|
queryParams.set('prependOperatingExpenses', String(params.prependOperatingExpenses));
|
|
@@ -520,8 +529,11 @@ async function solveRelayUsdcAmount(srcChainId, srcToken, srcAmount, userAddress
|
|
|
520
529
|
if (impactPercent > maxImpactPercent) {
|
|
521
530
|
throw new Error(`Price impact too high: ${impactPercent.toFixed(2)}%`);
|
|
522
531
|
}
|
|
523
|
-
// Amount in is within our budget
|
|
524
|
-
|
|
532
|
+
// Amount in is within our budget — accept up to 0.5% over to absorb
|
|
533
|
+
// rounding noise from the bridge's internal pricing (otherwise the loop
|
|
534
|
+
// bounces on quotes that are e.g. 0.1% over, until convergence breaks).
|
|
535
|
+
const budgetWithTolerance = BigInt(srcAmount) * 1005n / 1000n;
|
|
536
|
+
if (amountIn <= budgetWithTolerance) {
|
|
525
537
|
console.log('[SilentSwap:SolveRelay] Found optimal amount', {
|
|
526
538
|
usdcOut: amountOut.toString(),
|
|
527
539
|
amountIn: amountIn.toString(),
|
|
@@ -536,7 +548,13 @@ async function solveRelayUsdcAmount(srcChainId, srcToken, srcAmount, userAddress
|
|
|
536
548
|
.minus(srcAmount)
|
|
537
549
|
.times(yg_utoken_price)
|
|
538
550
|
.div(yg_uusdc_price);
|
|
539
|
-
|
|
551
|
+
// Anchor the next target on the *current target* (what we asked for),
|
|
552
|
+
// not on `currencyOut.amount` (what the bridge's quote estimates we'd
|
|
553
|
+
// get). For some endpoints — notably deBridge single-chain —
|
|
554
|
+
// `amount > requested target` because of a built-in slippage buffer,
|
|
555
|
+
// and using it as the base produces new_target > previous_target on
|
|
556
|
+
// tight iterations, breaking convergence.
|
|
557
|
+
const sg_uusdc_target = BigNumber(targetUsdcOut.toString())
|
|
540
558
|
.minus(yg_uusdc_over)
|
|
541
559
|
.toFixed(0);
|
|
542
560
|
console.log(`[SilentSwap:SolveRelay] Reducing target`, {
|
|
@@ -622,8 +640,10 @@ async function solveDebridgeUsdcAmount(srcChainId, srcToken, srcAmount, userAddr
|
|
|
622
640
|
throw new Error(`Price impact too high: ${impactPercent.toFixed(2)}%`);
|
|
623
641
|
}
|
|
624
642
|
const amountIn = BigInt(quote.estimation.srcChainTokenIn.amount);
|
|
625
|
-
// Amount in is within our budget
|
|
626
|
-
|
|
643
|
+
// Amount in is within our budget — accept up to 0.5% over to absorb
|
|
644
|
+
// rounding noise from deBridge's internal pricing.
|
|
645
|
+
const budgetWithTolerance = BigInt(srcAmount) * 1005n / 1000n;
|
|
646
|
+
if (amountIn <= budgetWithTolerance) {
|
|
627
647
|
// quote.tx can be undefined when DeBridge returns an estimation-only response
|
|
628
648
|
// (e.g. unroutable edge cases, takers without capacity for the route, or non-EVM
|
|
629
649
|
// sources like Solana/Tron where no ERC-20 allowance is needed at all).
|
|
@@ -641,7 +661,14 @@ async function solveDebridgeUsdcAmount(srcChainId, srcToken, srcAmount, userAddr
|
|
|
641
661
|
.minus(srcAmount)
|
|
642
662
|
.times(yg_utoken_price)
|
|
643
663
|
.div(yg_uusdc_price);
|
|
644
|
-
|
|
664
|
+
// Anchor on the *requested target* (targetUsdcOut), not the bridge's
|
|
665
|
+
// estimated `amount`. deBridge's single-chain endpoint returns
|
|
666
|
+
// `amount > minAmount` (built-in slippage buffer), which makes the
|
|
667
|
+
// current iteration's `dstChainTokenOut.amount` larger than the
|
|
668
|
+
// requested target. Subtracting a small overshoot from that larger
|
|
669
|
+
// value yields a new target that's still bigger than the previous
|
|
670
|
+
// target, and the convergence guard fires.
|
|
671
|
+
const sg_uusdc_target = BigNumber(targetUsdcOut.toString())
|
|
645
672
|
.minus(yg_uusdc_over)
|
|
646
673
|
.toFixed(0);
|
|
647
674
|
if (BigInt(sg_uusdc_target) >= targetUsdcOut) {
|
|
@@ -714,8 +741,10 @@ async function solveDebridgeSingleChainUsdcAmount(chainId, srcToken, srcAmount,
|
|
|
714
741
|
throw new Error(`Price impact too high: ${impactPercent.toFixed(2)}%`);
|
|
715
742
|
}
|
|
716
743
|
const amountIn = BigInt(quote.estimation.srcChainTokenIn.amount);
|
|
717
|
-
// Amount in is within our budget
|
|
718
|
-
|
|
744
|
+
// Amount in is within our budget — accept up to 0.5% over to absorb
|
|
745
|
+
// rounding noise from deBridge's internal pricing.
|
|
746
|
+
const budgetWithTolerance = BigInt(srcAmount) * 1005n / 1000n;
|
|
747
|
+
if (amountIn <= budgetWithTolerance) {
|
|
719
748
|
// quote.tx can be undefined when DeBridge returns an estimation-only response
|
|
720
749
|
// (e.g. unroutable edge cases, takers without capacity for the route, or non-EVM
|
|
721
750
|
// sources like Solana/Tron where no ERC-20 allowance is needed at all).
|
|
@@ -733,7 +762,14 @@ async function solveDebridgeSingleChainUsdcAmount(chainId, srcToken, srcAmount,
|
|
|
733
762
|
.minus(srcAmount)
|
|
734
763
|
.times(yg_utoken_price)
|
|
735
764
|
.div(yg_uusdc_price);
|
|
736
|
-
|
|
765
|
+
// Anchor on the *requested target* (targetUsdcOut), not the bridge's
|
|
766
|
+
// estimated `amount`. deBridge's single-chain endpoint returns
|
|
767
|
+
// `amount > minAmount` (built-in slippage buffer), which makes the
|
|
768
|
+
// current iteration's `dstChainTokenOut.amount` larger than the
|
|
769
|
+
// requested target. Subtracting a small overshoot from that larger
|
|
770
|
+
// value yields a new target that's still bigger than the previous
|
|
771
|
+
// target, and the convergence guard fires.
|
|
772
|
+
const sg_uusdc_target = BigNumber(targetUsdcOut.toString())
|
|
737
773
|
.minus(yg_uusdc_over)
|
|
738
774
|
.toFixed(0);
|
|
739
775
|
if (BigInt(sg_uusdc_target) >= targetUsdcOut) {
|
|
@@ -789,6 +825,7 @@ forceProvider) {
|
|
|
789
825
|
const debridgeData = debridgeResult.status === 'fulfilled' ? debridgeResult.value : null;
|
|
790
826
|
// Both failed — surface the most relevant inner error message
|
|
791
827
|
if (!relayData && !debridgeData) {
|
|
828
|
+
debugger;
|
|
792
829
|
const errors = [
|
|
793
830
|
relayResult.status === 'rejected' ? relayResult.reason : null,
|
|
794
831
|
debridgeResult.status === 'rejected' ? debridgeResult.reason : null,
|
package/dist/chain.js
CHANGED
|
@@ -31,12 +31,10 @@ export async function ensureChain(chainId, walletClient, connector, options) {
|
|
|
31
31
|
}
|
|
32
32
|
console.log('[ensureChain] currentChainId:', currentChainId, 'targetChainId:', chainId, 'needsSwitch:', currentChainId !== chainId);
|
|
33
33
|
if (currentChainId !== chainId) {
|
|
34
|
-
let switchSucceeded = false;
|
|
35
34
|
try {
|
|
36
35
|
// Try to switch chain
|
|
37
36
|
console.log('[ensureChain] attempting switchChain ...', { hasSwitchChain: !!connector.switchChain });
|
|
38
37
|
await connector.switchChain?.({ chainId });
|
|
39
|
-
switchSucceeded = true;
|
|
40
38
|
console.log('[ensureChain] switchChain succeeded');
|
|
41
39
|
}
|
|
42
40
|
catch (switchError) {
|
|
@@ -49,16 +47,17 @@ export async function ensureChain(chainId, walletClient, connector, options) {
|
|
|
49
47
|
}
|
|
50
48
|
// Non-required: chain switch failed (e.g. Trust Wallet via WalletConnect
|
|
51
49
|
// doesn't support programmatic chain switching). Still recreate the
|
|
52
|
-
// walletClient from the provider so the WalletConnect transport
|
|
53
|
-
// — TrustWallet silently drops signTypedData when the client's
|
|
54
|
-
// doesn't match its active session chain.
|
|
50
|
+
// walletClient from the provider below so the WalletConnect transport
|
|
51
|
+
// is fresh — TrustWallet silently drops signTypedData when the client's
|
|
52
|
+
// chain scope doesn't match its active session chain.
|
|
55
53
|
}
|
|
56
|
-
// Always
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// by
|
|
60
|
-
//
|
|
61
|
-
|
|
54
|
+
// Always rebind the walletClient if its `chain` config doesn't match the target.
|
|
55
|
+
// viem's signTypedData / sendTransaction asserts that walletClient.chain.id matches
|
|
56
|
+
// the wallet's reported eth_chainId — if the wallet was already switched to `chainId`
|
|
57
|
+
// (e.g. by an earlier ensureChain call) but the closure's walletClient was created
|
|
58
|
+
// by wagmi with a different chain, signing throws "Provided chainId X must match
|
|
59
|
+
// active chainId Y". Recreating the client here keeps the two in sync.
|
|
60
|
+
console.log('[ensureChain] rebinding walletClient to chain', chainId, '(was', walletClient.chain?.id, ')');
|
|
62
61
|
const provider = await connector.getProvider();
|
|
63
62
|
const chain = getChainById(chainId);
|
|
64
63
|
if (!chain) {
|
|
@@ -78,7 +77,7 @@ export async function ensureChain(chainId, walletClient, connector, options) {
|
|
|
78
77
|
});
|
|
79
78
|
return newClient;
|
|
80
79
|
}
|
|
81
|
-
console.log('[ensureChain] already on correct chain, returning existing walletClient');
|
|
80
|
+
console.log('[ensureChain] already on correct chain with matching client, returning existing walletClient');
|
|
82
81
|
return walletClient;
|
|
83
82
|
}
|
|
84
83
|
/**
|
package/dist/order.d.ts
CHANGED
|
@@ -276,5 +276,10 @@ export declare const EIP712_DOMAIN_ORDER_DEFAULT: {
|
|
|
276
276
|
readonly version: "1";
|
|
277
277
|
readonly chainId: 1;
|
|
278
278
|
};
|
|
279
|
+
export declare const EIP712_DOMAIN_WALLET_GENERATION: {
|
|
280
|
+
readonly name: "SilentSwap v2";
|
|
281
|
+
readonly version: "1";
|
|
282
|
+
readonly chainId: 43114;
|
|
283
|
+
};
|
|
279
284
|
export declare function quoteResponseToEip712Document(quoteResponse: QuoteResponse): TypedDataDocument<typeof EIP712_DOMAIN_ORDER_DEFAULT>;
|
|
280
285
|
export {};
|
package/dist/order.js
CHANGED
|
@@ -94,11 +94,25 @@ export const EIP712_TYPES_WALLET_GENERATION = {
|
|
|
94
94
|
{ name: 'token', type: 'string' },
|
|
95
95
|
],
|
|
96
96
|
};
|
|
97
|
+
// Orders must be signed with chainId === 1 (Ethereum mainnet) — the backend
|
|
98
|
+
// rejects any other chainId with "eip712Domain value is invalid or not supported.
|
|
99
|
+
// expected mainnet chain ID 1". Wallet-generation typed data uses a different
|
|
100
|
+
// domain (EIP712_DOMAIN_WALLET_GENERATION below) and is not subject to that
|
|
101
|
+
// constraint.
|
|
97
102
|
export const EIP712_DOMAIN_ORDER_DEFAULT = {
|
|
98
103
|
name: 'SilentSwap v2',
|
|
99
104
|
version: '1',
|
|
100
105
|
chainId: 1,
|
|
101
106
|
};
|
|
107
|
+
// Wallet-generation typed data is signed locally to derive entropy — the chainId
|
|
108
|
+
// only matters for the wallet's cross-chain replay-protection check at sign time.
|
|
109
|
+
// Pinned to Avalanche (43114) so the user doesn't have to leave their working
|
|
110
|
+
// chain just to derive the wallet.
|
|
111
|
+
export const EIP712_DOMAIN_WALLET_GENERATION = {
|
|
112
|
+
name: 'SilentSwap v2',
|
|
113
|
+
version: '1',
|
|
114
|
+
chainId: 43114,
|
|
115
|
+
};
|
|
102
116
|
export function quoteResponseToEip712Document(quoteResponse) {
|
|
103
117
|
return {
|
|
104
118
|
domain: EIP712_DOMAIN_ORDER_DEFAULT,
|
package/dist/sdk.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ export declare function createEip712DocForWalletGeneration(scope: string, token:
|
|
|
156
156
|
domain: {
|
|
157
157
|
readonly name: "SilentSwap v2";
|
|
158
158
|
readonly version: "1";
|
|
159
|
-
readonly chainId:
|
|
159
|
+
readonly chainId: 43114;
|
|
160
160
|
};
|
|
161
161
|
primaryType: "WalletGeneration";
|
|
162
162
|
message: {
|
package/dist/sdk.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSiweMessage } from 'viem/siwe';
|
|
2
|
-
import { EIP712_DOMAIN_ORDER_DEFAULT, EIP712_TYPES_ORDER, EIP712_TYPES_WALLET_GENERATION } from './order.js';
|
|
2
|
+
import { EIP712_DOMAIN_ORDER_DEFAULT, EIP712_DOMAIN_WALLET_GENERATION, EIP712_TYPES_ORDER, EIP712_TYPES_WALLET_GENERATION } from './order.js';
|
|
3
3
|
/**
|
|
4
4
|
* Creates a SIWE message for authentication with SilentSwap
|
|
5
5
|
* @param user - user's EVM address
|
|
@@ -49,7 +49,7 @@ export function createEip712DocForOrder(quote) {
|
|
|
49
49
|
export function createEip712DocForWalletGeneration(scope, token) {
|
|
50
50
|
return {
|
|
51
51
|
types: EIP712_TYPES_WALLET_GENERATION,
|
|
52
|
-
domain:
|
|
52
|
+
domain: EIP712_DOMAIN_WALLET_GENERATION,
|
|
53
53
|
primaryType: 'WalletGeneration',
|
|
54
54
|
message: {
|
|
55
55
|
description: 'Securely create a temporary, anonymous wallet for SilentSwap use only.',
|