@shapeshiftoss/swapper 20.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 (97) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.js +5 -1
  3. package/dist/cjs/swappers/BobGatewaySwapper/swapperApi/getTradeRate.js +11 -2
  4. package/dist/cjs/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.d.ts +2 -1
  5. package/dist/cjs/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.js +2 -1
  6. package/dist/cjs/swappers/BobGatewaySwapper/utils/helpers.d.ts +2 -1
  7. package/dist/cjs/swappers/BobGatewaySwapper/utils/helpers.js +2 -1
  8. package/dist/cjs/swappers/ChainflipSwapper/ChainflipSwapper.js +1 -0
  9. package/dist/cjs/swappers/ChainflipSwapper/checkTradeStatus.test.d.ts +1 -0
  10. package/dist/cjs/swappers/ChainflipSwapper/checkTradeStatus.test.js +48 -0
  11. package/dist/cjs/swappers/ChainflipSwapper/endpoints.js +5 -2
  12. package/dist/cjs/swappers/ChainflipSwapper/types.d.ts +3 -0
  13. package/dist/cjs/swappers/NearIntentsSwapper/NearIntentsSwapper.js +1 -0
  14. package/dist/cjs/swappers/NearIntentsSwapper/checkTradeStatus.test.d.ts +1 -0
  15. package/dist/cjs/swappers/NearIntentsSwapper/checkTradeStatus.test.js +52 -0
  16. package/dist/cjs/swappers/NearIntentsSwapper/constants.d.ts +1 -1
  17. package/dist/cjs/swappers/NearIntentsSwapper/constants.js +4 -3
  18. package/dist/cjs/swappers/NearIntentsSwapper/endpoints.js +2 -1
  19. package/dist/cjs/swappers/NearIntentsSwapper/utils/helpers.d.ts +2 -2
  20. package/dist/cjs/swappers/NearIntentsSwapper/utils/helpers.js +3 -3
  21. package/dist/cjs/swappers/NearIntentsSwapper/utils/quoteDeadline.test.d.ts +1 -0
  22. package/dist/cjs/swappers/NearIntentsSwapper/utils/quoteDeadline.test.js +28 -0
  23. package/dist/cjs/swappers/PortalsSwapper/endpoints.js +51 -47
  24. package/dist/cjs/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.js +1 -1
  25. package/dist/cjs/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.js +1 -1
  26. package/dist/cjs/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.d.ts +0 -1
  27. package/dist/cjs/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.js +3 -6
  28. package/dist/cjs/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.js +6 -3
  29. package/dist/cjs/swappers/PortalsSwapper/utils/fetchSquidStatus.d.ts +0 -1
  30. package/dist/cjs/swappers/PortalsSwapper/utils/fetchSquidStatus.js +1 -8
  31. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsStepData.js +13 -5
  32. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsStepData.test.d.ts +1 -0
  33. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsStepData.test.js +70 -0
  34. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.d.ts +2 -2
  35. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.js +27 -15
  36. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.d.ts +1 -0
  37. package/dist/cjs/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.js +93 -0
  38. package/dist/cjs/swappers/RelaySwapper/endpoints.js +3 -18
  39. package/dist/cjs/swappers/RelaySwapper/utils/relayStatus.d.ts +8 -0
  40. package/dist/cjs/swappers/RelaySwapper/utils/relayStatus.js +39 -0
  41. package/dist/cjs/swappers/RelaySwapper/utils/types.d.ts +14 -0
  42. package/dist/cjs/swappers/ThorchainSwapper/generated/generatedThorLongtailTokens.json +1 -0
  43. package/dist/cjs/types.d.ts +2 -0
  44. package/dist/cjs/utils/cowswap/index.d.ts +4 -1
  45. package/dist/cjs/utils/cowswap/index.js +5 -1
  46. package/dist/cjs/utils/helpers.d.ts +4 -1
  47. package/dist/cjs/utils/helpers.js +19 -1
  48. package/dist/cjs/utils/helpers.test.js +41 -0
  49. package/dist/esm/.tsbuildinfo +1 -1
  50. package/dist/esm/swappers/BobGatewaySwapper/swapperApi/getTradeQuote.js +5 -1
  51. package/dist/esm/swappers/BobGatewaySwapper/swapperApi/getTradeRate.js +11 -2
  52. package/dist/esm/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.d.ts +2 -1
  53. package/dist/esm/swappers/BobGatewaySwapper/utils/getBobGatewayTradeContext.js +2 -1
  54. package/dist/esm/swappers/BobGatewaySwapper/utils/helpers.d.ts +2 -1
  55. package/dist/esm/swappers/BobGatewaySwapper/utils/helpers.js +2 -1
  56. package/dist/esm/swappers/ChainflipSwapper/ChainflipSwapper.js +1 -0
  57. package/dist/esm/swappers/ChainflipSwapper/checkTradeStatus.test.d.ts +1 -0
  58. package/dist/esm/swappers/ChainflipSwapper/checkTradeStatus.test.js +46 -0
  59. package/dist/esm/swappers/ChainflipSwapper/endpoints.js +5 -2
  60. package/dist/esm/swappers/ChainflipSwapper/types.d.ts +3 -0
  61. package/dist/esm/swappers/NearIntentsSwapper/NearIntentsSwapper.js +1 -0
  62. package/dist/esm/swappers/NearIntentsSwapper/checkTradeStatus.test.d.ts +1 -0
  63. package/dist/esm/swappers/NearIntentsSwapper/checkTradeStatus.test.js +50 -0
  64. package/dist/esm/swappers/NearIntentsSwapper/constants.d.ts +1 -1
  65. package/dist/esm/swappers/NearIntentsSwapper/constants.js +3 -2
  66. package/dist/esm/swappers/NearIntentsSwapper/endpoints.js +2 -1
  67. package/dist/esm/swappers/NearIntentsSwapper/utils/helpers.d.ts +2 -2
  68. package/dist/esm/swappers/NearIntentsSwapper/utils/helpers.js +5 -5
  69. package/dist/esm/swappers/NearIntentsSwapper/utils/quoteDeadline.test.d.ts +1 -0
  70. package/dist/esm/swappers/NearIntentsSwapper/utils/quoteDeadline.test.js +26 -0
  71. package/dist/esm/swappers/PortalsSwapper/endpoints.js +53 -49
  72. package/dist/esm/swappers/PortalsSwapper/getPortalsTradeQuote/getPortalsTradeQuote.js +1 -1
  73. package/dist/esm/swappers/PortalsSwapper/getPortalsTradeRate/getPortalsTradeRate.js +1 -1
  74. package/dist/esm/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.d.ts +0 -1
  75. package/dist/esm/swappers/PortalsSwapper/utils/fetchAxelarscanStatus.js +2 -4
  76. package/dist/esm/swappers/PortalsSwapper/utils/fetchPortalsTradeOrder.js +6 -3
  77. package/dist/esm/swappers/PortalsSwapper/utils/fetchSquidStatus.d.ts +0 -1
  78. package/dist/esm/swappers/PortalsSwapper/utils/fetchSquidStatus.js +0 -6
  79. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsStepData.js +10 -2
  80. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsStepData.test.d.ts +1 -0
  81. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsStepData.test.js +68 -0
  82. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.d.ts +2 -2
  83. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.js +28 -16
  84. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.d.ts +1 -0
  85. package/dist/esm/swappers/PortalsSwapper/utils/getPortalsTradeContext.test.js +91 -0
  86. package/dist/esm/swappers/RelaySwapper/endpoints.js +3 -18
  87. package/dist/esm/swappers/RelaySwapper/utils/relayStatus.d.ts +8 -0
  88. package/dist/esm/swappers/RelaySwapper/utils/relayStatus.js +32 -0
  89. package/dist/esm/swappers/RelaySwapper/utils/types.d.ts +14 -0
  90. package/dist/esm/swappers/ThorchainSwapper/generated/generatedThorLongtailTokens.json +1 -0
  91. package/dist/esm/types.d.ts +2 -0
  92. package/dist/esm/utils/cowswap/index.d.ts +4 -1
  93. package/dist/esm/utils/cowswap/index.js +3 -0
  94. package/dist/esm/utils/helpers.d.ts +4 -1
  95. package/dist/esm/utils/helpers.js +17 -0
  96. package/dist/esm/utils/helpers.test.js +42 -1
  97. package/package.json +7 -7
