@shapeshiftoss/swapper 19.0.0 → 20.1.0

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.
Files changed (165) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/exactOutputFiltering.test.d.ts +1 -0
  3. package/dist/cjs/exactOutputFiltering.test.js +70 -0
  4. package/dist/cjs/swapper.d.ts +3 -3
  5. package/dist/cjs/swapper.js +29 -6
  6. package/dist/cjs/swappers/AcrossSwapper/utils/getAcrossStepData.d.ts +1 -0
  7. package/dist/cjs/swappers/AcrossSwapper/utils/getAcrossStepData.js +2 -2
  8. package/dist/cjs/swappers/AcrossSwapper/utils/getAcrossTradeContext.js +1 -0
  9. package/dist/cjs/swappers/BebopSwapper/utils/getBebopStepData.d.ts +1 -0
  10. package/dist/cjs/swappers/BebopSwapper/utils/getBebopStepData.js +2 -2
  11. package/dist/cjs/swappers/BebopSwapper/utils/getBebopTradeContext.js +1 -0
  12. package/dist/cjs/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.js +5 -1
  13. package/dist/cjs/swappers/BobGatewaySwapper/swapperApi/getTradeRate.js +11 -2
  14. package/dist/cjs/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.d.ts +2 -1
  15. package/dist/cjs/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.js +2 -1
  16. package/dist/cjs/swappers/BobGatewaySwapper/utils/helpers.d.ts +2 -1
  17. package/dist/cjs/swappers/BobGatewaySwapper/utils/helpers.js +2 -1
  18. package/dist/cjs/swappers/ChainflipSwapper/ChainflipSwapper.js +1 -0
  19. package/dist/cjs/swappers/ChainflipSwapper/checkTradeStatus.test.d.ts +1 -0
  20. package/dist/cjs/swappers/ChainflipSwapper/checkTradeStatus.test.js +48 -0
  21. package/dist/cjs/swappers/ChainflipSwapper/endpoints.js +5 -2
  22. package/dist/cjs/swappers/ChainflipSwapper/types.d.ts +3 -0
  23. package/dist/cjs/swappers/DebridgeSwapper/utils/getDebridgeStepData.d.ts +1 -0
  24. package/dist/cjs/swappers/DebridgeSwapper/utils/getDebridgeStepData.js +2 -2
  25. package/dist/cjs/swappers/DebridgeSwapper/utils/getDebridgeTradeContext.js +1 -0
  26. package/dist/cjs/swappers/NearIntentsSwapper/NearIntentsSwapper.js +1 -0
  27. package/dist/cjs/swappers/NearIntentsSwapper/checkTradeStatus.test.d.ts +1 -0
  28. package/dist/cjs/swappers/NearIntentsSwapper/checkTradeStatus.test.js +52 -0
  29. package/dist/cjs/swappers/NearIntentsSwapper/constants.d.ts +1 -1
  30. package/dist/cjs/swappers/NearIntentsSwapper/constants.js +4 -3
  31. package/dist/cjs/swappers/NearIntentsSwapper/endpoints.js +4 -1
  32. package/dist/cjs/swappers/NearIntentsSwapper/swapperApi/getTradeQuote.d.ts +2 -1
  33. package/dist/cjs/swappers/NearIntentsSwapper/swapperApi/getTradeQuote.js +14 -9
  34. package/dist/cjs/swappers/NearIntentsSwapper/swapperApi/getTradeRate.d.ts +2 -1
  35. package/dist/cjs/swappers/NearIntentsSwapper/swapperApi/getTradeRate.js +14 -9
  36. package/dist/cjs/swappers/NearIntentsSwapper/types.d.ts +3 -1
  37. package/dist/cjs/swappers/NearIntentsSwapper/utils/exactOutput.test.d.ts +1 -0
  38. package/dist/cjs/swappers/NearIntentsSwapper/utils/exactOutput.test.js +61 -0
  39. package/dist/cjs/swappers/NearIntentsSwapper/utils/getNearIntentsTradeContext.d.ts +2 -2
  40. package/dist/cjs/swappers/NearIntentsSwapper/utils/getNearIntentsTradeContext.js +4 -2
  41. package/dist/cjs/swappers/NearIntentsSwapper/utils/helpers.d.ts +5 -5
  42. package/dist/cjs/swappers/NearIntentsSwapper/utils/helpers.js +30 -26
  43. package/dist/cjs/swappers/NearIntentsSwapper/utils/quoteDeadline.test.d.ts +1 -0
  44. package/dist/cjs/swappers/NearIntentsSwapper/utils/quoteDeadline.test.js +28 -0
  45. package/dist/cjs/swappers/PortalsSwapper/endpoints.js +51 -47
  46. package/dist/cjs/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.js +1 -1
  47. package/dist/cjs/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.js +1 -1
  48. package/dist/cjs/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.d.ts +0 -1
  49. package/dist/cjs/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.js +3 -6
  50. package/dist/cjs/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.js +6 -3
  51. package/dist/cjs/swappers/PortalsSwapper/utils/fetchSquidStatus.d.ts +0 -1
  52. package/dist/cjs/swappers/PortalsSwapper/utils/fetchSquidStatus.js +1 -8
  53. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsStepData.d.ts +1 -0
  54. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsStepData.js +16 -8
  55. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsStepData.test.d.ts +1 -0
  56. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsStepData.test.js +70 -0
  57. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.d.ts +3 -2
  58. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.js +34 -16
  59. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.d.ts +1 -0
  60. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.js +93 -0
  61. package/dist/cjs/swappers/RelaySwapper/endpoints.js +9 -18
  62. package/dist/cjs/swappers/RelaySwapper/getTradeQuote/getTradeQuote.d.ts +2 -1
  63. package/dist/cjs/swappers/RelaySwapper/getTradeQuote/getTradeQuote.js +5 -2
  64. package/dist/cjs/swappers/RelaySwapper/getTradeRate/getTradeRate.d.ts +2 -1
  65. package/dist/cjs/swappers/RelaySwapper/getTradeRate/getTradeRate.js +5 -2
  66. package/dist/cjs/swappers/RelaySwapper/utils/getRelayTradeContext.d.ts +2 -2
  67. package/dist/cjs/swappers/RelaySwapper/utils/getRelayTradeContext.js +22 -10
  68. package/dist/cjs/swappers/RelaySwapper/utils/helpers.d.ts +2 -2
  69. package/dist/cjs/swappers/RelaySwapper/utils/relayStatus.d.ts +8 -0
  70. package/dist/cjs/swappers/RelaySwapper/utils/relayStatus.js +39 -0
  71. package/dist/cjs/swappers/RelaySwapper/utils/types.d.ts +18 -1
  72. package/dist/cjs/swappers/SunioSwapper/utils/getSunioStepData.d.ts +1 -0
  73. package/dist/cjs/swappers/SunioSwapper/utils/getSunioStepData.js +2 -2
  74. package/dist/cjs/swappers/SunioSwapper/utils/getSunioTradeContext.js +6 -1
  75. package/dist/cjs/swappers/ThorchainSwapper/generated/generatedThorLongtailTokens.json +1 -0
  76. package/dist/cjs/types.d.ts +21 -2
  77. package/dist/cjs/types.js +2 -0
  78. package/dist/cjs/utils/cowswap/index.d.ts +4 -1
  79. package/dist/cjs/utils/cowswap/index.js +5 -1
  80. package/dist/cjs/utils/helpers.d.ts +9 -0
  81. package/dist/cjs/utils/helpers.js +23 -1
  82. package/dist/cjs/utils/helpers.test.js +41 -0
  83. package/dist/esm/.tsbuildinfo +1 -1
  84. package/dist/esm/exactOutputFiltering.test.d.ts +1 -0
  85. package/dist/esm/exactOutputFiltering.test.js +68 -0
  86. package/dist/esm/swapper.d.ts +3 -3
  87. package/dist/esm/swapper.js +29 -6
  88. package/dist/esm/swappers/AcrossSwapper/utils/getAcrossStepData.d.ts +1 -0
  89. package/dist/esm/swappers/AcrossSwapper/utils/getAcrossStepData.js +2 -2
  90. package/dist/esm/swappers/AcrossSwapper/utils/getAcrossTradeContext.js +1 -0
  91. package/dist/esm/swappers/BebopSwapper/utils/getBebopStepData.d.ts +1 -0
  92. package/dist/esm/swappers/BebopSwapper/utils/getBebopStepData.js +2 -2
  93. package/dist/esm/swappers/BebopSwapper/utils/getBebopTradeContext.js +1 -0
  94. package/dist/esm/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.js +5 -1
  95. package/dist/esm/swappers/BobGatewaySwapper/swapperApi/getTradeRate.js +11 -2
  96. package/dist/esm/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.d.ts +2 -1
  97. package/dist/esm/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.js +2 -1
  98. package/dist/esm/swappers/BobGatewaySwapper/utils/helpers.d.ts +2 -1
  99. package/dist/esm/swappers/BobGatewaySwapper/utils/helpers.js +2 -1
  100. package/dist/esm/swappers/ChainflipSwapper/ChainflipSwapper.js +1 -0
  101. package/dist/esm/swappers/ChainflipSwapper/checkTradeStatus.test.d.ts +1 -0
  102. package/dist/esm/swappers/ChainflipSwapper/checkTradeStatus.test.js +46 -0
  103. package/dist/esm/swappers/ChainflipSwapper/endpoints.js +5 -2
  104. package/dist/esm/swappers/ChainflipSwapper/types.d.ts +3 -0
  105. package/dist/esm/swappers/DebridgeSwapper/utils/getDebridgeStepData.d.ts +1 -0
  106. package/dist/esm/swappers/DebridgeSwapper/utils/getDebridgeStepData.js +2 -2
  107. package/dist/esm/swappers/DebridgeSwapper/utils/getDebridgeTradeContext.js +1 -0
  108. package/dist/esm/swappers/NearIntentsSwapper/NearIntentsSwapper.js +1 -0
  109. package/dist/esm/swappers/NearIntentsSwapper/checkTradeStatus.test.d.ts +1 -0
  110. package/dist/esm/swappers/NearIntentsSwapper/checkTradeStatus.test.js +50 -0
  111. package/dist/esm/swappers/NearIntentsSwapper/constants.d.ts +1 -1
  112. package/dist/esm/swappers/NearIntentsSwapper/constants.js +3 -2
  113. package/dist/esm/swappers/NearIntentsSwapper/endpoints.js +6 -3
  114. package/dist/esm/swappers/NearIntentsSwapper/swapperApi/getTradeQuote.d.ts +2 -1
  115. package/dist/esm/swappers/NearIntentsSwapper/swapperApi/getTradeQuote.js +7 -3
  116. package/dist/esm/swappers/NearIntentsSwapper/swapperApi/getTradeRate.d.ts +2 -1
  117. package/dist/esm/swappers/NearIntentsSwapper/swapperApi/getTradeRate.js +7 -3
  118. package/dist/esm/swappers/NearIntentsSwapper/types.d.ts +3 -1
  119. package/dist/esm/swappers/NearIntentsSwapper/utils/exactOutput.test.d.ts +1 -0
  120. package/dist/esm/swappers/NearIntentsSwapper/utils/exactOutput.test.js +59 -0
  121. package/dist/esm/swappers/NearIntentsSwapper/utils/getNearIntentsTradeContext.d.ts +2 -2
  122. package/dist/esm/swappers/NearIntentsSwapper/utils/getNearIntentsTradeContext.js +4 -2
  123. package/dist/esm/swappers/NearIntentsSwapper/utils/helpers.d.ts +5 -5
  124. package/dist/esm/swappers/NearIntentsSwapper/utils/helpers.js +32 -28
  125. package/dist/esm/swappers/NearIntentsSwapper/utils/quoteDeadline.test.d.ts +1 -0
  126. package/dist/esm/swappers/NearIntentsSwapper/utils/quoteDeadline.test.js +26 -0
  127. package/dist/esm/swappers/PortalsSwapper/endpoints.js +53 -49
  128. package/dist/esm/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.js +1 -1
  129. package/dist/esm/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.js +1 -1
  130. package/dist/esm/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.d.ts +0 -1
  131. package/dist/esm/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.js +2 -4
  132. package/dist/esm/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.js +6 -3
  133. package/dist/esm/swappers/PortalsSwapper/utils/fetchSquidStatus.d.ts +0 -1
  134. package/dist/esm/swappers/PortalsSwapper/utils/fetchSquidStatus.js +0 -6
  135. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsStepData.d.ts +1 -0
  136. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsStepData.js +13 -5
  137. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsStepData.test.d.ts +1 -0
  138. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsStepData.test.js +68 -0
  139. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.d.ts +3 -2
  140. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.js +35 -17
  141. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.d.ts +1 -0
  142. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.js +91 -0
  143. package/dist/esm/swappers/RelaySwapper/endpoints.js +11 -20
  144. package/dist/esm/swappers/RelaySwapper/getTradeQuote/getTradeQuote.d.ts +2 -1
  145. package/dist/esm/swappers/RelaySwapper/getTradeQuote/getTradeQuote.js +3 -1
  146. package/dist/esm/swappers/RelaySwapper/getTradeRate/getTradeRate.d.ts +2 -1
  147. package/dist/esm/swappers/RelaySwapper/getTradeRate/getTradeRate.js +3 -1
  148. package/dist/esm/swappers/RelaySwapper/utils/getRelayTradeContext.d.ts +2 -2
  149. package/dist/esm/swappers/RelaySwapper/utils/getRelayTradeContext.js +23 -11
  150. package/dist/esm/swappers/RelaySwapper/utils/helpers.d.ts +2 -2
  151. package/dist/esm/swappers/RelaySwapper/utils/relayStatus.d.ts +8 -0
  152. package/dist/esm/swappers/RelaySwapper/utils/relayStatus.js +32 -0
  153. package/dist/esm/swappers/RelaySwapper/utils/types.d.ts +18 -1
  154. package/dist/esm/swappers/SunioSwapper/utils/getSunioStepData.d.ts +1 -0
  155. package/dist/esm/swappers/SunioSwapper/utils/getSunioStepData.js +2 -2
  156. package/dist/esm/swappers/SunioSwapper/utils/getSunioTradeContext.js +6 -1
  157. package/dist/esm/swappers/ThorchainSwapper/generated/generatedThorLongtailTokens.json +1 -0
  158. package/dist/esm/types.d.ts +21 -2
  159. package/dist/esm/types.js +2 -0
  160. package/dist/esm/utils/cowswap/index.d.ts +4 -1
  161. package/dist/esm/utils/cowswap/index.js +3 -0
  162. package/dist/esm/utils/helpers.d.ts +9 -0
  163. package/dist/esm/utils/helpers.js +20 -0
  164. package/dist/esm/utils/helpers.test.js +42 -1
  165. package/package.json +6 -6
