@eth-optimism/actions-sdk 0.2.0 → 0.3.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.
- package/dist/__mocks__/MockAssets.d.ts.map +1 -1
- package/dist/__mocks__/MockAssets.js +2 -0
- package/dist/__mocks__/MockAssets.js.map +1 -1
- package/dist/__tests__/actions.test.js +0 -4
- package/dist/__tests__/actions.test.js.map +1 -1
- package/dist/actions.d.ts +20 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +37 -13
- package/dist/actions.js.map +1 -1
- package/dist/constants/assets.d.ts +4 -0
- package/dist/constants/assets.d.ts.map +1 -1
- package/dist/constants/assets.js +15 -0
- package/dist/constants/assets.js.map +1 -1
- package/dist/constants/contracts.d.ts +4 -0
- package/dist/constants/contracts.d.ts.map +1 -0
- package/dist/constants/contracts.js +3 -0
- package/dist/constants/contracts.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lend/core/LendProvider.d.ts +0 -6
- package/dist/lend/core/LendProvider.d.ts.map +1 -1
- package/dist/lend/core/LendProvider.js +6 -15
- package/dist/lend/core/LendProvider.js.map +1 -1
- package/dist/lend/core/__tests__/LendProvider.test.js +2 -4
- package/dist/lend/core/__tests__/LendProvider.test.js.map +1 -1
- package/dist/lend/providers/morpho/__tests__/sdk.test.js +3 -2
- package/dist/lend/providers/morpho/__tests__/sdk.test.js.map +1 -1
- package/dist/lend/providers/morpho/api.d.ts +4 -0
- package/dist/lend/providers/morpho/api.d.ts.map +1 -1
- package/dist/lend/providers/morpho/api.js.map +1 -1
- package/dist/lend/providers/morpho/sdk.d.ts.map +1 -1
- package/dist/lend/providers/morpho/sdk.js +10 -1
- package/dist/lend/providers/morpho/sdk.js.map +1 -1
- package/dist/supported/tokens.d.ts.map +1 -1
- package/dist/supported/tokens.js +9 -2
- package/dist/supported/tokens.js.map +1 -1
- package/dist/swap/__mocks__/MockSwapProvider.d.ts +38 -0
- package/dist/swap/__mocks__/MockSwapProvider.d.ts.map +1 -0
- package/dist/swap/__mocks__/MockSwapProvider.js +138 -0
- package/dist/swap/__mocks__/MockSwapProvider.js.map +1 -0
- package/dist/swap/core/SwapProvider.d.ts +56 -0
- package/dist/swap/core/SwapProvider.d.ts.map +1 -0
- package/dist/swap/core/SwapProvider.js +177 -0
- package/dist/swap/core/SwapProvider.js.map +1 -0
- package/dist/swap/core/__tests__/SwapProvider.test.d.ts +2 -0
- package/dist/swap/core/__tests__/SwapProvider.test.d.ts.map +1 -0
- package/dist/swap/core/__tests__/SwapProvider.test.js +329 -0
- package/dist/swap/core/__tests__/SwapProvider.test.js.map +1 -0
- package/dist/swap/index.d.ts +7 -0
- package/dist/swap/index.d.ts.map +1 -0
- package/dist/swap/index.js +8 -0
- package/dist/swap/index.js.map +1 -0
- package/dist/swap/namespaces/ActionsSwapNamespace.d.ts +8 -0
- package/dist/swap/namespaces/ActionsSwapNamespace.d.ts.map +1 -0
- package/dist/swap/namespaces/ActionsSwapNamespace.js +8 -0
- package/dist/swap/namespaces/ActionsSwapNamespace.js.map +1 -0
- package/dist/swap/namespaces/BaseSwapNamespace.d.ts +33 -0
- package/dist/swap/namespaces/BaseSwapNamespace.d.ts.map +1 -0
- package/dist/swap/namespaces/BaseSwapNamespace.js +58 -0
- package/dist/swap/namespaces/BaseSwapNamespace.js.map +1 -0
- package/dist/swap/namespaces/WalletSwapNamespace.d.ts +22 -0
- package/dist/swap/namespaces/WalletSwapNamespace.d.ts.map +1 -0
- package/dist/swap/namespaces/WalletSwapNamespace.js +60 -0
- package/dist/swap/namespaces/WalletSwapNamespace.js.map +1 -0
- package/dist/swap/namespaces/__tests__/BaseSwapNamespace.spec.d.ts +2 -0
- package/dist/swap/namespaces/__tests__/BaseSwapNamespace.spec.d.ts.map +1 -0
- package/dist/swap/namespaces/__tests__/BaseSwapNamespace.spec.js +106 -0
- package/dist/swap/namespaces/__tests__/BaseSwapNamespace.spec.js.map +1 -0
- package/dist/swap/namespaces/__tests__/WalletSwapNamespace.spec.d.ts +2 -0
- package/dist/swap/namespaces/__tests__/WalletSwapNamespace.spec.d.ts.map +1 -0
- package/dist/swap/namespaces/__tests__/WalletSwapNamespace.spec.js +132 -0
- package/dist/swap/namespaces/__tests__/WalletSwapNamespace.spec.js.map +1 -0
- package/dist/swap/providers/uniswap/UniswapSwapProvider.d.ts +68 -0
- package/dist/swap/providers/uniswap/UniswapSwapProvider.d.ts.map +1 -0
- package/dist/swap/providers/uniswap/UniswapSwapProvider.js +206 -0
- package/dist/swap/providers/uniswap/UniswapSwapProvider.js.map +1 -0
- package/dist/swap/providers/uniswap/__tests__/UniswapSwapProvider.test.d.ts +2 -0
- package/dist/swap/providers/uniswap/__tests__/UniswapSwapProvider.test.d.ts.map +1 -0
- package/dist/swap/providers/uniswap/__tests__/UniswapSwapProvider.test.js +257 -0
- package/dist/swap/providers/uniswap/__tests__/UniswapSwapProvider.test.js.map +1 -0
- package/dist/swap/providers/uniswap/__tests__/sdk.test.d.ts +2 -0
- package/dist/swap/providers/uniswap/__tests__/sdk.test.d.ts.map +1 -0
- package/dist/swap/providers/uniswap/__tests__/sdk.test.js +312 -0
- package/dist/swap/providers/uniswap/__tests__/sdk.test.js.map +1 -0
- package/dist/swap/providers/uniswap/abis.d.ts +227 -0
- package/dist/swap/providers/uniswap/abis.d.ts.map +1 -0
- package/dist/swap/providers/uniswap/abis.js +138 -0
- package/dist/swap/providers/uniswap/abis.js.map +1 -0
- package/dist/swap/providers/uniswap/addresses.d.ts +21 -0
- package/dist/swap/providers/uniswap/addresses.d.ts.map +1 -0
- package/dist/swap/providers/uniswap/addresses.js +81 -0
- package/dist/swap/providers/uniswap/addresses.js.map +1 -0
- package/dist/swap/providers/uniswap/encoding.d.ts +77 -0
- package/dist/swap/providers/uniswap/encoding.d.ts.map +1 -0
- package/dist/swap/providers/uniswap/encoding.js +233 -0
- package/dist/swap/providers/uniswap/encoding.js.map +1 -0
- package/dist/swap/providers/uniswap/types.d.ts +20 -0
- package/dist/swap/providers/uniswap/types.d.ts.map +1 -0
- package/dist/swap/providers/uniswap/types.js +2 -0
- package/dist/swap/providers/uniswap/types.js.map +1 -0
- package/dist/types/actions.d.ts +19 -5
- package/dist/types/actions.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/lend/base.d.ts +3 -13
- package/dist/types/lend/base.d.ts.map +1 -1
- package/dist/types/lend/base.js.map +1 -1
- package/dist/types/swap/base.d.ts +238 -0
- package/dist/types/swap/base.d.ts.map +1 -0
- package/dist/types/swap/base.js +4 -0
- package/dist/types/swap/base.js.map +1 -0
- package/dist/types/swap/index.d.ts +2 -0
- package/dist/types/swap/index.d.ts.map +1 -0
- package/dist/types/swap/index.js +2 -0
- package/dist/types/swap/index.js.map +1 -0
- package/dist/types/transaction.d.ts +14 -0
- package/dist/types/transaction.d.ts.map +1 -0
- package/dist/types/transaction.js +2 -0
- package/dist/types/transaction.js.map +1 -0
- package/dist/utils/assets.d.ts +4 -5
- package/dist/utils/assets.d.ts.map +1 -1
- package/dist/utils/assets.js +4 -11
- package/dist/utils/assets.js.map +1 -1
- package/dist/utils/assets.test.js +13 -1
- package/dist/utils/assets.test.js.map +1 -1
- package/dist/utils/permit2.d.ts +46 -0
- package/dist/utils/permit2.d.ts.map +1 -0
- package/dist/utils/permit2.js +100 -0
- package/dist/utils/permit2.js.map +1 -0
- package/dist/utils/permit2.test.d.ts +2 -0
- package/dist/utils/permit2.test.d.ts.map +1 -0
- package/dist/utils/permit2.test.js +110 -0
- package/dist/utils/permit2.test.js.map +1 -0
- package/dist/utils/validation.d.ts +12 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +44 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/wallet/core/providers/hosted/abstract/HostedWalletProvider.d.ts +7 -1
- package/dist/wallet/core/providers/hosted/abstract/HostedWalletProvider.d.ts.map +1 -1
- package/dist/wallet/core/providers/hosted/abstract/HostedWalletProvider.js +2 -1
- package/dist/wallet/core/providers/hosted/abstract/HostedWalletProvider.js.map +1 -1
- package/dist/wallet/core/providers/hosted/types/index.d.ts +5 -1
- package/dist/wallet/core/providers/hosted/types/index.d.ts.map +1 -1
- package/dist/wallet/core/providers/smart/default/DefaultSmartWalletProvider.d.ts +7 -1
- package/dist/wallet/core/providers/smart/default/DefaultSmartWalletProvider.d.ts.map +1 -1
- package/dist/wallet/core/providers/smart/default/DefaultSmartWalletProvider.js +5 -1
- package/dist/wallet/core/providers/smart/default/DefaultSmartWalletProvider.js.map +1 -1
- package/dist/wallet/core/providers/smart/default/__tests__/DefaultSmartWalletProvider.spec.js +2 -2
- package/dist/wallet/core/providers/smart/default/__tests__/DefaultSmartWalletProvider.spec.js.map +1 -1
- package/dist/wallet/core/wallets/abstract/Wallet.d.ts +13 -2
- package/dist/wallet/core/wallets/abstract/Wallet.d.ts.map +1 -1
- package/dist/wallet/core/wallets/abstract/Wallet.js +7 -1
- package/dist/wallet/core/wallets/abstract/Wallet.js.map +1 -1
- package/dist/wallet/core/wallets/smart/default/DefaultSmartWallet.d.ts +7 -2
- package/dist/wallet/core/wallets/smart/default/DefaultSmartWallet.d.ts.map +1 -1
- package/dist/wallet/core/wallets/smart/default/DefaultSmartWallet.js +6 -5
- package/dist/wallet/core/wallets/smart/default/DefaultSmartWallet.js.map +1 -1
- package/dist/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.d.ts +6 -1
- package/dist/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.d.ts.map +1 -1
- package/dist/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.js +3 -1
- package/dist/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.js.map +1 -1
- package/dist/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.d.ts.map +1 -1
- package/dist/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.js +4 -3
- package/dist/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.js.map +1 -1
- package/dist/wallet/node/providers/hosted/turnkey/TurnkeyHostedWalletProvider.d.ts +5 -1
- package/dist/wallet/node/providers/hosted/turnkey/TurnkeyHostedWalletProvider.d.ts.map +1 -1
- package/dist/wallet/node/providers/hosted/turnkey/TurnkeyHostedWalletProvider.js +4 -2
- package/dist/wallet/node/providers/hosted/turnkey/TurnkeyHostedWalletProvider.js.map +1 -1
- package/dist/wallet/node/wallets/hosted/privy/PrivyWallet.d.ts +6 -1
- package/dist/wallet/node/wallets/hosted/privy/PrivyWallet.d.ts.map +1 -1
- package/dist/wallet/node/wallets/hosted/privy/PrivyWallet.js +4 -3
- package/dist/wallet/node/wallets/hosted/privy/PrivyWallet.js.map +1 -1
- package/dist/wallet/node/wallets/hosted/turnkey/TurnkeyWallet.d.ts +5 -1
- package/dist/wallet/node/wallets/hosted/turnkey/TurnkeyWallet.d.ts.map +1 -1
- package/dist/wallet/node/wallets/hosted/turnkey/TurnkeyWallet.js +2 -2
- package/dist/wallet/node/wallets/hosted/turnkey/TurnkeyWallet.js.map +1 -1
- package/dist/wallet/react/providers/hosted/dynamic/DynamicHostedWalletProvider.d.ts +7 -1
- package/dist/wallet/react/providers/hosted/dynamic/DynamicHostedWalletProvider.d.ts.map +1 -1
- package/dist/wallet/react/providers/hosted/dynamic/DynamicHostedWalletProvider.js +6 -2
- package/dist/wallet/react/providers/hosted/dynamic/DynamicHostedWalletProvider.js.map +1 -1
- package/dist/wallet/react/providers/hosted/dynamic/__tests__/DynamicHostedWalletProvider.spec.js +1 -0
- package/dist/wallet/react/providers/hosted/dynamic/__tests__/DynamicHostedWalletProvider.spec.js.map +1 -1
- package/dist/wallet/react/providers/hosted/privy/PrivyHostedWalletProvider.d.ts +6 -1
- package/dist/wallet/react/providers/hosted/privy/PrivyHostedWalletProvider.d.ts.map +1 -1
- package/dist/wallet/react/providers/hosted/privy/PrivyHostedWalletProvider.js +5 -2
- package/dist/wallet/react/providers/hosted/privy/PrivyHostedWalletProvider.js.map +1 -1
- package/dist/wallet/react/providers/hosted/privy/__tests__/PrivyHostedWalletProvider.spec.js +1 -0
- package/dist/wallet/react/providers/hosted/privy/__tests__/PrivyHostedWalletProvider.spec.js.map +1 -1
- package/dist/wallet/react/providers/hosted/turnkey/TurnkeyHostedWalletProvider.d.ts +6 -3
- package/dist/wallet/react/providers/hosted/turnkey/TurnkeyHostedWalletProvider.d.ts.map +1 -1
- package/dist/wallet/react/providers/hosted/turnkey/TurnkeyHostedWalletProvider.js +5 -4
- package/dist/wallet/react/providers/hosted/turnkey/TurnkeyHostedWalletProvider.js.map +1 -1
- package/dist/wallet/react/providers/registry/ReactHostedWalletProviderRegistry.d.ts.map +1 -1
- package/dist/wallet/react/providers/registry/ReactHostedWalletProviderRegistry.js +6 -6
- package/dist/wallet/react/providers/registry/ReactHostedWalletProviderRegistry.js.map +1 -1
- package/dist/wallet/react/wallets/hosted/dynamic/DynamicWallet.d.ts +7 -1
- package/dist/wallet/react/wallets/hosted/dynamic/DynamicWallet.d.ts.map +1 -1
- package/dist/wallet/react/wallets/hosted/dynamic/DynamicWallet.js +5 -3
- package/dist/wallet/react/wallets/hosted/dynamic/DynamicWallet.js.map +1 -1
- package/dist/wallet/react/wallets/hosted/privy/PrivyWallet.d.ts +5 -1
- package/dist/wallet/react/wallets/hosted/privy/PrivyWallet.d.ts.map +1 -1
- package/dist/wallet/react/wallets/hosted/privy/PrivyWallet.js +3 -3
- package/dist/wallet/react/wallets/hosted/privy/PrivyWallet.js.map +1 -1
- package/dist/wallet/react/wallets/hosted/turnkey/TurnkeyWallet.d.ts +5 -1
- package/dist/wallet/react/wallets/hosted/turnkey/TurnkeyWallet.d.ts.map +1 -1
- package/dist/wallet/react/wallets/hosted/turnkey/TurnkeyWallet.js +2 -2
- package/dist/wallet/react/wallets/hosted/turnkey/TurnkeyWallet.js.map +1 -1
- package/package.json +2 -2
- package/src/__mocks__/MockAssets.ts +2 -0
- package/src/__tests__/actions.test.ts +0 -4
- package/src/actions.ts +57 -19
- package/src/constants/assets.ts +16 -0
- package/src/constants/contracts.ts +5 -0
- package/src/index.ts +30 -2
- package/src/lend/core/LendProvider.ts +6 -18
- package/src/lend/core/__tests__/LendProvider.test.ts +2 -5
- package/src/lend/providers/morpho/__tests__/sdk.test.ts +3 -2
- package/src/lend/providers/morpho/api.ts +4 -0
- package/src/lend/providers/morpho/sdk.ts +10 -1
- package/src/supported/tokens.ts +16 -2
- package/src/swap/__mocks__/MockSwapProvider.ts +216 -0
- package/src/swap/core/SwapProvider.ts +319 -0
- package/src/swap/core/__tests__/SwapProvider.test.ts +478 -0
- package/src/swap/index.ts +14 -0
- package/src/swap/namespaces/ActionsSwapNamespace.ts +7 -0
- package/src/swap/namespaces/BaseSwapNamespace.ts +77 -0
- package/src/swap/namespaces/WalletSwapNamespace.ts +82 -0
- package/src/swap/namespaces/__tests__/BaseSwapNamespace.spec.ts +138 -0
- package/src/swap/namespaces/__tests__/WalletSwapNamespace.spec.ts +162 -0
- package/src/swap/providers/uniswap/UniswapSwapProvider.ts +304 -0
- package/src/swap/providers/uniswap/__tests__/UniswapSwapProvider.test.ts +299 -0
- package/src/swap/providers/uniswap/__tests__/sdk.test.ts +370 -0
- package/src/swap/providers/uniswap/abis.ts +144 -0
- package/src/swap/providers/uniswap/addresses.ts +108 -0
- package/src/swap/providers/uniswap/encoding.ts +406 -0
- package/src/swap/providers/uniswap/types.ts +24 -0
- package/src/types/actions.ts +22 -6
- package/src/types/index.ts +2 -0
- package/src/types/lend/base.ts +4 -14
- package/src/types/swap/base.ts +259 -0
- package/src/types/swap/index.ts +1 -0
- package/src/types/transaction.ts +14 -0
- package/src/utils/assets.test.ts +16 -1
- package/src/utils/assets.ts +13 -10
- package/src/utils/permit2.test.ts +142 -0
- package/src/utils/permit2.ts +144 -0
- package/src/utils/validation.ts +76 -0
- package/src/wallet/core/providers/hosted/abstract/HostedWalletProvider.ts +9 -1
- package/src/wallet/core/providers/hosted/types/index.ts +5 -1
- package/src/wallet/core/providers/smart/default/DefaultSmartWalletProvider.ts +13 -1
- package/src/wallet/core/providers/smart/default/__tests__/DefaultSmartWalletProvider.spec.ts +2 -0
- package/src/wallet/core/wallets/abstract/Wallet.ts +18 -2
- package/src/wallet/core/wallets/smart/default/DefaultSmartWallet.ts +14 -5
- package/src/wallet/node/providers/hosted/privy/PrivyHostedWalletProvider.ts +13 -2
- package/src/wallet/node/providers/hosted/registry/NodeHostedWalletProviderRegistry.ts +10 -2
- package/src/wallet/node/providers/hosted/turnkey/TurnkeyHostedWalletProvider.ts +8 -2
- package/src/wallet/node/wallets/hosted/privy/PrivyWallet.ts +11 -2
- package/src/wallet/node/wallets/hosted/turnkey/TurnkeyWallet.ts +10 -2
- package/src/wallet/react/providers/hosted/dynamic/DynamicHostedWalletProvider.ts +10 -2
- package/src/wallet/react/providers/hosted/dynamic/__tests__/DynamicHostedWalletProvider.spec.ts +1 -0
- package/src/wallet/react/providers/hosted/privy/PrivyHostedWalletProvider.ts +9 -2
- package/src/wallet/react/providers/hosted/privy/__tests__/PrivyHostedWalletProvider.spec.ts +1 -0
- package/src/wallet/react/providers/hosted/turnkey/TurnkeyHostedWalletProvider.ts +9 -4
- package/src/wallet/react/providers/registry/ReactHostedWalletProviderRegistry.ts +18 -6
- package/src/wallet/react/wallets/hosted/dynamic/DynamicWallet.ts +12 -2
- package/src/wallet/react/wallets/hosted/privy/PrivyWallet.ts +10 -2
- package/src/wallet/react/wallets/hosted/turnkey/TurnkeyWallet.ts +10 -2
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { Address, PublicClient } from 'viem'
|
|
2
|
+
import { baseSepolia } from 'viem/chains'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { MockWETHAsset } from '@/__mocks__/MockAssets.js'
|
|
6
|
+
import type { SupportedChainId } from '@/constants/supportedChains.js'
|
|
7
|
+
import type { ChainManager } from '@/services/ChainManager.js'
|
|
8
|
+
import type { Asset } from '@/types/asset.js'
|
|
9
|
+
|
|
10
|
+
import type { UniswapSwapProviderConfig } from '../types.js'
|
|
11
|
+
import { UniswapSwapProvider } from '../UniswapSwapProvider.js'
|
|
12
|
+
|
|
13
|
+
const CHAIN_ID = baseSepolia.id as SupportedChainId
|
|
14
|
+
|
|
15
|
+
const USDC: Asset = {
|
|
16
|
+
type: 'erc20',
|
|
17
|
+
address: {
|
|
18
|
+
[CHAIN_ID]: '0x1111111111111111111111111111111111111111' as Address,
|
|
19
|
+
},
|
|
20
|
+
metadata: { name: 'USD Coin', symbol: 'USDC', decimals: 6 },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const OP: Asset = {
|
|
24
|
+
type: 'erc20',
|
|
25
|
+
address: {
|
|
26
|
+
[CHAIN_ID]: '0x3333333333333333333333333333333333333333' as Address,
|
|
27
|
+
},
|
|
28
|
+
metadata: { name: 'Optimism', symbol: 'OP', decimals: 18 },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createMockChainManager(): ChainManager {
|
|
32
|
+
const mockPublicClient = {
|
|
33
|
+
simulateContract: vi.fn().mockResolvedValue({
|
|
34
|
+
result: [500000000000000000n, 150000n],
|
|
35
|
+
}),
|
|
36
|
+
readContract: vi
|
|
37
|
+
.fn()
|
|
38
|
+
.mockImplementation(
|
|
39
|
+
({ functionName, args }: { functionName: string; args: unknown[] }) => {
|
|
40
|
+
if (functionName === 'extsload')
|
|
41
|
+
return Promise.resolve(
|
|
42
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
43
|
+
)
|
|
44
|
+
// Permit2 allowance: 3 args (owner, token, spender)
|
|
45
|
+
if (args?.length === 3) return Promise.resolve([0n, 0, 0])
|
|
46
|
+
// ERC20 allowance: 2 args (owner, spender)
|
|
47
|
+
return Promise.resolve(0n)
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
} as unknown as PublicClient
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
getPublicClient: vi.fn().mockReturnValue(mockPublicClient),
|
|
54
|
+
getSupportedChains: vi.fn().mockReturnValue([CHAIN_ID]),
|
|
55
|
+
} as unknown as ChainManager
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createProvider(
|
|
59
|
+
configOverrides?: Partial<UniswapSwapProviderConfig>,
|
|
60
|
+
): UniswapSwapProvider {
|
|
61
|
+
const config: UniswapSwapProviderConfig = {
|
|
62
|
+
defaultSlippage: 0.005,
|
|
63
|
+
marketAllowlist: [
|
|
64
|
+
{ assets: [USDC, OP], fee: 100, tickSpacing: 2, chainId: CHAIN_ID },
|
|
65
|
+
],
|
|
66
|
+
...configOverrides,
|
|
67
|
+
}
|
|
68
|
+
return new UniswapSwapProvider(config, createMockChainManager())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('UniswapSwapProvider', () => {
|
|
72
|
+
describe('supportedChainIds', () => {
|
|
73
|
+
it('returns Base Sepolia', () => {
|
|
74
|
+
const provider = createProvider()
|
|
75
|
+
expect(provider.supportedChainIds()).toContain(CHAIN_ID)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('execute', () => {
|
|
80
|
+
it('returns swap transaction with approval data', async () => {
|
|
81
|
+
const provider = createProvider()
|
|
82
|
+
const result = await provider.execute({
|
|
83
|
+
amountIn: 100,
|
|
84
|
+
assetIn: USDC,
|
|
85
|
+
assetOut: OP,
|
|
86
|
+
chainId: CHAIN_ID,
|
|
87
|
+
walletAddress: '0xwallet' as Address,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(result.transactionData.swap).toBeDefined()
|
|
91
|
+
expect(result.transactionData.swap.to).toBeDefined()
|
|
92
|
+
expect(result.transactionData.swap.data).toMatch(/^0x/)
|
|
93
|
+
expect(result.amountIn).toBeDefined()
|
|
94
|
+
expect(result.amountOut).toBeDefined()
|
|
95
|
+
expect(result.price).toBeDefined()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('includes token approval when allowance is insufficient', async () => {
|
|
99
|
+
const provider = createProvider()
|
|
100
|
+
const result = await provider.execute({
|
|
101
|
+
amountIn: 100,
|
|
102
|
+
assetIn: USDC,
|
|
103
|
+
assetOut: OP,
|
|
104
|
+
chainId: CHAIN_ID,
|
|
105
|
+
walletAddress: '0xwallet' as Address,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Mock readContract returns 0n (no allowance), so approvals should be needed
|
|
109
|
+
expect(result.transactionData.tokenApproval).toBeDefined()
|
|
110
|
+
expect(result.transactionData.permit2Approval).toBeDefined()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('throws without fee/tickSpacing in market filter', async () => {
|
|
114
|
+
const provider = createProvider({
|
|
115
|
+
marketAllowlist: [{ assets: [USDC, OP], chainId: CHAIN_ID }],
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
await expect(
|
|
119
|
+
provider.execute({
|
|
120
|
+
amountIn: 100,
|
|
121
|
+
assetIn: USDC,
|
|
122
|
+
assetOut: OP,
|
|
123
|
+
chainId: CHAIN_ID,
|
|
124
|
+
walletAddress: '0xwallet' as Address,
|
|
125
|
+
}),
|
|
126
|
+
).rejects.toThrow('fee and tickSpacing must be configured')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe('getPrice', () => {
|
|
131
|
+
it('returns price quote', async () => {
|
|
132
|
+
const provider = createProvider()
|
|
133
|
+
const price = await provider.getPrice({
|
|
134
|
+
assetIn: USDC,
|
|
135
|
+
assetOut: OP,
|
|
136
|
+
amountIn: 100,
|
|
137
|
+
chainId: CHAIN_ID,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
expect(price.price).toBeDefined()
|
|
141
|
+
expect(price.amountIn).toBeDefined()
|
|
142
|
+
expect(price.amountOut).toBeDefined()
|
|
143
|
+
expect(price.route.path).toEqual([USDC, OP])
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('defaults to 1 unit when no amount specified', async () => {
|
|
147
|
+
const provider = createProvider()
|
|
148
|
+
const price = await provider.getPrice({
|
|
149
|
+
assetIn: USDC,
|
|
150
|
+
assetOut: OP,
|
|
151
|
+
chainId: CHAIN_ID,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// 1 USDC = 1000000 (6 decimals)
|
|
155
|
+
expect(price.amountInWei).toBe(1000000n)
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe('getMarkets', () => {
|
|
160
|
+
it('returns markets from allowlist config', async () => {
|
|
161
|
+
const provider = createProvider()
|
|
162
|
+
const markets = await provider.getMarkets({})
|
|
163
|
+
expect(markets).toHaveLength(1)
|
|
164
|
+
expect(markets[0].assets).toEqual([USDC, OP])
|
|
165
|
+
expect(markets[0].fee).toBe(100)
|
|
166
|
+
expect(markets[0].provider).toBe('uniswap')
|
|
167
|
+
expect(markets[0].marketId.poolId).toMatch(/^0x/)
|
|
168
|
+
expect(markets[0].marketId.chainId).toBe(CHAIN_ID)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('returns empty when no allowlist configured', async () => {
|
|
172
|
+
const provider = createProvider({ marketAllowlist: [] })
|
|
173
|
+
const markets = await provider.getMarkets({})
|
|
174
|
+
expect(markets).toEqual([])
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('expands multi-asset filter into all pairs', async () => {
|
|
178
|
+
const provider = createProvider({
|
|
179
|
+
marketAllowlist: [
|
|
180
|
+
{
|
|
181
|
+
assets: [USDC, OP, MockWETHAsset],
|
|
182
|
+
fee: 100,
|
|
183
|
+
tickSpacing: 2,
|
|
184
|
+
chainId: CHAIN_ID,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
})
|
|
188
|
+
const markets = await provider.getMarkets({})
|
|
189
|
+
// 3 assets → 3 pairs: USDC/OP, USDC/WETH, OP/WETH
|
|
190
|
+
expect(markets).toHaveLength(3)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('filters by asset', async () => {
|
|
194
|
+
const provider = createProvider({
|
|
195
|
+
marketAllowlist: [
|
|
196
|
+
{
|
|
197
|
+
assets: [USDC, OP, MockWETHAsset],
|
|
198
|
+
fee: 100,
|
|
199
|
+
tickSpacing: 2,
|
|
200
|
+
chainId: CHAIN_ID,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
})
|
|
204
|
+
const markets = await provider.getMarkets({ asset: USDC })
|
|
205
|
+
// Only pairs containing USDC: USDC/OP, USDC/WETH
|
|
206
|
+
expect(markets).toHaveLength(2)
|
|
207
|
+
for (const market of markets) {
|
|
208
|
+
expect(market.assets).toContain(USDC)
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('skips filters without fee/tickSpacing', async () => {
|
|
213
|
+
const provider = createProvider({
|
|
214
|
+
marketAllowlist: [
|
|
215
|
+
{ assets: [USDC, OP], chainId: CHAIN_ID },
|
|
216
|
+
{
|
|
217
|
+
assets: [USDC, MockWETHAsset],
|
|
218
|
+
fee: 500,
|
|
219
|
+
tickSpacing: 10,
|
|
220
|
+
chainId: CHAIN_ID,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
})
|
|
224
|
+
const markets = await provider.getMarkets({})
|
|
225
|
+
// Only the second filter has fee+tickSpacing
|
|
226
|
+
expect(markets).toHaveLength(1)
|
|
227
|
+
expect(markets[0].assets).toEqual([USDC, MockWETHAsset])
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('skips assets without address on target chain', async () => {
|
|
231
|
+
const noChainAsset: Asset = {
|
|
232
|
+
type: 'erc20',
|
|
233
|
+
address: { 1: '0x5555555555555555555555555555555555555555' as Address },
|
|
234
|
+
metadata: { name: 'No Chain', symbol: 'NC', decimals: 18 },
|
|
235
|
+
}
|
|
236
|
+
const provider = createProvider({
|
|
237
|
+
marketAllowlist: [
|
|
238
|
+
{
|
|
239
|
+
assets: [USDC, noChainAsset],
|
|
240
|
+
fee: 100,
|
|
241
|
+
tickSpacing: 2,
|
|
242
|
+
chainId: CHAIN_ID,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
})
|
|
246
|
+
const markets = await provider.getMarkets({})
|
|
247
|
+
expect(markets).toEqual([])
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('produces deterministic poolIds', async () => {
|
|
251
|
+
const provider = createProvider()
|
|
252
|
+
const first = await provider.getMarkets({})
|
|
253
|
+
const second = await provider.getMarkets({})
|
|
254
|
+
expect(first[0].marketId.poolId).toBe(second[0].marketId.poolId)
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
describe('getMarket', () => {
|
|
259
|
+
it('finds market by poolId', async () => {
|
|
260
|
+
const provider = createProvider()
|
|
261
|
+
const markets = await provider.getMarkets({})
|
|
262
|
+
const market = await provider.getMarket({
|
|
263
|
+
poolId: markets[0].marketId.poolId,
|
|
264
|
+
chainId: CHAIN_ID,
|
|
265
|
+
})
|
|
266
|
+
expect(market.fee).toBe(100)
|
|
267
|
+
expect(market.assets).toEqual([USDC, OP])
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('throws for unknown poolId', async () => {
|
|
271
|
+
const provider = createProvider()
|
|
272
|
+
await expect(
|
|
273
|
+
provider.getMarket({ poolId: '0xunknown', chainId: CHAIN_ID }),
|
|
274
|
+
).rejects.toThrow('not found')
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('finds correct market in multi-asset filter', async () => {
|
|
278
|
+
const provider = createProvider({
|
|
279
|
+
marketAllowlist: [
|
|
280
|
+
{
|
|
281
|
+
assets: [USDC, OP, MockWETHAsset],
|
|
282
|
+
fee: 100,
|
|
283
|
+
tickSpacing: 2,
|
|
284
|
+
chainId: CHAIN_ID,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
})
|
|
288
|
+
const markets = await provider.getMarkets({})
|
|
289
|
+
// Look up each market by its poolId
|
|
290
|
+
for (const expected of markets) {
|
|
291
|
+
const found = await provider.getMarket({
|
|
292
|
+
poolId: expected.marketId.poolId,
|
|
293
|
+
chainId: CHAIN_ID,
|
|
294
|
+
})
|
|
295
|
+
expect(found.marketId.poolId).toBe(expected.marketId.poolId)
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
})
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { type Address, type PublicClient, zeroAddress } from 'viem'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import type { SupportedChainId } from '@/constants/supportedChains.js'
|
|
5
|
+
import type { Asset } from '@/types/asset.js'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
calculatePriceImpact,
|
|
9
|
+
encodeUniversalRouterSwap,
|
|
10
|
+
getQuote,
|
|
11
|
+
} from '../encoding.js'
|
|
12
|
+
|
|
13
|
+
const USDC: Asset = {
|
|
14
|
+
type: 'erc20',
|
|
15
|
+
address: { 84532: '0x1111111111111111111111111111111111111111' as Address },
|
|
16
|
+
metadata: { name: 'USD Coin', symbol: 'USDC', decimals: 6 },
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const WETH: Asset = {
|
|
20
|
+
type: 'erc20',
|
|
21
|
+
address: { 84532: '0x2222222222222222222222222222222222222222' as Address },
|
|
22
|
+
metadata: { name: 'Wrapped Ether', symbol: 'WETH', decimals: 18 },
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ETH: Asset = {
|
|
26
|
+
type: 'native',
|
|
27
|
+
address: { 84532: 'native' },
|
|
28
|
+
metadata: { name: 'Ethereum', symbol: 'ETH', decimals: 18 },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const QUOTER = '0x4a6513c898fe1b2d0e78d3b0e0a4a151589b1cba' as Address
|
|
32
|
+
const POOL_MANAGER = '0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408' as Address
|
|
33
|
+
const CHAIN_ID = 84532 as SupportedChainId
|
|
34
|
+
const FEE = 100
|
|
35
|
+
const TICK_SPACING = 2
|
|
36
|
+
|
|
37
|
+
// Mock sqrtPriceX96 for a ~2000 USDC/WETH pool
|
|
38
|
+
// sqrtPriceX96 = sqrt(price) * 2^96, where price = WETH/USDC adjusted for decimals
|
|
39
|
+
// For 1 WETH = 2000 USDC: price(token0→token1) depends on sort order
|
|
40
|
+
const MOCK_SQRT_PRICE =
|
|
41
|
+
'0x0000000000000000000000000000000000000000000000010000000000000000' as `0x${string}`
|
|
42
|
+
|
|
43
|
+
function createMockPublicClient(
|
|
44
|
+
amountResult: bigint,
|
|
45
|
+
gasEstimate = 150000n,
|
|
46
|
+
): PublicClient {
|
|
47
|
+
return {
|
|
48
|
+
simulateContract: vi.fn().mockResolvedValue({
|
|
49
|
+
result: [amountResult, gasEstimate],
|
|
50
|
+
}),
|
|
51
|
+
readContract: vi.fn().mockResolvedValue(MOCK_SQRT_PRICE),
|
|
52
|
+
} as unknown as PublicClient
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('getQuote', () => {
|
|
56
|
+
it('returns quote for exact-in swap', async () => {
|
|
57
|
+
const publicClient = createMockPublicClient(500000000000000000n) // 0.5 WETH
|
|
58
|
+
const quote = await getQuote({
|
|
59
|
+
assetIn: USDC,
|
|
60
|
+
assetOut: WETH,
|
|
61
|
+
amountInWei: 100000000n, // 100 USDC
|
|
62
|
+
chainId: CHAIN_ID,
|
|
63
|
+
publicClient,
|
|
64
|
+
quoterAddress: QUOTER,
|
|
65
|
+
poolManagerAddress: POOL_MANAGER,
|
|
66
|
+
fee: FEE,
|
|
67
|
+
tickSpacing: TICK_SPACING,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
expect(quote.amountIn).toBe(100)
|
|
71
|
+
expect(quote.amountOut).toBe(0.5)
|
|
72
|
+
expect(quote.amountInWei).toBe(100000000n)
|
|
73
|
+
expect(quote.amountOutWei).toBe(500000000000000000n)
|
|
74
|
+
expect(quote.price).toBeDefined()
|
|
75
|
+
expect(quote.priceInverse).toBeDefined()
|
|
76
|
+
expect(typeof quote.priceImpact).toBe('number')
|
|
77
|
+
expect(quote.priceImpact).toBeGreaterThanOrEqual(0)
|
|
78
|
+
expect(quote.route.path).toEqual([USDC, WETH])
|
|
79
|
+
expect(quote.route.pools).toHaveLength(1)
|
|
80
|
+
expect(quote.gasEstimate).toBe(150000n)
|
|
81
|
+
|
|
82
|
+
expect(publicClient.simulateContract).toHaveBeenCalledWith(
|
|
83
|
+
expect.objectContaining({
|
|
84
|
+
functionName: 'quoteExactInputSingle',
|
|
85
|
+
}),
|
|
86
|
+
)
|
|
87
|
+
// Should also read sqrtPriceX96 via extsload
|
|
88
|
+
expect(publicClient.readContract).toHaveBeenCalledWith(
|
|
89
|
+
expect.objectContaining({
|
|
90
|
+
address: POOL_MANAGER,
|
|
91
|
+
functionName: 'extsload',
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('returns quote for exact-out swap', async () => {
|
|
97
|
+
const publicClient = createMockPublicClient(100000000n) // 100 USDC needed
|
|
98
|
+
const quote = await getQuote({
|
|
99
|
+
assetIn: USDC,
|
|
100
|
+
assetOut: WETH,
|
|
101
|
+
amountOutWei: 500000000000000000n, // 0.5 WETH
|
|
102
|
+
chainId: CHAIN_ID,
|
|
103
|
+
publicClient,
|
|
104
|
+
quoterAddress: QUOTER,
|
|
105
|
+
poolManagerAddress: POOL_MANAGER,
|
|
106
|
+
fee: FEE,
|
|
107
|
+
tickSpacing: TICK_SPACING,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(quote.amountInWei).toBe(100000000n)
|
|
111
|
+
expect(quote.amountOutWei).toBe(500000000000000000n)
|
|
112
|
+
|
|
113
|
+
expect(publicClient.simulateContract).toHaveBeenCalledWith(
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
functionName: 'quoteExactOutputSingle',
|
|
116
|
+
}),
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('sorts currency0/currency1 correctly', async () => {
|
|
121
|
+
const publicClient = createMockPublicClient(100000000n)
|
|
122
|
+
await getQuote({
|
|
123
|
+
assetIn: WETH, // higher address
|
|
124
|
+
assetOut: USDC, // lower address
|
|
125
|
+
amountInWei: 1000000000000000000n,
|
|
126
|
+
chainId: CHAIN_ID,
|
|
127
|
+
publicClient,
|
|
128
|
+
quoterAddress: QUOTER,
|
|
129
|
+
poolManagerAddress: POOL_MANAGER,
|
|
130
|
+
fee: FEE,
|
|
131
|
+
tickSpacing: TICK_SPACING,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const call = vi.mocked(publicClient.simulateContract).mock.calls[0][0]
|
|
135
|
+
const args = (call as any).args[0]
|
|
136
|
+
// currency0 should be the lower address
|
|
137
|
+
expect(
|
|
138
|
+
args.poolKey.currency0.toLowerCase() <
|
|
139
|
+
args.poolKey.currency1.toLowerCase(),
|
|
140
|
+
).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('uses address(0) for native ETH in pool key', async () => {
|
|
144
|
+
const publicClient = createMockPublicClient(100000000n)
|
|
145
|
+
await getQuote({
|
|
146
|
+
assetIn: ETH,
|
|
147
|
+
assetOut: USDC,
|
|
148
|
+
amountInWei: 1000000000000000000n,
|
|
149
|
+
chainId: CHAIN_ID,
|
|
150
|
+
publicClient,
|
|
151
|
+
quoterAddress: QUOTER,
|
|
152
|
+
poolManagerAddress: POOL_MANAGER,
|
|
153
|
+
fee: FEE,
|
|
154
|
+
tickSpacing: TICK_SPACING,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const call = vi.mocked(publicClient.simulateContract).mock.calls[0][0]
|
|
158
|
+
const args = (call as any).args[0]
|
|
159
|
+
// Native ETH should be address(0), sorted as currency0 (lowest possible address)
|
|
160
|
+
expect(args.poolKey.currency0).toBe(zeroAddress)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('uses address(0) for native ETH as output', async () => {
|
|
164
|
+
const publicClient = createMockPublicClient(1000000000000000000n)
|
|
165
|
+
await getQuote({
|
|
166
|
+
assetIn: USDC,
|
|
167
|
+
assetOut: ETH,
|
|
168
|
+
amountInWei: 100000000n,
|
|
169
|
+
chainId: CHAIN_ID,
|
|
170
|
+
publicClient,
|
|
171
|
+
quoterAddress: QUOTER,
|
|
172
|
+
poolManagerAddress: POOL_MANAGER,
|
|
173
|
+
fee: FEE,
|
|
174
|
+
tickSpacing: TICK_SPACING,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const call = vi.mocked(publicClient.simulateContract).mock.calls[0][0]
|
|
178
|
+
const args = (call as any).args[0]
|
|
179
|
+
// Native ETH should be address(0) regardless of swap direction
|
|
180
|
+
expect(args.poolKey.currency0).toBe(zeroAddress)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
describe('calculatePriceImpact', () => {
|
|
185
|
+
// sqrtPriceX96 for a pool where 1 USDC-wei buys 5e9 WETH-wei
|
|
186
|
+
// (100 USDC → 0.5 WETH at mid-price)
|
|
187
|
+
// Computed as: sqrt(5e9) * 2^96 ≈ 70711 * 2^96
|
|
188
|
+
const MID_SQRT_PRICE = 5602302599546145575577086272208896n
|
|
189
|
+
|
|
190
|
+
it('returns 0 when sqrtPriceX96 is 0', () => {
|
|
191
|
+
const impact = calculatePriceImpact({
|
|
192
|
+
sqrtPriceX96: 0n,
|
|
193
|
+
amountIn: 100000000n,
|
|
194
|
+
amountOut: 500000000000000000n,
|
|
195
|
+
zeroForOne: true,
|
|
196
|
+
})
|
|
197
|
+
expect(impact).toBe(0)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('returns ~0 for a trade at mid-price', () => {
|
|
201
|
+
// 100 USDC → ~0.5 WETH, which matches the mid-price
|
|
202
|
+
const impact = calculatePriceImpact({
|
|
203
|
+
sqrtPriceX96: MID_SQRT_PRICE,
|
|
204
|
+
amountIn: 100000000n,
|
|
205
|
+
amountOut: 500000000000000000n,
|
|
206
|
+
zeroForOne: true,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// Very small due to integer sqrt rounding, but near 0
|
|
210
|
+
expect(impact).toBeLessThan(0.001)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('returns positive impact when execution is worse than mid-price', () => {
|
|
214
|
+
// Mid-price says we should get ~0.5 WETH, but we only get 0.4 WETH
|
|
215
|
+
const impact = calculatePriceImpact({
|
|
216
|
+
sqrtPriceX96: MID_SQRT_PRICE,
|
|
217
|
+
amountIn: 100000000n,
|
|
218
|
+
amountOut: 400000000000000000n, // 0.4 WETH instead of ~0.5
|
|
219
|
+
zeroForOne: true,
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// ~20% price impact
|
|
223
|
+
expect(impact).toBeGreaterThan(0.15)
|
|
224
|
+
expect(impact).toBeLessThan(0.25)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('clamps negative impact to 0', () => {
|
|
228
|
+
// Execution was better than mid-price (got more than expected)
|
|
229
|
+
const impact = calculatePriceImpact({
|
|
230
|
+
sqrtPriceX96: MID_SQRT_PRICE,
|
|
231
|
+
amountIn: 100000000n,
|
|
232
|
+
amountOut: 600000000000000000n, // 0.6 WETH — better than ~0.5 mid
|
|
233
|
+
zeroForOne: true,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
expect(impact).toBe(0)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('works for oneForZero direction', () => {
|
|
240
|
+
// Selling WETH for USDC (oneForZero)
|
|
241
|
+
// At mid-price: 0.5 WETH should get ~100 USDC-wei worth
|
|
242
|
+
// But we only get 90 USDC → some impact
|
|
243
|
+
const impact = calculatePriceImpact({
|
|
244
|
+
sqrtPriceX96: MID_SQRT_PRICE,
|
|
245
|
+
amountIn: 500000000000000000n, // 0.5 WETH
|
|
246
|
+
amountOut: 90000000n, // 90 USDC (less than ~100)
|
|
247
|
+
zeroForOne: false,
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
expect(impact).toBeGreaterThan(0.05)
|
|
251
|
+
expect(impact).toBeLessThan(0.15)
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe('encodeUniversalRouterSwap', () => {
|
|
256
|
+
const baseQuote = {
|
|
257
|
+
price: '0.005',
|
|
258
|
+
priceInverse: '200',
|
|
259
|
+
amountIn: 100,
|
|
260
|
+
amountOut: 0.5,
|
|
261
|
+
amountInWei: 100000000n,
|
|
262
|
+
amountOutWei: 500000000000000000n,
|
|
263
|
+
priceImpact: 0.001,
|
|
264
|
+
route: { path: [USDC, WETH], pools: [] },
|
|
265
|
+
gasEstimate: 150000n,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
it('encodes exact-in swap calldata', () => {
|
|
269
|
+
const calldata = encodeUniversalRouterSwap({
|
|
270
|
+
amountInWei: 100000000n,
|
|
271
|
+
assetIn: USDC,
|
|
272
|
+
assetOut: WETH,
|
|
273
|
+
slippage: 0.005,
|
|
274
|
+
deadline: 1700000000,
|
|
275
|
+
recipient: '0xrecipient' as Address,
|
|
276
|
+
chainId: CHAIN_ID,
|
|
277
|
+
quote: baseQuote,
|
|
278
|
+
universalRouterAddress: '0xrouter' as Address,
|
|
279
|
+
fee: FEE,
|
|
280
|
+
tickSpacing: TICK_SPACING,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
expect(calldata).toMatch(/^0x/)
|
|
284
|
+
expect(calldata.length).toBeGreaterThan(10)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('encodes exact-out swap calldata', () => {
|
|
288
|
+
const calldata = encodeUniversalRouterSwap({
|
|
289
|
+
amountOutWei: 500000000000000000n,
|
|
290
|
+
assetIn: USDC,
|
|
291
|
+
assetOut: WETH,
|
|
292
|
+
slippage: 0.005,
|
|
293
|
+
deadline: 1700000000,
|
|
294
|
+
recipient: '0xrecipient' as Address,
|
|
295
|
+
chainId: CHAIN_ID,
|
|
296
|
+
quote: baseQuote,
|
|
297
|
+
universalRouterAddress: '0xrouter' as Address,
|
|
298
|
+
fee: FEE,
|
|
299
|
+
tickSpacing: TICK_SPACING,
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
expect(calldata).toMatch(/^0x/)
|
|
303
|
+
expect(calldata.length).toBeGreaterThan(10)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('produces different calldata for exact-in vs exact-out', () => {
|
|
307
|
+
const exactIn = encodeUniversalRouterSwap({
|
|
308
|
+
amountInWei: 100000000n,
|
|
309
|
+
assetIn: USDC,
|
|
310
|
+
assetOut: WETH,
|
|
311
|
+
slippage: 0.005,
|
|
312
|
+
deadline: 1700000000,
|
|
313
|
+
recipient: '0xrecipient' as Address,
|
|
314
|
+
chainId: CHAIN_ID,
|
|
315
|
+
quote: baseQuote,
|
|
316
|
+
universalRouterAddress: '0xrouter' as Address,
|
|
317
|
+
fee: FEE,
|
|
318
|
+
tickSpacing: TICK_SPACING,
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
const exactOut = encodeUniversalRouterSwap({
|
|
322
|
+
amountOutWei: 500000000000000000n,
|
|
323
|
+
assetIn: USDC,
|
|
324
|
+
assetOut: WETH,
|
|
325
|
+
slippage: 0.005,
|
|
326
|
+
deadline: 1700000000,
|
|
327
|
+
recipient: '0xrecipient' as Address,
|
|
328
|
+
chainId: CHAIN_ID,
|
|
329
|
+
quote: baseQuote,
|
|
330
|
+
universalRouterAddress: '0xrouter' as Address,
|
|
331
|
+
fee: FEE,
|
|
332
|
+
tickSpacing: TICK_SPACING,
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
expect(exactIn).not.toBe(exactOut)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('applies slippage to minimum output for exact-in', () => {
|
|
339
|
+
const noSlippage = encodeUniversalRouterSwap({
|
|
340
|
+
amountInWei: 100000000n,
|
|
341
|
+
assetIn: USDC,
|
|
342
|
+
assetOut: WETH,
|
|
343
|
+
slippage: 0,
|
|
344
|
+
deadline: 1700000000,
|
|
345
|
+
recipient: '0xrecipient' as Address,
|
|
346
|
+
chainId: CHAIN_ID,
|
|
347
|
+
quote: baseQuote,
|
|
348
|
+
universalRouterAddress: '0xrouter' as Address,
|
|
349
|
+
fee: FEE,
|
|
350
|
+
tickSpacing: TICK_SPACING,
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
const withSlippage = encodeUniversalRouterSwap({
|
|
354
|
+
amountInWei: 100000000n,
|
|
355
|
+
assetIn: USDC,
|
|
356
|
+
assetOut: WETH,
|
|
357
|
+
slippage: 0.05, // 5%
|
|
358
|
+
deadline: 1700000000,
|
|
359
|
+
recipient: '0xrecipient' as Address,
|
|
360
|
+
chainId: CHAIN_ID,
|
|
361
|
+
quote: baseQuote,
|
|
362
|
+
universalRouterAddress: '0xrouter' as Address,
|
|
363
|
+
fee: FEE,
|
|
364
|
+
tickSpacing: TICK_SPACING,
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
// Different slippage should produce different calldata
|
|
368
|
+
expect(noSlippage).not.toBe(withSlippage)
|
|
369
|
+
})
|
|
370
|
+
})
|