@@ -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
+ });
@@ -15,12 +15,12 @@ type PortalsTradeContext = {
15
15
  tx: PortalsTx;
16
16
  };
17
17
  };
18
- export declare const getPortalsTradeContext: ({ input, deps, sellChainId, inputToken, orderContext, tx, }: {
18
+ export declare const getPortalsTradeContext: ({ input, deps, sellChainId, orderContext, outputToken, tx, }: {
19
19
  input: PortalsTradeQuoteInput | PortalsTradeRateInput;
20
20
  deps: SwapperDeps;
21
21
  sellChainId: PortalsSupportedChainId;
22
- inputToken: string;
23
22
  orderContext: PortalsTradeOrderResponse['context'];
23
+ outputToken: string;
24
24
  tx: PortalsTx;
25
25
  }) => Result<PortalsTradeContext, SwapErrorRight>;
26
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,
@@ -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
+ });
@@ -11,6 +11,7 @@ import { getExactOutputTradeRate, getTradeRate } from './getTradeRate/getTradeRa
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 = {
@@ -66,7 +67,7 @@ export const relayApi = {
66
67
  }
67
68
  // relay.link tracks the swap by its origin chain transaction
68
69
  const swapperTxId = txHash;
69
- const swapperTxLink = `https://relay.link/transaction/${txHash}`;
70
+ const swapperTxLink = getRelayTrackingLink(txHash);
70
71
  const maybeStatusResponse = await relayService.get(`${config.VITE_RELAY_API_URL}/intents/status/v3?requestId=${relayMetadata.relayId}`, getRelayRequestConfig(config));
71
72
  if (maybeStatusResponse.isErr()) {
72
73
  return {
@@ -78,23 +79,7 @@ export const relayApi = {
78
79
  };
79
80
  }
80
81
  const { data: statusResponse } = maybeStatusResponse.unwrap();
81
- const status = (() => {
82
- switch (statusResponse.status) {
83
- case 'success':
84
- return TxStatus.Confirmed;
85
- case 'waiting':
86
- case 'delayed':
87
- case 'pending':
88
- case 'depositing':
89
- case 'submitted':
90
- return TxStatus.Pending;
91
- case 'failure':
92
- case 'refund':
93
- return TxStatus.Failed;
94
- default:
95
- return TxStatus.Unknown;
96
- }
97
- })();
82
+ const status = relayStatusToTxStatus(statusResponse.status);
98
83
  // Relay refers to in Txs as "inTxHashes" but to out Txs as simply "txHashes" when they really mean "outTxHashes"
99
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
100
85
  const outTxHashes = statusResponse.txHashes;
@@ -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
+ };
@@ -12,6 +12,20 @@ export type RelayMetadata = {
12
12
  relayId: string;
13
13
  data?: string;
14
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
+ };
15
29
  export type RelayStatus = {
16
30
  status: 'success' | 'failure' | 'pending' | 'submitted' | 'depositing' | 'refund' | 'delayed' | 'waiting';
17
31
  details?: string;
@@ -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",
@@ -574,6 +574,7 @@ export type CheckTradeStatusInput = {
574
574
  export type TradeStatus = {
575
575
  status: TxStatus;
576
576
  buyTxHash: string | undefined;
577
+ sellTxHash?: string | undefined;
577
578
  swapperTxId?: string | undefined;
578
579
  swapperTxLink?: string | undefined;
579
580
  message: string | [string, InterpolationOptions] | undefined;
@@ -583,6 +584,7 @@ export type TradeQuoteResult = Result<TradeQuote[], SwapErrorRight>;
583
584
  export type TradeRateResult = Result<TradeRate[], SwapErrorRight>;
584
585
  export type EvmMessageToSign = CowMessageToSign;
585
586
  export type Swapper = {
587
+ supportsExternalPayment?: boolean;
586
588
  executeEvmTransaction?: (txToSign: SignTx<EvmChainId>, callbacks: EvmTransactionExecutionProps) => Promise<string>;
587
589
  executeEvmMessage?: (txMetaToSign: EvmMessageToSign, callbacks: EvmMessageExecutionProps, config: SwapperConfig) => Promise<string>;
588
590
  executeUtxoTransaction?: (txToSign: SignTx<UtxoChainId>, callbacks: UtxoTransactionExecutionProps) => Promise<string>;
@@ -1,4 +1,5 @@
1
- import type { OrderClass1 } from '@cowprotocol/app-data/dist/generatedTypes/v1.3.0';
1
+ import type { AppDataRootSchema as AppDataRootSchemaLegacy } from '@cowprotocol/app-data/dist/generatedTypes/v0.4.0';
2
+ import type { AppDataRootSchema as AppDataRootSchemaLatest, OrderClass1 } from '@cowprotocol/app-data/dist/generatedTypes/v1.3.0';
2
3
  import type { ChainId } from '@shapeshiftoss/caip';
3
4
  import type { EvmChainAdapter } from '@shapeshiftoss/chain-adapters';
4
5
  import type { HDWallet } from '@shapeshiftoss/hdwallet-core';
@@ -22,3 +23,5 @@ export declare const getFullAppData: (slippageTolerancePercentage: string, affil
22
23
  appDataHash: string;
23
24
  appData: string;
24
25
  }>;
26
+ export type ParsedAppData = AppDataRootSchemaLatest | AppDataRootSchemaLegacy;
27
+ export declare const isLegacyAppData: (appData: ParsedAppData) => appData is AppDataRootSchemaLegacy;
@@ -158,3 +158,6 @@ export const getFullAppData = async (slippageTolerancePercentage, affiliateAppDa
158
158
  const { fullAppData, appDataKeccak256 } = await generateAppDataFromDoc(appDataDoc);
159
159
  return { appDataHash: appDataKeccak256, appData: fullAppData };
160
160
  };
161
+ export const isLegacyAppData = (appData) => {
162
+ return appData.version === '0.4.0';
163
+ };
@@ -1,5 +1,6 @@
1
1
  import type { AssetId, ChainId } from '@shapeshiftoss/caip';
2
- import type { TradeAmount } from '../types.js';
2
+ import type { TradeAmount, TradeStepCommon } from '../types.js';
3
+ import { SwapperName } from '../types.js';
3
4
  export declare const FALLBACK_QUOTE_DEADLINE_MS = 60000;
4
5
  export declare const getTradeAmount: (input: {
5
6
  sellAmountIncludingProtocolFeesCryptoBaseUnit: string;
@@ -9,3 +10,5 @@ export declare const getTradeAmount: (input: {
9
10
  export declare const normalizeEpochToMs: (value: number) => number;
10
11
  export declare const isNativeEvmAsset: (assetId: AssetId) => boolean;
11
12
  export declare const getTreasuryAddressFromChainId: (chainId: ChainId) => string;
13
+ export type DepositAddressStep = Pick<TradeStepCommon, 'chainflipSpecific' | 'swapperMetadata'>;
14
+ export declare const getDepositAddress: (step: DepositAddressStep, swapperName: SwapperName) => string | undefined;
@@ -1,6 +1,7 @@
1
1
  import { arbitrumAssetId, avalancheAssetId, baseAssetId, bscAssetId, ethAssetId, fromAssetId, gnosisAssetId, katanaAssetId, monadAssetId, optimismAssetId, plasmaAssetId, polygonAssetId, } from '@shapeshiftoss/caip';
2
2
  import { KnownChainIds } from '@shapeshiftoss/types';
3
3
  import { DAO_TREASURY_ARBITRUM, DAO_TREASURY_AVALANCHE, DAO_TREASURY_BASE, DAO_TREASURY_BITCOIN, DAO_TREASURY_BOB, DAO_TREASURY_BSC, DAO_TREASURY_ETHEREUM_MAINNET, DAO_TREASURY_GNOSIS, DAO_TREASURY_HYPEREVM, DAO_TREASURY_MONAD, DAO_TREASURY_OPTIMISM, DAO_TREASURY_POLYGON, DAO_TREASURY_SOLANA, DAO_TREASURY_STARKNET, DAO_TREASURY_TON, isTreasuryChainId, } from '@shapeshiftoss/utils';
4
+ import { SwapperName } from '../types.js';
4
5
  // Deadline for providers without their own expiry - short enough to keep priced amounts honest
5
6
  export const FALLBACK_QUOTE_DEADLINE_MS = 60_000;
6
7
  export const getTradeAmount = (input) => 'buyAmountCryptoBaseUnit' in input
@@ -71,3 +72,19 @@ export const getTreasuryAddressFromChainId = (chainId) => {
71
72
  throw new Error(`[getTreasuryAddressFromChainId] - Unsupported chainId: ${chainId}`);
72
73
  return treasuryAddress;
73
74
  };
75
+ export const getDepositAddress = (step, swapperName) => {
76
+ switch (swapperName) {
77
+ case SwapperName.Chainflip:
78
+ return step.chainflipSpecific?.depositAddress || undefined;
79
+ case SwapperName.NearIntents: {
80
+ if (step.swapperMetadata?.name !== 'nearIntents')
81
+ return;
82
+ const { depositAddress, depositMemo } = step.swapperMetadata;
83
+ if (depositMemo)
84
+ return;
85
+ return depositAddress || undefined;
86
+ }
87
+ default:
88
+ return;
89
+ }
90
+ };
@@ -2,7 +2,9 @@ import { thorchainChainId } from '@shapeshiftoss/caip';
2
2
  import { KnownChainIds } from '@shapeshiftoss/types';
3
3
  import { treasuryChainIds } from '@shapeshiftoss/utils';
4
4
  import { describe, expect, it } from 'vitest';
5
- import { getTreasuryAddressFromChainId, normalizeEpochToMs } from './helpers.js';
5
+ import { swappers } from '../constants.js';
6
+ import { SwapperName } from '../types.js';
7
+ import { getDepositAddress, getTreasuryAddressFromChainId, normalizeEpochToMs } from './helpers.js';
6
8
  describe('getTreasuryAddressFromChainId', () => {
7
9
  // Affiliate and fee recipient addresses for every swapper flow through here, so pin the values
8
10
  // rather than the wiring - a chain pointing at another chain's treasury misroutes revenue silently
@@ -47,3 +49,42 @@ describe('normalizeEpochToMs', () => {
47
49
  expect(normalizeEpochToMs(epochMs * 1e6)).toBe(epochMs);
48
50
  });
49
51
  });
52
+ const makeStep = (step) => step;
53
+ describe('getDepositAddress', () => {
54
+ it('reads the chainflip deposit address', () => {
55
+ const step = makeStep({ chainflipSpecific: { depositAddress: 'bc1qdeposit' } });
56
+ expect(getDepositAddress(step, SwapperName.Chainflip)).toBe('bc1qdeposit');
57
+ });
58
+ it('returns undefined when chainflip has no deposit address', () => {
59
+ expect(getDepositAddress(makeStep({}), SwapperName.Chainflip)).toBeUndefined();
60
+ });
61
+ it('reads the near intents deposit address', () => {
62
+ const step = makeStep({
63
+ swapperMetadata: { name: 'nearIntents', depositAddress: '0xdeposit' },
64
+ });
65
+ expect(getDepositAddress(step, SwapperName.NearIntents)).toBe('0xdeposit');
66
+ });
67
+ it('rejects a memo-bound near intents deposit address', () => {
68
+ const step = makeStep({
69
+ swapperMetadata: { name: 'nearIntents', depositAddress: 'EQdeposit', depositMemo: '12345' },
70
+ });
71
+ expect(getDepositAddress(step, SwapperName.NearIntents)).toBeUndefined();
72
+ });
73
+ it('rejects an empty near intents deposit address', () => {
74
+ const step = makeStep({ swapperMetadata: { name: 'nearIntents', depositAddress: '' } });
75
+ expect(getDepositAddress(step, SwapperName.NearIntents)).toBeUndefined();
76
+ });
77
+ it('returns undefined for swappers that do not use deposit addresses', () => {
78
+ const step = makeStep({ chainflipSpecific: { depositAddress: 'bc1qdeposit' } });
79
+ expect(getDepositAddress(step, SwapperName.Relay)).toBeUndefined();
80
+ });
81
+ });
82
+ describe('supportsExternalPayment', () => {
83
+ it('is flagged on exactly the externally paid swappers', () => {
84
+ const flagged = Object.entries(swappers)
85
+ .filter(([, swapper]) => swapper?.supportsExternalPayment)
86
+ .map(([name]) => name)
87
+ .sort();
88
+ expect(flagged).toEqual([SwapperName.Chainflip, SwapperName.NearIntents].sort());
89
+ });
90
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshiftoss/swapper",
3
- "version": "20.0.0",
3
+ "version": "20.1.0",
4
4
  "repository": "https://github.com/shapeshift/web",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,7 +28,7 @@
28
28
  "@coral-xyz/anchor": "0.29.0",
29
29
  "@cowprotocol/app-data": "^2.3.0",
30
30
  "@defuse-protocol/one-click-sdk-typescript": "^0.1.1-0.2",
31
- "@gobob/bob-sdk": "5.8.1",
31
+ "@gobob/bob-sdk": "5.11.1",
32
32
  "@mysten/sui": "^1.45.2",
33
33
  "@shapeshiftoss/bitcoinjs-lib": "7.0.0-shapeshift.0",
34
34
  "@sniptt/monads": "^0.5.10",
@@ -50,13 +50,13 @@
50
50
  "retry-axios": "^2.6.0",
51
51
  "uuid": "^9.0.0",
52
52
  "viem": "2.55.11",
53
- "@shapeshiftoss/chain-adapters": "^11.5.1",
53
+ "@shapeshiftoss/caip": "^8.16.10",
54
54
  "@shapeshiftoss/hdwallet-core": "^1.62.41",
55
55
  "@shapeshiftoss/contracts": "^1.0.7",
56
- "@shapeshiftoss/caip": "^8.16.10",
57
- "@shapeshiftoss/unchained-client": "^10.14.11",
58
- "@shapeshiftoss/utils": "^1.1.0",
59
- "@shapeshiftoss/types": "^9.0.0"
56
+ "@shapeshiftoss/types": "^9.0.1",
57
+ "@shapeshiftoss/chain-adapters": "^11.5.1",
58
+ "@shapeshiftoss/utils": "^1.1.1",
59
+ "@shapeshiftoss/unchained-client": "^10.14.11"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/lodash": "^4.14.178",