@@ -0,0 +1,68 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { estimateGasWithStateOverride } from '../../../utils/evm/index.js';
3
+ import { USDC_MAINNET } from '../../../utils/test-data/assets.js';
4
+ import { fetchPortalsTradeEstimate } from './fetchPortalsTradeOrder.js';
5
+ import { getPortalsStepData } from './getPortalsStepData.js';
6
+ vi.mock('../../../utils/evm', async (importOriginal) => ({
7
+ ...(await importOriginal()),
8
+ estimateGasWithStateOverride: vi.fn(),
9
+ }));
10
+ vi.mock('./fetchPortalsTradeOrder', async (importOriginal) => ({
11
+ ...(await importOriginal()),
12
+ fetchPortalsTradeEstimate: vi.fn(),
13
+ }));
14
+ const GAS_PRICE = '1000000000';
15
+ const deps = {
16
+ assertGetEvmChainAdapter: () => ({
17
+ getGasFeeData: () => Promise.resolve({
18
+ average: { gasPrice: GAS_PRICE, maxFeePerGas: '0', maxPriorityFeePerGas: '0' },
19
+ }),
20
+ }),
21
+ config: {},
22
+ };
23
+ const tx = (gasLimit) => ({
24
+ to: '0x4cd00e387622c35bddb9b4c962c136462338bc31',
25
+ from: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
26
+ data: '0xdeadbeef',
27
+ value: '0',
28
+ gasLimit,
29
+ });
30
+ const rate = (portalsTx) => getPortalsStepData({
31
+ type: 'rate',
32
+ input: { supportsEIP1559: false },
33
+ deps,
34
+ tx: portalsTx,
35
+ sellAsset: USDC_MAINNET,
36
+ sellAmountCryptoBaseUnit: '1000000000',
37
+ spenderAddress: '0x4cd00e387622c35bddb9b4c962c136462338bc31',
38
+ inputToken: 'ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
39
+ outputToken: 'arbitrum:0xaf88d065e77c8cc2239327c5edb3a432268e5831',
40
+ inputAmount: '1000000000',
41
+ slippageTolerancePercentage: 2.5,
42
+ });
43
+ const estimateEndpointGasLimit = (gasLimit) => vi.mocked(fetchPortalsTradeEstimate).mockResolvedValue({
44
+ context: { gasLimit },
45
+ });
46
+ describe('getPortalsStepData rate gas limit', () => {
47
+ beforeEach(() => vi.clearAllMocks());
48
+ it('falls back to the order gas limit on bridge routes the estimate endpoint zeroes', async () => {
49
+ vi.mocked(estimateGasWithStateOverride).mockRejectedValue(new Error('execution reverted'));
50
+ estimateEndpointGasLimit(0);
51
+ const actual = await rate(tx('76384'));
52
+ expect(actual.unwrap()).toEqual({ networkFeeCryptoBaseUnit: '76384000000000' });
53
+ expect(fetchPortalsTradeEstimate).not.toHaveBeenCalled();
54
+ });
55
+ it('falls back to the estimate endpoint when the order carries no gas limit', async () => {
56
+ vi.mocked(estimateGasWithStateOverride).mockRejectedValue(new Error('execution reverted'));
57
+ estimateEndpointGasLimit(273203);
58
+ const actual = await rate(tx(undefined));
59
+ expect(actual.unwrap()).toEqual({ networkFeeCryptoBaseUnit: '273203000000000' });
60
+ });
61
+ it('errors rather than pricing a zero gas limit as a free trade', async () => {
62
+ vi.mocked(estimateGasWithStateOverride).mockRejectedValue(new Error('execution reverted'));
63
+ estimateEndpointGasLimit(0);
64
+ const actual = await rate(tx(undefined));
65
+ expect(actual.isErr()).toBe(true);
66
+ expect(actual.unwrapErr().message).toContain('getPortalsStepData');
67
+ });
68
+ });
@@ -10,16 +10,17 @@ type PortalsTradeContext = {
10
10
  stepDataArgs: {
11
11
  deps: SwapperDeps;
12
12
  sellAsset: Asset;
13
+ sellAmountCryptoBaseUnit: string;
13
14
  spenderAddress: string;
14
15
  tx: PortalsTx;
15
16
  };
16
17
  };
