@silentswap/sdk 0.1.62 → 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 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
- if (amountIn <= BigInt(srcAmount)) {
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
- const sg_uusdc_target = BigNumber(quote.details.currencyOut.amount)
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,12 +640,17 @@ 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
- if (amountIn <= BigInt(srcAmount)) {
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) {
647
+ // quote.tx can be undefined when DeBridge returns an estimation-only response
648
+ // (e.g. unroutable edge cases, takers without capacity for the route, or non-EVM
649
+ // sources like Solana/Tron where no ERC-20 allowance is needed at all).
627
650
  return [
628
651
  BigInt(quote.estimation.dstChainTokenOut.amount),
629
652
  amountIn,
630
- quote.tx.allowanceTarget || '',
653
+ quote.tx?.allowanceTarget || '',
631
654
  ];
632
655
  }
633
656
  // Calculate new target based on overshoot
@@ -638,7 +661,14 @@ async function solveDebridgeUsdcAmount(srcChainId, srcToken, srcAmount, userAddr
638
661
  .minus(srcAmount)
639
662
  .times(yg_utoken_price)
640
663
  .div(yg_uusdc_price);
641
- const sg_uusdc_target = BigNumber(quote.estimation.dstChainTokenOut.amount)
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())
642
672
  .minus(yg_uusdc_over)
643
673
  .toFixed(0);
644
674
  if (BigInt(sg_uusdc_target) >= targetUsdcOut) {
@@ -711,12 +741,17 @@ async function solveDebridgeSingleChainUsdcAmount(chainId, srcToken, srcAmount,
711
741
  throw new Error(`Price impact too high: ${impactPercent.toFixed(2)}%`);
712
742
  }
713
743
  const amountIn = BigInt(quote.estimation.srcChainTokenIn.amount);
714
- // Amount in is within our budget
715
- if (amountIn <= BigInt(srcAmount)) {
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) {
748
+ // quote.tx can be undefined when DeBridge returns an estimation-only response
749
+ // (e.g. unroutable edge cases, takers without capacity for the route, or non-EVM
750
+ // sources like Solana/Tron where no ERC-20 allowance is needed at all).
716
751
  return [
717
752
  BigInt(quote.estimation.dstChainTokenOut.amount),
718
753
  amountIn,
719
- quote.tx.allowanceTarget || '',
754
+ quote.tx?.allowanceTarget || '',
720
755
  ];
721
756
  }
722
757
  // Calculate new target based on overshoot
@@ -727,7 +762,14 @@ async function solveDebridgeSingleChainUsdcAmount(chainId, srcToken, srcAmount,
727
762
  .minus(srcAmount)
728
763
  .times(yg_utoken_price)
729
764
  .div(yg_uusdc_price);
730
- const sg_uusdc_target = BigNumber(quote.estimation.dstChainTokenOut.amount)
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())
731
773
  .minus(yg_uusdc_over)
732
774
  .toFixed(0);
733
775
  if (BigInt(sg_uusdc_target) >= targetUsdcOut) {
@@ -783,6 +825,7 @@ forceProvider) {
783
825
  const debridgeData = debridgeResult.status === 'fulfilled' ? debridgeResult.value : null;
784
826
  // Both failed — surface the most relevant inner error message
785
827
  if (!relayData && !debridgeData) {
828
+ debugger;
786
829
  const errors = [
787
830
  relayResult.status === 'rejected' ? relayResult.reason : null,
788
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 is fresh
53
- // — TrustWallet silently drops signTypedData when the client's chain scope
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 recreate walletClient from the provider after a chain change attempt.
57
- // Critical for WalletConnect: the old walletClient's chain scope (e.g. eip155:43114)
58
- // doesn't match the active chain, so signing requests get silently dropped
59
- // by the mobile wallet. This applies both after a successful switch AND a failed
60
- // one the provider's internal state may still have updated even if switchChain threw.
61
- console.log('[ensureChain] recreating walletClient from provider ...', { switchSucceeded });
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: 1;
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: EIP712_DOMAIN_ORDER_DEFAULT,
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.',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@silentswap/sdk",
3
3
  "type": "module",
4
- "version": "0.1.62",
4
+ "version": "0.1.64",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
7
7
  "files": [