17
- export declare const getPortalsTradeContext: ({ input, deps, sellChainId, inputToken, orderContext, tx, }: {
18
+ export declare const getPortalsTradeContext: ({ input, deps, sellChainId, orderContext, outputToken, tx, }: {
18
19
  input: PortalsTradeQuoteInput | PortalsTradeRateInput;
19
20
  deps: SwapperDeps;
20
21
  sellChainId: PortalsSupportedChainId;
21
- inputToken: string;
22
22
  orderContext: PortalsTradeOrderResponse['context'];
23
+ outputToken: string;
23
24
  tx: PortalsTx;
24
25
  }) => Result<PortalsTradeContext, SwapErrorRight>;
25
26
  export {};
@@ -1,14 +1,29 @@
1
- import { bn, bnOrZero } from '@shapeshiftoss/utils';
1
+ import { BigNumber, bn, bnOrZero } from '@shapeshiftoss/utils';
2
2
  import { Err, Ok } from '@sniptt/monads';
3
3
  import { SwapperName, TradeQuoteError } from '../../../types.js';
4
4
  import { getInputOutputRate, makeSwapErrorRight } from '../../../utils/index.js';
5
5
  import { buildAffiliateFee } from '../../../utils/affiliateFee.js';
6
6
  import { getPortalsRouterAddressByChainId } from './helpers.js';
7
- export const getPortalsTradeContext = ({ input, deps, sellChainId, inputToken, orderContext, tx, }) => {
8
- const { sellAsset, buyAsset, affiliateBps, sellAmountIncludingProtocolFeesCryptoBaseUnit } = input;
9
- const { orderId, outputAmount, minOutputAmount, target, feeAmount, feeToken } = orderContext;
7
+ export const getPortalsTradeContext = ({ input, deps, sellChainId, orderContext, outputToken, tx, }) => {
8
+ const { sellAsset, buyAsset, affiliateBps } = input;
9
+ const { orderId, inputAmount, outputAmount, minOutputAmount, target, feeAmount, feeToken, slippageTolerancePercentage, } = orderContext;
10
+ // Portals can fill less than we asked for - price against what the transaction actually spends
11
+ const sellAmountIncludingProtocolFeesCryptoBaseUnit = inputAmount;
10
12
  const isCrossChain = sellAsset.chainId !== buyAsset.chainId;
11
- const buyAmountAfterFeesCryptoBaseUnit = isCrossChain ? minOutputAmount : outputAmount;
13
+ // Portals report the slippage they applied, not the one we requested - bridge routes apply none
14
+ const appliedSlippageDecimal = bnOrZero(slippageTolerancePercentage).div(100);
15
+ // Unvalidated orders report the post-slippage minimum in both amount fields
16
+ const hasSlippageBuffer = bnOrZero(outputAmount).gt(minOutputAmount);
17
+ const preSlippageOutputAmount = bnOrZero(minOutputAmount)
18
+ .div(bn(1).minus(appliedSlippageDecimal))
19
+ .toFixed(0);
20
+ const buyAmountAfterFeesCryptoBaseUnit = hasSlippageBuffer
21
+ ? outputAmount
22
+ : preSlippageOutputAmount;
23
+ const bufferSlippageDecimal = hasSlippageBuffer
24
+ ? bnOrZero(outputAmount).minus(minOutputAmount).div(outputAmount)
25
+ : appliedSlippageDecimal;
26
+ const slippageTolerancePercentageDecimal = BigNumber.max(appliedSlippageDecimal, bufferSlippageDecimal).toString();
12
27
  const rate = getInputOutputRate({
13
28
  sellAmountCryptoBaseUnit: sellAmountIncludingProtocolFeesCryptoBaseUnit,
14
29
  buyAmountCryptoBaseUnit: buyAmountAfterFeesCryptoBaseUnit,
@@ -24,16 +39,13 @@ export const getPortalsTradeContext = ({ input, deps, sellChainId, inputToken, o
24
39
  details: { chainId: sellChainId },
25
40
  }));
26
41
  }
27
- // Portals reports the slippage we requested, not what it applied — recover the actual buffer from the amounts
28
- const actualBufferDecimal = bnOrZero(buyAmountAfterFeesCryptoBaseUnit)
29
- .minus(minOutputAmount)
30
- .div(buyAmountAfterFeesCryptoBaseUnit)
31
- .toString();
32
- // Reverse the buffer to recover the expected output (minOutput / (1 - buffer) = output)
33
- const buyAmountBeforeFeesCryptoBaseUnit = bnOrZero(minOutputAmount)
34
- .div(bn(1).minus(actualBufferDecimal))
35
- .toFixed(0);
36
- const protocolFeeAsset = feeToken === inputToken ? sellAsset : buyAsset;
42
+ // Portals lower-case echoed tokens, and take their fee in the sell asset on ERC20 sells
43
+ const isBuyAssetFee = feeToken?.toLowerCase() === outputToken.toLowerCase();
44
+ const protocolFeeAsset = isBuyAssetFee ? buyAsset : sellAsset;
45
+ // Portals report an output already net of their fee
46
+ const buyAmountBeforeFeesCryptoBaseUnit = isBuyAssetFee && feeAmount
47
+ ? bnOrZero(buyAmountAfterFeesCryptoBaseUnit).plus(feeAmount).toFixed(0)
48
+ : buyAmountAfterFeesCryptoBaseUnit;
37
49
  const protocolFees = (() => {
38
50
  if (!feeToken || !feeAmount)
39
51
  return;
@@ -51,7 +63,7 @@ export const getPortalsTradeContext = ({ input, deps, sellChainId, inputToken, o
51
63
  rate,
52
64
  swapperName: SwapperName.Portals,
53
65
  affiliateBps,
54
- slippageTolerancePercentageDecimal: actualBufferDecimal,
66
+ slippageTolerancePercentageDecimal,
55
67
  },
56
68
  stepCommon: {
57
69
  estimatedExecutionTimeMs: isCrossChain ? 300000 : 0,
@@ -74,6 +86,12 @@ export const getPortalsTradeContext = ({ input, deps, sellChainId, inputToken, o
74
86
  }),
75
87
  },
76
88
  protocolFees,
77
- stepDataArgs: { deps, sellAsset, spenderAddress: allowanceContract, tx },
89
+ stepDataArgs: {
90
+ deps,
91
+ sellAsset,
92
+ sellAmountCryptoBaseUnit: sellAmountIncludingProtocolFeesCryptoBaseUnit,
93
+ spenderAddress: allowanceContract,
94
+ tx,
95
+ },
78
96
  });
79
97
  };
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { USDC_ARBITRUM, USDC_MAINNET } from '../../../utils/test-data/assets.js';
3
+ import { getPortalsTradeContext } from './getPortalsTradeContext.js';
4
+ const deps = {};
5
+ const tx = {
6
+ to: '0x4cd00e387622c35bddb9b4c962c136462338bc31',
7
+ from: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
8
+ data: '0xdeadbeef',
9
+ value: '0',
10
+ };
11
+ const makeInput = (buyAsset = USDC_ARBITRUM) => ({
12
+ sellAsset: USDC_MAINNET,
13
+ buyAsset,
14
+ affiliateBps: '0',
15
+ sellAmountIncludingProtocolFeesCryptoBaseUnit: '4631313795',
16
+ });
17
+ const makeOrderContext = (overrides) => ({
18
+ orderId: 'eaae8ea5-30af-4463-97ab-1c5b1fe7def4',
19
+ target: '0x4cd00e387622c35bddb9b4c962c136462338bc31',
20
+ inputAmount: '4631313795',
21
+ ...overrides,
22
+ });
23
+ const unwrap = (args) => getPortalsTradeContext(args).unwrap();
24
+ describe('getPortalsTradeContext', () => {
25
+ it('does not gross up a bridge route Portals applied no slippage to', () => {
26
+ const { stepCommon, tradeCommon } = unwrap({
27
+ input: makeInput(),
28
+ deps,
29
+ sellChainId: USDC_MAINNET.chainId,
30
+ orderContext: makeOrderContext({
31
+ outputAmount: '4616564490',
32
+ minOutputAmount: '4616564490',
33
+ slippageTolerancePercentage: 0,
34
+ }),
35
+ outputToken: 'arbitrum:0xaf88d065e77c8cc2239327c5edb3a432268e5831',
36
+ tx,
37
+ });
38
+ expect(stepCommon.buyAmountAfterFeesCryptoBaseUnit).toBe('4616564490');
39
+ expect(tradeCommon.slippageTolerancePercentageDecimal).toBe('0');
40
+ });
41
+ it('reverses the applied slippage on an unvalidated order collapsed to its minimum', () => {
42
+ const { stepCommon, tradeCommon } = unwrap({
43
+ input: makeInput(USDC_MAINNET),
44
+ deps,
45
+ sellChainId: USDC_MAINNET.chainId,
46
+ orderContext: makeOrderContext({
47
+ outputAmount: '2360574345',
48
+ minOutputAmount: '2360574345',
49
+ slippageTolerancePercentage: 2.5,
50
+ }),
51
+ outputToken: 'ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
52
+ tx,
53
+ });
54
+ // Within 0.01% of the 2420957576 the same order returns when validated
55
+ expect(stepCommon.buyAmountAfterFeesCryptoBaseUnit).toBe('2421101892');
56
+ expect(tradeCommon.slippageTolerancePercentageDecimal).toBe('0.025');
57
+ });
58
+ it('keeps the reported output of a validated order and advertises the wider buffer', () => {
59
+ const { stepCommon, tradeCommon } = unwrap({
60
+ input: makeInput(USDC_MAINNET),
61
+ deps,
62
+ sellChainId: USDC_MAINNET.chainId,
63
+ orderContext: makeOrderContext({
64
+ outputAmount: '2420957576',
65
+ minOutputAmount: '2360574345',
66
+ slippageTolerancePercentage: 2.5,
67
+ }),
68
+ outputToken: 'ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
69
+ tx,
70
+ });
71
+ expect(stepCommon.buyAmountAfterFeesCryptoBaseUnit).toBe('2420957576');
72
+ expect(tradeCommon.slippageTolerancePercentageDecimal).toBe('0.025');
73
+ });
74
+ it('prices against the input amount Portals filled rather than the one requested', () => {
75
+ const { stepCommon } = unwrap({
76
+ input: makeInput(),
77
+ deps,
78
+ sellChainId: USDC_MAINNET.chainId,
79
+ orderContext: makeOrderContext({
80
+ // Portals withhold 0.01% of the requested 4631313795 on cross-chain orders
81
+ inputAmount: '4630850664',
82
+ outputAmount: '4616564490',
83
+ minOutputAmount: '4616564490',
84
+ slippageTolerancePercentage: 0,
85
+ }),
86
+ outputToken: 'arbitrum:0xaf88d065e77c8cc2239327c5edb3a432268e5831',
87
+ tx,
88
+ });
89
+ expect(stepCommon.sellAmountIncludingProtocolFeesCryptoBaseUnit).toBe('4630850664');
90
+ });
91
+ });
@@ -6,11 +6,12 @@ import { getUnsignedSolanaTransaction } from '../../utils/solana/getUnsignedSola
6
6
  import { getTronTransactionFees, getUnsignedTronTransaction } from '../../utils/tron/index.js';
7
7
  import { getUnsignedUtxoTransaction, getUtxoTransactionFees } from '../../utils/utxo/index.js';
8
8
  import { chainIdToRelayChainId } from './constant.js';
9
- import { getTradeQuote } from './getTradeQuote/getTradeQuote.js';
10
- import { getTradeRate } from './getTradeRate/getTradeRate.js';
9
+ import { getExactOutputTradeQuote, getTradeQuote } from './getTradeQuote/getTradeQuote.js';
10
+ import { getExactOutputTradeRate, getTradeRate } from './getTradeRate/getTradeRate.js';
11
11
  import { getLatestRelayStatusMessage } from './utils/getLatestRelayStatusMessage.js';
12
12
  import { notifyTransactionIndexing } from './utils/notifyTransactionIndexing.js';
13
13
  import { getRelayRequestConfig, relayService } from './utils/relayService.js';
14
+ import { getRelayTrackingLink, relayStatusToTxStatus } from './utils/relayStatus.js';
14
15
  // Keep track of the trades we already notified the relay indexer about
15
16
  const txIndexingMap = new Map();
16
17
  export const relayApi = {
@@ -20,6 +21,12 @@ export const relayApi = {
20
21
  getTradeRate: (input, deps) => {
21
22
  return getTradeRate(input, deps, chainIdToRelayChainId);
22
23
  },
24
+ getExactOutputTradeQuote: (input, deps) => {
25
+ return getExactOutputTradeQuote(input, deps, chainIdToRelayChainId);
26
+ },
27
+ getExactOutputTradeRate: (input, deps) => {
28
+ return getExactOutputTradeRate(input, deps, chainIdToRelayChainId);
29
+ },
23
30
  getEvmTransactionFees,
24
31
  getUnsignedEvmTransaction,
25
32
  getUnsignedUtxoTransaction,
@@ -60,7 +67,7 @@ export const relayApi = {
60
67
  }
61
68
  // relay.link tracks the swap by its origin chain transaction
62
69
  const swapperTxId = txHash;
63
- const swapperTxLink = `https://relay.link/transaction/${txHash}`;
70
+ const swapperTxLink = getRelayTrackingLink(txHash);
64
71
  const maybeStatusResponse = await relayService.get(`${config.VITE_RELAY_API_URL}/intents/status/v3?requestId=${relayMetadata.relayId}`, getRelayRequestConfig(config));
65
72
  if (maybeStatusResponse.isErr()) {
66
73
  return {
@@ -72,23 +79,7 @@ export const relayApi = {
72
79
  };
73
80
  }
74
81
  const { data: statusResponse } = maybeStatusResponse.unwrap();
75
- const status = (() => {
76
- switch (statusResponse.status) {
77
- case 'success':
78
- return TxStatus.Confirmed;
79
- case 'waiting':
80
- case 'delayed':
81
- case 'pending':
82
- case 'depositing':
83
- case 'submitted':
84
- return TxStatus.Pending;
85
- case 'failure':
86
- case 'refund':
87
- return TxStatus.Failed;
88
- default:
89
- return TxStatus.Unknown;
90
- }
91
- })();
82
+ const status = relayStatusToTxStatus(statusResponse.status);
92
83
  // Relay refers to in Txs as "inTxHashes" but to out Txs as simply "txHashes" when they really mean "outTxHashes"
93
84
  // One thing to note is that for same-chain Txs, there is no "out Tx" per se since the in Tx *is* the out Tx
94
85
  const outTxHashes = statusResponse.txHashes;
@@ -1,5 +1,6 @@
1
1
  import type { Result } from '@sniptt/monads';
2
2
  import type { SwapErrorRight, SwapperDeps, TradeQuote } from '../../../types.js';
3
3
  import type { chainIdToRelayChainId as relayChainMapImplementation } from '../constant.js';
4
- import type { RelayTradeQuoteInput } from '../utils/types.js';
4
+ import type { RelayExactOutputTradeQuoteInput, RelayTradeQuoteInput } from '../utils/types.js';
5
5
  export declare const getTradeQuote: (input: RelayTradeQuoteInput, deps: SwapperDeps, relayChainMap: typeof relayChainMapImplementation) => Promise<Result<TradeQuote[], SwapErrorRight>>;
6
+ export declare const getExactOutputTradeQuote: (input: RelayExactOutputTradeQuoteInput, deps: SwapperDeps, relayChainMap: typeof relayChainMapImplementation) => Promise<Result<TradeQuote[], SwapErrorRight>>;
@@ -3,7 +3,7 @@ import { assertQuoteAddresses } from '../../../utils/index.js';
3
3
  import { FALLBACK_QUOTE_DEADLINE_MS } from '../../../utils/helpers.js';
4
4
  import { getRelayStepData } from '../utils/getRelayStepData.js';
5
5
  import { getRelayTradeContext } from '../utils/getRelayTradeContext.js';
6
- export const getTradeQuote = async (input, deps, relayChainMap) => {
6
+ const getQuote = async (input, deps, relayChainMap) => {
7
7
  const { accountNumber } = input;
8
8
  const addresses = assertQuoteAddresses(input);
9
9
  if (addresses.isErr())
@@ -49,3 +49,5 @@ export const getTradeQuote = async (input, deps, relayChainMap) => {
49
49
  };
50
50
  return Ok([tradeQuote]);
51
51
  };
52
+ export const getTradeQuote = (input, deps, relayChainMap) => getQuote(input, deps, relayChainMap);
53
+ export const getExactOutputTradeQuote = (input, deps, relayChainMap) => getQuote(input, deps, relayChainMap);
@@ -1,5 +1,6 @@
1
1
  import type { Result } from '@sniptt/monads';
2
2
  import type { SwapErrorRight, SwapperDeps, TradeRate } from '../../../types.js';
3
3
  import type { chainIdToRelayChainId as relayChainMapImplementation } from '../constant.js';
4
- import type { RelayTradeRateInput } from '../utils/types.js';
4
+ import type { RelayExactOutputTradeRateInput, RelayTradeRateInput } from '../utils/types.js';
5
5
  export declare const getTradeRate: (input: RelayTradeRateInput, deps: SwapperDeps, relayChainMap: typeof relayChainMapImplementation) => Promise<Result<TradeRate[], SwapErrorRight>>;
6
+ export declare const getExactOutputTradeRate: (input: RelayExactOutputTradeRateInput, deps: SwapperDeps, relayChainMap: typeof relayChainMapImplementation) => Promise<Result<TradeRate[], SwapErrorRight>>;
@@ -1,7 +1,7 @@
1
1
  import { Err, Ok } from '@sniptt/monads';
2
2
  import { getRelayStepData } from '../utils/getRelayStepData.js';
3
3
  import { getRelayTradeContext } from '../utils/getRelayTradeContext.js';
4
- export const getTradeRate = async (input, deps, relayChainMap) => {
4
+ const getRate = async (input, deps, relayChainMap) => {
5
5
  const maybeContext = await getRelayTradeContext({ input, deps, relayChainMap });
6
6
  if (maybeContext.isErr())
7
7
  return Err(maybeContext.unwrapErr());
@@ -36,3 +36,5 @@ export const getTradeRate = async (input, deps, relayChainMap) => {
36
36
  };
37
37
  return Ok([tradeRate]);
38
38
  };
39
+ export const getTradeRate = (input, deps, relayChainMap) => getRate(input, deps, relayChainMap);
40
+ export const getExactOutputTradeRate = (input, deps, relayChainMap) => getRate(input, deps, relayChainMap);
@@ -2,7 +2,7 @@ import type { Result } from '@sniptt/monads';
2
2
  import type { QuoteFeeData, SwapErrorRight, SwapperDeps, TradeCommon, TradeStepCommon } from '../../../types.js';
3
3
  import type { chainIdToRelayChainId as relayChainMapImplementation } from '../constant.js';
4
4
  import type { GetRelayStepDataArgs } from './getRelayStepData.js';
5
- import type { RelayQuoteItem, RelayTradeQuoteInput, RelayTradeRateInput } from './types.js';
5
+ import type { RelayExactOutputTradeQuoteInput, RelayExactOutputTradeRateInput, RelayQuoteItem, RelayTradeQuoteInput, RelayTradeRateInput } from './types.js';
6
6
  type RelayTradeContext = {
7
7
  tradeCommon: TradeCommon;
8
8
  stepCommon: Omit<TradeStepCommon, 'feeData' | 'allowanceContract'>;
@@ -15,7 +15,7 @@ type RelayTradeContext = {
15
15
  relayId: string;
16
16
  };
17
17
  export declare const getRelayTradeContext: ({ input, deps, relayChainMap, }: {
18
- input: RelayTradeQuoteInput | RelayTradeRateInput;
18
+ input: RelayTradeQuoteInput | RelayTradeRateInput | RelayExactOutputTradeQuoteInput | RelayExactOutputTradeRateInput;
19
19
  deps: SwapperDeps;
20
20
  relayChainMap: typeof relayChainMapImplementation;
21
21
  }) => Promise<Result<RelayTradeContext, SwapErrorRight>>;
@@ -7,7 +7,7 @@ import { zeroAddress } from 'viem';
7
7
  import { MixPanelEvent, SwapperName, TradeQuoteError } from '../../../types.js';
8
8
  import { getInputOutputRate, makeSwapErrorRight } from '../../../utils/index.js';
9
9
  import { buildAffiliateFee } from '../../../utils/affiliateFee.js';
10
- import { isNativeEvmAsset } from '../../../utils/helpers.js';
10
+ import { getTradeAmount, isNativeEvmAsset } from '../../../utils/helpers.js';
11
11
  import { MAXIMUM_SUPPORTED_RELAY_STEPS, relayErrorCodeToTradeQuoteError } from '../constant.js';
12
12
  import { getRelayAssetAddress } from '../utils/getRelayAssetAddress.js';
13
13
  import { relayTokenToAsset } from '../utils/relayTokenToAsset.js';
@@ -16,7 +16,9 @@ import { fetchRelayTrade } from './fetchRelayTrade.js';
16
16
  import { assertValidTrade, getRelayAllowanceContract, resolveRelayAddresses } from './helpers.js';
17
17
  import { isRelayError } from './types.js';
18
18
  export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
19
- const { sellAsset, buyAsset, sellAmountIncludingProtocolFeesCryptoBaseUnit, affiliateBps } = input;
19
+ const { sellAsset, buyAsset, affiliateBps } = input;
20
+ const amount = getTradeAmount(input);
21
+ const isExactOutput = amount.direction === 'exactOut';
20
22
  const xpub = 'xpub' in input ? input.xpub : undefined;
21
23
  const slippageToleranceBps = input.slippageTolerancePercentageDecimal
22
24
  ? convertDecimalPercentageToBasisPoints(input.slippageTolerancePercentageDecimal).toFixed()
@@ -35,8 +37,8 @@ export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
35
37
  originCurrency: getRelayAssetAddress(sellAsset),
36
38
  destinationChainId: buyRelayChainId,
37
39
  destinationCurrency: getRelayAssetAddress(buyAsset),
38
- tradeType: 'EXACT_INPUT',
39
- amount: sellAmountIncludingProtocolFeesCryptoBaseUnit,
40
+ tradeType: isExactOutput ? 'EXACT_OUTPUT' : 'EXACT_INPUT',
41
+ amount: amount.cryptoBaseUnit,
40
42
  recipient,
41
43
  user: sendAddress,
42
44
  refundTo,
@@ -78,10 +80,19 @@ export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
78
80
  code: TradeQuoteError.UnknownError,
79
81
  }));
80
82
  }
81
- const { slippageTolerance, currencyOut, timeEstimate } = quote.details;
82
- const buyAmountAfterFeesCryptoBaseUnit = currencyOut.amount;
83
+ const { slippageTolerance, currencyIn, currencyOut, timeEstimate } = quote.details;
84
+ const sellAmountCryptoBaseUnit = currencyIn.amount;
85
+ const buyAmountAfterFeesCryptoBaseUnit = isExactOutput
86
+ ? currencyOut.minimumAmount
87
+ : currencyOut.amount;
88
+ if (isExactOutput && !bnOrZero(buyAmountAfterFeesCryptoBaseUnit).eq(amount.cryptoBaseUnit)) {
89
+ return Err(makeSwapErrorRight({
90
+ message: `Relay guarantees ${buyAmountAfterFeesCryptoBaseUnit} against a requested exact output of ${amount.cryptoBaseUnit}`,
91
+ code: TradeQuoteError.InvalidResponse,
92
+ }));
93
+ }
83
94
  const rate = getInputOutputRate({
84
- sellAmountCryptoBaseUnit: sellAmountIncludingProtocolFeesCryptoBaseUnit,
95
+ sellAmountCryptoBaseUnit,
85
96
  buyAmountCryptoBaseUnit: buyAmountAfterFeesCryptoBaseUnit,
86
97
  sellAsset,
87
98
  buyAsset,
@@ -242,7 +253,7 @@ export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
242
253
  })();
243
254
  // Add back relayer service and gas fees (relayer is including both) since they are downsides,
244
255
  // and add appFees — these are quote-level and identical across every step
245
- const buyAmountBeforeFeesCryptoBaseUnit = bnOrZero(currencyOut.amount)
256
+ const buyAmountBeforeFeesCryptoBaseUnit = bnOrZero(buyAmountAfterFeesCryptoBaseUnit)
246
257
  .plus(relayerFeesBuyAssetCryptoBaseUnit)
247
258
  .plus(appFeesBaseUnit)
248
259
  .toFixed();
@@ -256,6 +267,7 @@ export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
256
267
  id: relayId,
257
268
  rate,
258
269
  swapperName: SwapperName.Relay,
270
+ isExactOutput,
259
271
  affiliateBps,
260
272
  slippageTolerancePercentageDecimal,
261
273
  },
@@ -263,7 +275,7 @@ export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
263
275
  rate,
264
276
  buyAmountBeforeFeesCryptoBaseUnit,
265
277
  buyAmountAfterFeesCryptoBaseUnit,
266
- sellAmountIncludingProtocolFeesCryptoBaseUnit,
278
+ sellAmountIncludingProtocolFeesCryptoBaseUnit: sellAmountCryptoBaseUnit,
267
279
  buyAsset,
268
280
  sellAsset,
269
281
  source: SwapperName.Relay,
@@ -273,7 +285,7 @@ export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
273
285
  affiliateBps,
274
286
  sellAsset,
275
287
  buyAsset,
276
- sellAmountCryptoBaseUnit: sellAmountIncludingProtocolFeesCryptoBaseUnit,
288
+ sellAmountCryptoBaseUnit,
277
289
  buyAmountCryptoBaseUnit: buyAmountAfterFeesCryptoBaseUnit,
278
290
  fixedAssetId: chainIdToFeeAssetId(baseChainId),
279
291
  fixedAsset: deps.assetsById[chainIdToFeeAssetId(baseChainId)],
@@ -291,7 +303,7 @@ export const getRelayTradeContext = async ({ input, deps, relayChainMap, }) => {
291
303
  relayStepInputs,
292
304
  stepDataArgs: {
293
305
  sellAsset,
294
- sellAmountCryptoBaseUnit: sellAmountIncludingProtocolFeesCryptoBaseUnit,
306
+ sellAmountCryptoBaseUnit,
295
307
  orderId,
296
308
  from: sendAddress,
297
309
  xpub,
@@ -4,7 +4,7 @@ import type { Result } from '@sniptt/monads';
4
4
  import type { TransactionInstruction } from '@solana/web3.js';
5
5
  import type { SwapErrorRight } from '../../../types.js';
6
6
  import type { chainIdToRelayChainId as relayChainMapImplementation } from '../constant.js';
7
- import type { RelayQuoteItem, RelaySolanaInstruction, RelayTradeQuoteInput, RelayTradeRateInput } from './types.js';
7
+ import type { RelayExactOutputTradeQuoteInput, RelayExactOutputTradeRateInput, RelayQuoteItem, RelaySolanaInstruction, RelayTradeQuoteInput, RelayTradeRateInput } from './types.js';
8
8
  export declare const assertValidTrade: ({ sellAsset, buyAsset, relayChainMap, }: {
9
9
  sellAsset: Asset;
10
10
  buyAsset: Asset;
@@ -14,7 +14,7 @@ export declare const assertValidTrade: ({ sellAsset, buyAsset, relayChainMap, }:
14
14
  buyRelayChainId: number;
15
15
  }, SwapErrorRight>;
16
16
  export declare const resolveRelayAddresses: ({ input, sellChainId, buyChainId, }: {
17
- input: RelayTradeQuoteInput | RelayTradeRateInput;
17
+ input: RelayTradeQuoteInput | RelayTradeRateInput | RelayExactOutputTradeQuoteInput | RelayExactOutputTradeRateInput;
18
18
  sellChainId: ChainId;
19
19
  buyChainId: ChainId;
20
20
  }) => {
@@ -0,0 +1,8 @@
1
+ import { TxStatus } from '@shapeshiftoss/unchained-client';
2
+ import type { Result } from '@sniptt/monads';
3
+ import type { SwapErrorRight, SwapperConfig } from '../../../types.js';
4
+ import type { RelayRequest, RelayStatus } from './types.js';
5
+ export declare const getRelayTrackingLink: (txHash: string) => string;
6
+ export declare const relayStatusToTxStatus: (status: RelayStatus['status'] | undefined) => TxStatus;
7
+ export declare const fetchRelayRequestByTxHash: (txHash: string, config: SwapperConfig) => Promise<Result<RelayRequest | undefined, SwapErrorRight>>;
8
+ export declare const getRelayRequestFailureMessage: (request: RelayRequest) => string;
@@ -0,0 +1,32 @@
1
+ import { TxStatus } from '@shapeshiftoss/unchained-client';
2
+ import { getRelayRequestConfig, relayService } from './relayService.js';
3
+ export const getRelayTrackingLink = (txHash) => `https://relay.link/transaction/${txHash}`;
4
+ export const relayStatusToTxStatus = (status) => {
5
+ switch (status) {
6
+ case 'success':
7
+ return TxStatus.Confirmed;
8
+ case 'waiting':
9
+ case 'delayed':
10
+ case 'pending':
11
+ case 'depositing':
12
+ case 'submitted':
13
+ return TxStatus.Pending;
14
+ case 'failure':
15
+ case 'refund':
16
+ return TxStatus.Failed;
17
+ default:
18
+ return TxStatus.Unknown;
19
+ }
20
+ };
21
+ // Relay index requests by origin tx hash, resolving a swap we hold no relayId for
22
+ export const fetchRelayRequestByTxHash = async (txHash, config) => {
23
+ const maybeResponse = await relayService.get(`${config.VITE_RELAY_API_URL}/requests/v2?hash=${txHash}`, getRelayRequestConfig(config));
24
+ // Match the origin tx ourselves rather than trusting the listing to have filtered on it
25
+ return maybeResponse.map(({ data }) => data.requests?.find(({ data: request }) => request?.inTxs?.some(({ hash }) => hash?.toLowerCase() === txHash.toLowerCase())));
26
+ };
27
+ export const getRelayRequestFailureMessage = (request) => {
28
+ const { failReason, refundFailReason } = request.data ?? {};
29
+ // Relay report 'N/A' rather than omitting the reason when there isn't one
30
+ const reason = [failReason, refundFailReason].find(value => value && value !== 'N/A');
31
+ return reason ?? 'Bridge failed';
32
+ };
@@ -1,6 +1,8 @@
1
- import type { GetEvmTradeQuoteInput, GetEvmTradeRateInput, GetSolanaTradeQuoteInput, GetSolanaTradeRateInput, GetTronTradeQuoteInput, GetTronTradeRateInput, GetUtxoTradeQuoteInput, GetUtxoTradeRateInput } from '../../../types.js';
1
+ import type { GetEvmTradeQuoteInput, GetEvmTradeRateInput, GetSolanaTradeQuoteInput, GetSolanaTradeRateInput, GetTronTradeQuoteInput, GetTronTradeRateInput, GetUtxoTradeQuoteInput, GetUtxoTradeRateInput, WithExactBuyAmount } from '../../../types.js';
2
2
  export type RelayTradeQuoteInput = GetEvmTradeQuoteInput | GetUtxoTradeQuoteInput | GetSolanaTradeQuoteInput | GetTronTradeQuoteInput;
3
3
  export type RelayTradeRateInput = GetEvmTradeRateInput | GetUtxoTradeRateInput | GetSolanaTradeRateInput | GetTronTradeRateInput;
4
+ export type RelayExactOutputTradeQuoteInput = WithExactBuyAmount<RelayTradeQuoteInput>;
5
+ export type RelayExactOutputTradeRateInput = WithExactBuyAmount<RelayTradeRateInput>;
4
6
  export type RelayTransactionMetadata = {
5
7
  to?: string;
6
8
  data?: string;
@@ -10,6 +12,20 @@ export type RelayMetadata = {
10
12
  relayId: string;
11
13
  data?: string;
12
14
  };
15
+ export type RelayRequestTransaction = {
16
+ hash?: string;
17
+ chainId?: number;
18
+ };
19
+ export type RelayRequestData = {
20
+ failReason?: string;
21
+ refundFailReason?: string;
22
+ inTxs?: RelayRequestTransaction[];
23
+ outTxs?: RelayRequestTransaction[];
24
+ };
25
+ export type RelayRequest = {
26
+ status?: RelayStatus['status'];
27
+ data?: RelayRequestData;
28
+ };
13
29
  export type RelayStatus = {
14
30
  status: 'success' | 'failure' | 'pending' | 'submitted' | 'depositing' | 'refund' | 'delayed' | 'waiting';
15
31
  details?: string;
@@ -64,6 +80,7 @@ export type RelayFees = {
64
80
  app: RelayCurrencyData;
65
81
  };
66
82
  export type QuoteDetails = {
83
+ currencyIn: RelayCurrencyData;
67
84
  currencyOut: RelayCurrencyData;
68
85
  rate: string;
69
86
  slippageTolerance: {
@@ -3,6 +3,7 @@ import type { StepDataArgs, SwapErrorRight } from '../../../types.js';
3
3
  import type { SunioRoute, SunioTransactionData } from '../types.js';
4
4
  type BaseArgs = {
5
5
  route: SunioRoute;
6
+ sellAmountCryptoBaseUnit: string;
6
7
  };
7
8
  export type GetSunioStepDataArgs = StepDataArgs<BaseArgs>;
8
9
  type SunioRateStepData = {
@@ -3,12 +3,12 @@ import { Err, Ok } from '@sniptt/monads';
3
3
  import { makeNetworkFeeEstimationFailedErr } from '../../../utils/index.js';
4
4
  import { estimateSunioNetworkFeeCryptoBaseUnit } from './estimateSunioNetworkFee.js';
5
5
  export async function getSunioStepData(args) {
6
- const { type, route, sellAsset, from, input, deps } = args;
6
+ const { type, route, sellAsset, sellAmountCryptoBaseUnit, from, input, deps } = args;
7
7
  const estimateArgs = {
8
8
  rpcUrl: deps.config.VITE_TRON_NODE_URL,
9
9
  apiKey: deps.config.VITE_TRON_GRID_API_KEY,
10
10
  route,
11
- sellAmountCryptoBaseUnit: input.sellAmountIncludingProtocolFeesCryptoBaseUnit,
11
+ sellAmountCryptoBaseUnit,
12
12
  isSellingNativeTrx: !contractAddressOrUndefined(sellAsset.assetId),
13
13
  address: from,
14
14
  slippageTolerancePercentageDecimal: input.slippageTolerancePercentageDecimal,
@@ -80,7 +80,12 @@ export const getSunioTradeContext = async ({ input, deps, }) => {
80
80
  }),
81
81
  },
82
82
  protocolFees,
83
- stepDataArgs: { deps, sellAsset, route: bestRoute },
83
+ stepDataArgs: {
84
+ deps,
85
+ sellAsset,
86
+ sellAmountCryptoBaseUnit: sellAmountIncludingProtocolFeesCryptoBaseUnit,
87
+ route: bestRoute,
88
+ },
84
89
  });
85
90
  }
86
91
  catch (error) {
@@ -1016,6 +1016,7 @@
1016
1016
  "eip155:1/erc20:0x826180541412d574cf1336d22c0c0a287822678a",
1017
1017
  "eip155:1/erc20:0x2c06ba9e7f0daccbc1f6a33ea67e85bb68fbee3a",
1018
1018
  "eip155:1/erc20:0xcbb8f1bda10b9696c57e13bc128fe674769dcec0",
1019
+ "eip155:1/erc20:0x72dfb2e44f59c5ad2bafe84314e5b99a7cd5075e",
1019
1020
  "eip155:43114/erc20:0xc891eb4cbdeff6e073e859e987815ed1505c2acd",
1020
1021
  "eip155:43114/erc20:0xd24c2ad096400b6fbcd2ad8b24e7acbc21a1da64",
1021
1022
  "eip155:43114/erc20:0x47c3118ad183712acd42648e9e522e13690f29a0",