initial commit
This commit is contained in:
commit
061cc544fe
|
@ -0,0 +1,95 @@
|
|||
## NativeSwap
|
||||
|
||||
This is a non-production example program.
|
||||
|
||||
Multi-chain native-to-native token swap using existing DEXes.
|
||||
|
||||
### Details
|
||||
|
||||
Using liquidity of native vs UST (i.e. the UST highway), one can swap from native A on chain A to native B on chain B. For this specific example, we demonstrate a swap between Polygon (Mumbai testnet) and Ethereum (Goerli testnet) between MATIC and ETH. We wrote example smart contracts to interact with Uniswap V3 and Uniswap V2 forks (QuickSwap in this specific example for Polygon). Any DEX can be used to replace our example as long as the swap for a particular DEX has all of its parameters to perform the swap(s).
|
||||
|
||||
A protocol that hosts NativeSwap is expected to run its own relayer to enhance its user experience by only requiring a one-click transaction to perform the complete swap. Otherwise the user will have to perform an extra transaction to manually allow the final swap.
|
||||
|
||||
Here is what happens under the hood of this example:
|
||||
|
||||
- User generates quote from front-end for native-to-native swap.
|
||||
- User calls the smart contract with its quote on chain A.
|
||||
- Smart contract on chain A executes swap from native A to UST. If the swap succeeds, the smart contract will execute a Token Bridge transfer of UST with encoded swap parameters for chain B.
|
||||
- Guardians sign the Token Bridge transfer.
|
||||
- The relayer reads the signed VAA and calls the smart contract with the VAA as its only argument.
|
||||
- Smart contract on chain B completes the UST transfer and decodes the swap parameters from the Wormhole message payload.
|
||||
- Smart contract on chain B executes swap from UST to native B. If the swap succeeds, the smart contract will send native B to user. Otherwise, it will send UST to user.
|
||||
|
||||
The Wormhole message payload for swap parameters are all encoded and decoded on-chain.
|
||||
|
||||
We also wrote a front-end UI using a custom class (UniswapToUniswapExecutor) to perform the quotes for "Exact In" (swapping from an exact amount of native A to an estimated amount of native B) and "Exact Out" (swapping from an estimated amount of native A to an exact amount of native B) swaps and execute these swaps based on this quote. This library uses the ABIs of our example smart contracts to execute the swap transactions.
|
||||
|
||||
### What's next?
|
||||
|
||||
That is up to you! You are not limited to native-to-native multi-chain swaps. Build in your own smart routing with whichever DEX to perform any swap from chain A to chain B. Wormhole messaging and token transfers with payload are generic enough to adapt this example for any of the chains Wormhole currently supports.
|
||||
|
||||
### Deployment
|
||||
|
||||
Before deploying, you need to install contract and Truffle dependencies:
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
There are two deployment scripts found in the _scripts_ directory:
|
||||
|
||||
- _deploy_to_goerli.sh_
|
||||
- _deploy_to_mumbai.sh_
|
||||
|
||||
These will deploy _CrossChainSwapV3_ and _CrossChainSwapV2_ respectively (found in the _contracts_ directory) and automatically write those contract addresses to a Typescript file, which will be compiled with everything else for your front-end library. You can run them like so:
|
||||
|
||||
```bash
|
||||
bash scripts/deploy_to_goerli.sh
|
||||
bash scripts/deploy_to_mumbai.sh
|
||||
```
|
||||
|
||||
After you deploy your contracts, run the following to build the front-end library:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
First compile the example contracts:
|
||||
|
||||
```
|
||||
cd contracts
|
||||
npm ci
|
||||
./compile_contracts.sh
|
||||
```
|
||||
|
||||
Then copy sample.env to .env, edit .env and replace YOUR-PROJECT-ID with your Infura Goerli and Mumbai Project IDs and also add your Ethereum wallet's private key.
|
||||
These are needed to deploy the example contracts.
|
||||
|
||||
```
|
||||
cp .env.sample .env
|
||||
# make sure to edit .env file
|
||||
```
|
||||
|
||||
Then deploy the example contracts:
|
||||
|
||||
```
|
||||
./deploy_to_goerli.sh
|
||||
./deploy_to_mumbai.sh
|
||||
```
|
||||
|
||||
Then change into the react directory, copy sample.env to .env and replace YOUR-PROJECT-ID with your Infura Goerli and Mumbai Project IDs
|
||||
|
||||
```
|
||||
cd react
|
||||
cp .env.sample .env
|
||||
# make sure to edit .env file
|
||||
```
|
||||
|
||||
And finally, start the react app:
|
||||
|
||||
```
|
||||
npm ci
|
||||
npm run start
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
GOERLI_PROVIDER=https://goerli.infura.io/v3/YOUR-PROJECT-ID
|
||||
MUMBAI_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID
|
||||
ETH_PRIVATE_KEY=
|
|
@ -0,0 +1 @@
|
|||
.env
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
npx truffle compile --config truffle-config.ethereum.js
|
||||
npx truffle compile --config truffle-config.polygon.js
|
||||
|
||||
mkdir -p ../ui/src/abi/contracts
|
||||
|
||||
cp -r build/contracts/* ../ui/src/abi/contracts
|
|
@ -0,0 +1,272 @@
|
|||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
|
||||
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
|
||||
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import 'solidity-bytes-utils/contracts/BytesLib.sol';
|
||||
import './IWormhole.sol';
|
||||
import './SwapHelper.sol';
|
||||
|
||||
|
||||
interface TokenBridge {
|
||||
function transferTokensWithPayload(
|
||||
address token,
|
||||
uint256 amount,
|
||||
uint16 recipientChain,
|
||||
bytes32 recipient,
|
||||
uint256 arbiterFee,
|
||||
uint32 nonce,
|
||||
bytes memory payload
|
||||
) external payable returns (uint64);
|
||||
function completeTransferWithPayload(bytes memory encodedVm) external returns (IWormhole.VM memory);
|
||||
}
|
||||
|
||||
|
||||
contract CrossChainSwapV2 {
|
||||
using SafeERC20 for IERC20;
|
||||
using BytesLib for bytes;
|
||||
uint8 public immutable typeExactIn = 1;
|
||||
uint8 public immutable typeExactOut = 2;
|
||||
uint16 public immutable expectedVaaLength = 261;
|
||||
IUniswapV2Router02 public immutable swapRouter;
|
||||
address public immutable feeTokenAddress;
|
||||
address public immutable tokenBridgeAddress;
|
||||
|
||||
constructor(address _swapRouterAddress, address _feeTokenAddress, address _tokenBridgeAddress) {
|
||||
swapRouter = IUniswapV2Router02(_swapRouterAddress);
|
||||
feeTokenAddress = _feeTokenAddress;
|
||||
tokenBridgeAddress = _tokenBridgeAddress;
|
||||
}
|
||||
|
||||
function swapExactInFromV3(
|
||||
bytes calldata encodedVaa
|
||||
) external returns (uint256[] memory amounts) {
|
||||
// complete the transfer on the token bridge
|
||||
IWormhole.VM memory vm = TokenBridge(tokenBridgeAddress).completeTransferWithPayload(encodedVaa);
|
||||
require(vm.payload.length==expectedVaaLength, "VAA has the wrong number of bytes");
|
||||
|
||||
// parse the payload
|
||||
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.decodeVaaPayload(vm);
|
||||
require(payload.swapType==typeExactIn, "swap must be type ExactIn");
|
||||
|
||||
// create dynamic address array - uniswap won't take fixed size array
|
||||
address[] memory uniPath = new address[](2);
|
||||
uniPath[0] = payload.path[0];
|
||||
uniPath[1] = payload.path[1];
|
||||
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST");
|
||||
|
||||
// pay relayer before attempting to do the swap
|
||||
// reflect payment in second swap amount
|
||||
IERC20 feeToken = IERC20(feeTokenAddress);
|
||||
feeToken.safeTransfer(msg.sender, payload.relayerFee);
|
||||
uint256 swapAmountLessFees = payload.swapAmount - payload.relayerFee;
|
||||
|
||||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(uniPath[0], address(swapRouter), swapAmountLessFees);
|
||||
|
||||
// try to perform the swap
|
||||
try swapRouter.swapExactTokensForTokens(
|
||||
swapAmountLessFees,
|
||||
payload.estimatedAmount,
|
||||
uniPath,
|
||||
payload.recipientAddress,
|
||||
payload.deadline
|
||||
) returns (uint256[] memory amounts) {
|
||||
return amounts;
|
||||
} catch {
|
||||
// swap failed - return UST to recipient
|
||||
feeToken.safeTransfer(payload.recipientAddress, swapAmountLessFees);
|
||||
}
|
||||
}
|
||||
|
||||
function _swapExactInBeforeTransfer(
|
||||
uint256 amountIn,
|
||||
uint256 amountOutMinimum,
|
||||
address contractCaller,
|
||||
address[] calldata path,
|
||||
uint256 deadline
|
||||
) internal returns (uint256 amountOut) {
|
||||
// path[0] is the tokenIn in
|
||||
IERC20 token = IERC20(path[0]);
|
||||
token.safeTransferFrom(contractCaller, address(this), amountIn);
|
||||
|
||||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(path[0], address(swapRouter), amountIn);
|
||||
|
||||
// perform the swap
|
||||
uint256[] memory amounts = swapRouter.swapExactTokensForTokens(
|
||||
amountIn,
|
||||
amountOutMinimum,
|
||||
path,
|
||||
address(this),
|
||||
deadline
|
||||
);
|
||||
amountOut = amounts[1];
|
||||
}
|
||||
|
||||
function swapExactInToV3(
|
||||
SwapHelper.ExactInParameters calldata swapParams,
|
||||
address[] calldata path,
|
||||
uint256 relayerFee,
|
||||
uint16 targetChainId,
|
||||
bytes32 targetContractAddress,
|
||||
uint32 nonce
|
||||
) external {
|
||||
require(swapParams.amountOutMinimum > relayerFee, "insufficient amountOutMinimum to pay relayer");
|
||||
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
|
||||
|
||||
// peform the first swap
|
||||
uint256 amountOut = _swapExactInBeforeTransfer(
|
||||
swapParams.amountIn,
|
||||
swapParams.amountOutMinimum,
|
||||
msg.sender,
|
||||
path[0:2],
|
||||
swapParams.deadline
|
||||
);
|
||||
|
||||
// encode payload for second swap
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOutMinimum,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactIn
|
||||
);
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, amountOut);
|
||||
|
||||
// send transfer with payload to the TokenBridge
|
||||
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
|
||||
feeTokenAddress, amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
|
||||
);
|
||||
}
|
||||
|
||||
function swapExactOutFromV3(
|
||||
bytes calldata encodedVaa
|
||||
) external returns (uint256 amountInUsed) {
|
||||
// complete the transfer on the token bridge
|
||||
IWormhole.VM memory vm = TokenBridge(tokenBridgeAddress).completeTransferWithPayload(encodedVaa);
|
||||
require(vm.payload.length==expectedVaaLength, "VAA has the wrong number of bytes");
|
||||
|
||||
// parse the payload
|
||||
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.decodeVaaPayload(vm);
|
||||
require(payload.swapType==typeExactOut, "swap must be type ExactOut");
|
||||
|
||||
// amountOut is the estimated swap amount for exact out methods
|
||||
uint256 amountOut = payload.estimatedAmount;
|
||||
|
||||
// create dynamic address array - uniswap won't take fixed size array
|
||||
address[] memory uniPath = new address[](2);
|
||||
uniPath[0] = payload.path[0];
|
||||
uniPath[1] = payload.path[1];
|
||||
require(uniPath[0]==feeTokenAddress, "tokenIn must be UST");
|
||||
|
||||
// pay relayer before attempting to do the swap
|
||||
// reflect payment in second swap amount
|
||||
IERC20 feeToken = IERC20(feeTokenAddress);
|
||||
feeToken.safeTransfer(msg.sender, payload.relayerFee);
|
||||
uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee;
|
||||
|
||||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(uniPath[0], address(swapRouter), maxAmountInLessFees);
|
||||
|
||||
// try to perform the swap
|
||||
try swapRouter.swapTokensForExactTokens(
|
||||
amountOut,
|
||||
maxAmountInLessFees,
|
||||
uniPath,
|
||||
payload.recipientAddress,
|
||||
payload.deadline
|
||||
) returns (uint256[] memory amounts) {
|
||||
// amountIn used is first element in array
|
||||
amountInUsed = amounts[0];
|
||||
|
||||
// refund recipient with any UST not used in the swap
|
||||
if (amountInUsed < maxAmountInLessFees) {
|
||||
TransferHelper.safeApprove(feeTokenAddress, address(swapRouter), 0);
|
||||
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees - amountInUsed);
|
||||
}
|
||||
return amountInUsed;
|
||||
} catch {
|
||||
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees);
|
||||
}
|
||||
}
|
||||
|
||||
function _swapExactOutBeforeTransfer(
|
||||
uint256 amountOut,
|
||||
uint256 amountInMaximum,
|
||||
address contractCaller,
|
||||
address[] calldata path,
|
||||
uint256 deadline
|
||||
) internal {
|
||||
// path[0] is the tokenIn
|
||||
IERC20 token = IERC20(path[0]);
|
||||
token.safeTransferFrom(contractCaller, address(this), amountInMaximum);
|
||||
|
||||
// approve the router to spend tokens
|
||||
TransferHelper.safeApprove(path[0], address(swapRouter), amountInMaximum);
|
||||
|
||||
// perform the swap
|
||||
uint256[] memory amounts = swapRouter.swapTokensForExactTokens(
|
||||
amountOut,
|
||||
amountInMaximum,
|
||||
path,
|
||||
address(this),
|
||||
deadline
|
||||
);
|
||||
|
||||
// amountIn used is first element in array
|
||||
uint256 amountInUsed = amounts[0];
|
||||
|
||||
// refund contractCaller with any amountIn that wasn't spent
|
||||
if (amountInUsed < amountInMaximum) {
|
||||
TransferHelper.safeApprove(path[0], address(swapRouter), 0);
|
||||
token.safeTransfer(contractCaller, amountInMaximum - amountInUsed);
|
||||
}
|
||||
}
|
||||
|
||||
function swapExactOutToV3(
|
||||
SwapHelper.ExactOutParameters calldata swapParams,
|
||||
address[] calldata path,
|
||||
uint256 relayerFee,
|
||||
uint16 targetChainId,
|
||||
bytes32 targetContractAddress,
|
||||
uint32 nonce
|
||||
) external {
|
||||
require(swapParams.amountOut > relayerFee, "insufficient amountOut to pay relayer");
|
||||
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
|
||||
|
||||
// peform the first swap
|
||||
_swapExactOutBeforeTransfer(
|
||||
swapParams.amountOut,
|
||||
swapParams.amountInMaximum,
|
||||
msg.sender,
|
||||
path[0:2],
|
||||
swapParams.deadline
|
||||
);
|
||||
|
||||
// encode payload for second swap
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOut,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactOut
|
||||
);
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, swapParams.amountOut);
|
||||
|
||||
// send transfer with payload to the TokenBridge
|
||||
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
|
||||
feeTokenAddress, swapParams.amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
|
||||
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
|
||||
import '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
||||
import 'solidity-bytes-utils/contracts/BytesLib.sol';
|
||||
import './IWormhole.sol';
|
||||
import './SwapHelper.sol';
|
||||
|
||||
|
||||
interface TokenBridge {
|
||||
function transferTokensWithPayload(
|
||||
address token,
|
||||
uint256 amount,
|
||||
uint16 recipientChain,
|
||||
bytes32 recipient,
|
||||
uint256 arbiterFee,
|
||||
uint32 nonce,
|
||||
bytes memory payload
|
||||
) external payable returns (uint64);
|
||||
function completeTransferWithPayload(bytes memory encodedVm) external returns (IWormhole.VM memory);
|
||||
}
|
||||
|
||||
|
||||
contract CrossChainSwapV3 {
|
||||
using SafeERC20 for IERC20;
|
||||
using BytesLib for bytes;
|
||||
uint8 public immutable typeExactIn = 1;
|
||||
uint8 public immutable typeExactOut = 2;
|
||||
uint16 public immutable expectedVaaLength = 261;
|
||||
ISwapRouter public immutable swapRouter;
|
||||
address public immutable feeTokenAddress;
|
||||
address public immutable tokenBridgeAddress;
|
||||
|
||||
constructor(address _swapRouterAddress, address _feeTokenAddress, address _tokenBridgeAddress) {
|
||||
swapRouter = ISwapRouter(_swapRouterAddress);
|
||||
feeTokenAddress = _feeTokenAddress;
|
||||
tokenBridgeAddress = _tokenBridgeAddress;
|
||||
}
|
||||
|
||||
function swapExactInFromV2(
|
||||
bytes calldata encodedVaa
|
||||
) external returns (uint256 amountOut) {
|
||||
// complete the transfer on the token bridge
|
||||
IWormhole.VM memory vm = TokenBridge(tokenBridgeAddress).completeTransferWithPayload(encodedVaa);
|
||||
require(vm.payload.length==expectedVaaLength, "VAA has the wrong number of bytes");
|
||||
|
||||
// parse the payload
|
||||
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.decodeVaaPayload(vm);
|
||||
require(payload.swapType==typeExactIn, "swap must be type ExactIn");
|
||||
require(payload.path[0]==feeTokenAddress, "tokenIn must be UST");
|
||||
|
||||
// pay relayer before attempting to do the swap
|
||||
// reflect payment in second swap amount
|
||||
IERC20 feeToken = IERC20(feeTokenAddress);
|
||||
feeToken.safeTransfer(msg.sender, payload.relayerFee);
|
||||
uint256 swapAmountLessFees = payload.swapAmount - payload.relayerFee;
|
||||
|
||||
// approve the router to spend tokens based on amountIn
|
||||
TransferHelper.safeApprove(payload.path[0], address(swapRouter), swapAmountLessFees);
|
||||
|
||||
// set swap options with user params
|
||||
ISwapRouter.ExactInputSingleParams memory params =
|
||||
ISwapRouter.ExactInputSingleParams({
|
||||
tokenIn: payload.path[0],
|
||||
tokenOut: payload.path[1],
|
||||
fee: payload.poolFee,
|
||||
recipient: payload.recipientAddress,
|
||||
deadline: payload.deadline,
|
||||
amountIn: swapAmountLessFees,
|
||||
amountOutMinimum: payload.estimatedAmount,
|
||||
sqrtPriceLimitX96: 0
|
||||
});
|
||||
|
||||
// the call to `exactInputSingle` executes the swap
|
||||
try swapRouter.exactInputSingle(params) returns (uint256 amountOut) {
|
||||
return amountOut;
|
||||
} catch {
|
||||
// swap failed - return UST to recipient
|
||||
feeToken.safeTransfer(payload.recipientAddress, swapAmountLessFees);
|
||||
}
|
||||
}
|
||||
|
||||
function _swapExactInBeforeTransfer(
|
||||
uint256 amountIn,
|
||||
uint256 amountOutMinimum,
|
||||
address contractCaller,
|
||||
address[] calldata path,
|
||||
uint256 deadline,
|
||||
uint24 poolFee
|
||||
) internal returns (uint256 amountOut) {
|
||||
// transfer the allowed amount of tokens to this contract
|
||||
IERC20 token = IERC20(path[0]);
|
||||
token.safeTransferFrom(contractCaller, address(this), amountIn);
|
||||
|
||||
// approve the router to spend tokens based on amountIn
|
||||
TransferHelper.safeApprove(path[0], address(swapRouter), amountIn);
|
||||
|
||||
// set swap options with user params
|
||||
ISwapRouter.ExactInputSingleParams memory params =
|
||||
ISwapRouter.ExactInputSingleParams({
|
||||
tokenIn: path[0],
|
||||
tokenOut: path[1],
|
||||
fee: poolFee,
|
||||
recipient: address(this),
|
||||
deadline: deadline,
|
||||
amountIn: amountIn,
|
||||
amountOutMinimum: amountOutMinimum,
|
||||
sqrtPriceLimitX96: 0
|
||||
});
|
||||
|
||||
// the call to `exactInputSingle` executes the swap
|
||||
amountOut = swapRouter.exactInputSingle(params);
|
||||
}
|
||||
|
||||
function swapExactInToV2(
|
||||
SwapHelper.ExactInParameters calldata swapParams,
|
||||
address[] calldata path,
|
||||
uint256 relayerFee,
|
||||
uint16 targetChainId,
|
||||
bytes32 targetContractAddress,
|
||||
uint32 nonce
|
||||
) external {
|
||||
// makes sure the relayer is left whole after the second swap
|
||||
require(swapParams.amountOutMinimum > relayerFee, "insufficient amountOutMinimum to pay relayer");
|
||||
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
|
||||
|
||||
// peform the first swap
|
||||
uint256 amountOut = _swapExactInBeforeTransfer(
|
||||
swapParams.amountIn,
|
||||
swapParams.amountOutMinimum,
|
||||
msg.sender,
|
||||
path[0:2],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee
|
||||
);
|
||||
|
||||
// encode payload for second swap
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOutMinimum,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactIn
|
||||
);
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, amountOut);
|
||||
|
||||
// send transfer with payload to the TokenBridge
|
||||
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
|
||||
feeTokenAddress, amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
|
||||
);
|
||||
}
|
||||
|
||||
function swapExactOutFromV2(
|
||||
bytes calldata encodedVaa
|
||||
) external returns (uint256 amountInUsed) {
|
||||
// complete the transfer on the token bridge
|
||||
IWormhole.VM memory vm = TokenBridge(tokenBridgeAddress).completeTransferWithPayload(encodedVaa);
|
||||
require(vm.payload.length==expectedVaaLength, "VAA has the wrong number of bytes");
|
||||
|
||||
// parse the payload
|
||||
SwapHelper.DecodedVaaParameters memory payload = SwapHelper.decodeVaaPayload(vm);
|
||||
require(payload.swapType==typeExactOut, "swap must be type ExactOut");
|
||||
require(payload.path[0]==feeTokenAddress, "tokenIn must be UST");
|
||||
|
||||
// amountOut is the estimated swap amount for exact out methods
|
||||
uint256 amountOut = payload.estimatedAmount;
|
||||
|
||||
// pay relayer before attempting to do the swap
|
||||
// reflect payment in second swap amount
|
||||
IERC20 feeToken = IERC20(feeTokenAddress);
|
||||
feeToken.safeTransfer(msg.sender, payload.relayerFee);
|
||||
uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee;
|
||||
|
||||
// approve the router to spend swapAmount - which is maxAmountIn in payload
|
||||
TransferHelper.safeApprove(payload.path[0], address(swapRouter), maxAmountInLessFees);
|
||||
|
||||
// set swap options with user params
|
||||
ISwapRouter.ExactOutputSingleParams memory params =
|
||||
ISwapRouter.ExactOutputSingleParams({
|
||||
tokenIn: payload.path[0],
|
||||
tokenOut: payload.path[1],
|
||||
fee: payload.poolFee,
|
||||
recipient: payload.recipientAddress,
|
||||
deadline: payload.deadline,
|
||||
amountOut: amountOut,
|
||||
amountInMaximum: maxAmountInLessFees,
|
||||
sqrtPriceLimitX96: 0
|
||||
});
|
||||
|
||||
try swapRouter.exactOutputSingle(params) returns (uint256 amountInUsed) {
|
||||
// refund recipient with any UST not used in the swap
|
||||
if (amountInUsed < maxAmountInLessFees) {
|
||||
TransferHelper.safeApprove(feeTokenAddress, address(swapRouter), 0);
|
||||
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees - amountInUsed);
|
||||
}
|
||||
return amountInUsed;
|
||||
} catch {
|
||||
feeToken.safeTransfer(payload.recipientAddress, maxAmountInLessFees);
|
||||
}
|
||||
}
|
||||
|
||||
function _swapExactOutBeforeTransfer(
|
||||
uint256 amountOut,
|
||||
uint256 amountInMaximum,
|
||||
address contractCaller,
|
||||
address[] calldata path,
|
||||
uint256 deadline,
|
||||
uint24 poolFee
|
||||
) internal {
|
||||
// transfer the allowed amount of tokens to this contract
|
||||
IERC20 token = IERC20(path[0]);
|
||||
token.safeTransferFrom(contractCaller, address(this), amountInMaximum);
|
||||
|
||||
// approve the router to spend the specifed amountInMaximum of tokens
|
||||
TransferHelper.safeApprove(path[0], address(swapRouter), amountInMaximum);
|
||||
|
||||
// set swap options with user params
|
||||
ISwapRouter.ExactOutputSingleParams memory params =
|
||||
ISwapRouter.ExactOutputSingleParams({
|
||||
tokenIn: path[0],
|
||||
tokenOut: path[1],
|
||||
fee: poolFee,
|
||||
recipient: address(this),
|
||||
deadline: deadline,
|
||||
amountOut: amountOut,
|
||||
amountInMaximum: amountInMaximum,
|
||||
sqrtPriceLimitX96: 0
|
||||
});
|
||||
|
||||
// executes the swap returning the amountIn needed to spend to receive the desired amountOut
|
||||
uint256 amountInUsed = swapRouter.exactOutputSingle(params);
|
||||
|
||||
// refund contractCaller with any amountIn that's not spent
|
||||
if (amountInUsed < amountInMaximum) {
|
||||
TransferHelper.safeApprove(path[0], address(swapRouter), 0);
|
||||
token.safeTransfer(contractCaller, amountInMaximum - amountInUsed);
|
||||
}
|
||||
}
|
||||
|
||||
function swapExactOutToV2(
|
||||
SwapHelper.ExactOutParameters calldata swapParams,
|
||||
address[] calldata path,
|
||||
uint256 relayerFee,
|
||||
uint16 targetChainId, // make sure target contract address belongs to targeChainId
|
||||
bytes32 targetContractAddress,
|
||||
uint32 nonce
|
||||
) external {
|
||||
require(swapParams.amountOut > relayerFee, "insufficient amountOut to pay relayer");
|
||||
require(path[1]==feeTokenAddress, "tokenOut must be UST for first swap");
|
||||
|
||||
// peform the first swap
|
||||
_swapExactOutBeforeTransfer(
|
||||
swapParams.amountOut,
|
||||
swapParams.amountInMaximum,
|
||||
msg.sender,
|
||||
path[0:2],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee
|
||||
);
|
||||
|
||||
// encode payload for second swap
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOut,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactOut
|
||||
);
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(feeTokenAddress, tokenBridgeAddress, swapParams.amountOut);
|
||||
|
||||
// send transfer with payload to the TokenBridge
|
||||
TokenBridge(tokenBridgeAddress).transferTokensWithPayload(
|
||||
feeTokenAddress, swapParams.amountOut, targetChainId, targetContractAddress, relayerFee, nonce, payload
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// contracts/Messages.sol
|
||||
// SPDX-License-Identifier: Apache 2
|
||||
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import "./Structs.sol";
|
||||
|
||||
interface IWormhole is Structs {
|
||||
event LogMessagePublished(address indexed sender, uint64 sequence, uint32 nonce, bytes payload, uint8 consistencyLevel);
|
||||
|
||||
function publishMessage(
|
||||
uint32 nonce,
|
||||
bytes memory payload,
|
||||
uint8 consistencyLevel
|
||||
) external payable returns (uint64 sequence);
|
||||
|
||||
function parseAndVerifyVM(bytes calldata encodedVM) external view returns (Structs.VM memory vm, bool valid, string memory reason);
|
||||
|
||||
function verifyVM(Structs.VM memory vm) external view returns (bool valid, string memory reason);
|
||||
|
||||
function verifySignatures(bytes32 hash, Structs.Signature[] memory signatures, Structs.GuardianSet memory guardianSet) external pure returns (bool valid, string memory reason) ;
|
||||
|
||||
function parseVM(bytes memory encodedVM) external pure returns (Structs.VM memory vm);
|
||||
|
||||
function getGuardianSet(uint32 index) external view returns (Structs.GuardianSet memory) ;
|
||||
|
||||
function getCurrentGuardianSetIndex() external view returns (uint32) ;
|
||||
|
||||
function getGuardianSetExpiry() external view returns (uint32) ;
|
||||
|
||||
function governanceActionIsConsumed(bytes32 hash) external view returns (bool) ;
|
||||
|
||||
function isInitialized(address impl) external view returns (bool) ;
|
||||
|
||||
function chainId() external view returns (uint16) ;
|
||||
|
||||
function governanceChainId() external view returns (uint16);
|
||||
|
||||
function governanceContract() external view returns (bytes32);
|
||||
|
||||
function messageFee() external view returns (uint256) ;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.6;
|
||||
|
||||
contract Migrations {
|
||||
address public owner;
|
||||
uint public last_completed_migration;
|
||||
|
||||
modifier restricted() {
|
||||
if (msg.sender == owner) _;
|
||||
}
|
||||
|
||||
constructor() public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
function setCompleted(uint completed) public restricted {
|
||||
last_completed_migration = completed;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// contracts/Structs.sol
|
||||
// SPDX-License-Identifier: Apache 2
|
||||
|
||||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
interface Structs {
|
||||
struct Provider {
|
||||
uint16 chainId;
|
||||
uint16 governanceChainId;
|
||||
bytes32 governanceContract;
|
||||
}
|
||||
|
||||
struct GuardianSet {
|
||||
address[] keys;
|
||||
uint32 expirationTime;
|
||||
}
|
||||
|
||||
struct Signature {
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
uint8 v;
|
||||
uint8 guardianIndex;
|
||||
}
|
||||
|
||||
struct VM {
|
||||
uint8 version;
|
||||
uint32 timestamp;
|
||||
uint32 nonce;
|
||||
uint16 emitterChainId;
|
||||
bytes32 emitterAddress;
|
||||
uint64 sequence;
|
||||
uint8 consistencyLevel;
|
||||
bytes payload;
|
||||
|
||||
uint32 guardianSetIndex;
|
||||
Signature[] signatures;
|
||||
|
||||
bytes32 hash;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
pragma solidity ^0.7.6;
|
||||
pragma abicoder v2;
|
||||
|
||||
import './IWormhole.sol';
|
||||
import 'solidity-bytes-utils/contracts/BytesLib.sol';
|
||||
|
||||
library SwapHelper {
|
||||
using BytesLib for bytes;
|
||||
|
||||
struct ExactInParameters {
|
||||
uint256 amountIn;
|
||||
uint256 amountOutMinimum;
|
||||
uint256 targetAmountOutMinimum;
|
||||
address targetChainRecipient;
|
||||
uint256 deadline;
|
||||
uint24 poolFee;
|
||||
}
|
||||
|
||||
struct ExactOutParameters {
|
||||
uint256 amountOut;
|
||||
uint256 amountInMaximum;
|
||||
uint256 targetAmountOut;
|
||||
address targetChainRecipient;
|
||||
uint256 deadline;
|
||||
uint24 poolFee;
|
||||
}
|
||||
|
||||
struct DecodedVaaParameters {
|
||||
// in order of decoding
|
||||
uint8 version;
|
||||
uint256 swapAmount;
|
||||
address contractAddress;
|
||||
uint256 relayerFee;
|
||||
uint256 estimatedAmount;
|
||||
address recipientAddress;
|
||||
address[2] path;
|
||||
uint256 deadline;
|
||||
uint24 poolFee;
|
||||
uint8 swapType;
|
||||
}
|
||||
|
||||
function decodeVaaPayload(
|
||||
IWormhole.VM memory encodedVm
|
||||
) public view returns (DecodedVaaParameters memory decoded) {
|
||||
uint index = 0;
|
||||
|
||||
decoded.version = encodedVm.payload.toUint8(index);
|
||||
index += 1;
|
||||
|
||||
decoded.swapAmount = encodedVm.payload.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
// skip
|
||||
index += 46;
|
||||
|
||||
decoded.contractAddress = encodedVm.payload.toAddress(index);
|
||||
index += 20;
|
||||
|
||||
// skip
|
||||
index += 2;
|
||||
|
||||
decoded.relayerFee = encodedVm.payload.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
decoded.estimatedAmount = encodedVm.payload.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
decoded.recipientAddress = encodedVm.payload.toAddress(index);
|
||||
index += 20;
|
||||
|
||||
decoded.path[0] = encodedVm.payload.toAddress(index);
|
||||
index += 20;
|
||||
|
||||
decoded.path[1] = encodedVm.payload.toAddress(index);
|
||||
index += 20;
|
||||
|
||||
decoded.deadline = encodedVm.payload.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
// skip
|
||||
index += 1;
|
||||
|
||||
decoded.poolFee = encodedVm.payload.toUint16(index);
|
||||
index += 2;
|
||||
|
||||
decoded.swapType = encodedVm.payload.toUint8(index);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
npx truffle migrate --config truffle-config.ethereum.js --network goerli --reset
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
npx truffle migrate --config truffle-config.polygon.js --network mumbai --reset
|
|
@ -0,0 +1,5 @@
|
|||
const Migrations = artifacts.require("Migrations");
|
||||
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
const CrossChainSwapV3 = artifacts.require('CrossChainSwapV3');
|
||||
const CrossChainSwapV2 = artifacts.require('CrossChainSwapV2');
|
||||
const BytesDecodingTest = artifacts.require('BytesDecodingTest');
|
||||
|
||||
module.exports = function(deployer) {
|
||||
// CrossChainSwapV3
|
||||
{
|
||||
const routerAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564'
|
||||
const feeTokenAddress = '0x36Ed51Afc79619b299b238898E72ce482600568a' // wUST
|
||||
const tokenBridgeAddress = '0xF890982f9310df57d00f659cf4fd87e65adEd8d7';
|
||||
|
||||
deployer.deploy(CrossChainSwapV3, routerAddress, feeTokenAddress, tokenBridgeAddress);
|
||||
}
|
||||
|
||||
// CrossChainSwapV2
|
||||
{
|
||||
const routerAddress = '0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff'; // quickwap
|
||||
const feeTokenAddress = '0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c'; // wUST
|
||||
const tokenBridgeAddress = '0x377D55a7928c046E18eEbb61977e714d2a76472a';
|
||||
|
||||
deployer.deploy(CrossChainSwapV2, routerAddress, feeTokenAddress, tokenBridgeAddress);
|
||||
}
|
||||
|
||||
// BytesDecodingTest
|
||||
deployer.deploy(BytesDecodingTest)
|
||||
|
||||
|
||||
//deployer.link(ConvertLib, MetaCoin);
|
||||
//deployer.deploy(MetaCoin);
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
const Migrations = artifacts.require("Migrations");
|
||||
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
const fsp = require("fs/promises");
|
||||
|
||||
const CrossChainSwapV3 = artifacts.require("CrossChainSwapV3");
|
||||
const SwapHelper = artifacts.require("SwapHelper");
|
||||
|
||||
const scriptsAddressPath = "../ui/src/addresses";
|
||||
|
||||
module.exports = async function (deployer, network) {
|
||||
const routerAddress = "0xE592427A0AEce92De3Edee1F18E0157C05861564";
|
||||
const feeTokenAddress = "0x36Ed51Afc79619b299b238898E72ce482600568a"; // wUST
|
||||
const tokenBridgeAddress = "0xF890982f9310df57d00f659cf4fd87e65adEd8d7";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV3);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV3,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress
|
||||
);
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
||||
await fsp.writeFile(
|
||||
`${scriptsAddressPath}/${network}.ts`,
|
||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV3.address}';`
|
||||
);
|
||||
|
||||
//deployer.link(ConvertLib, MetaCoin);
|
||||
//deployer.deploy(MetaCoin);
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
const Migrations = artifacts.require("Migrations");
|
||||
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
const fsp = require("fs/promises");
|
||||
|
||||
const CrossChainSwapV2 = artifacts.require("CrossChainSwapV2");
|
||||
const SwapHelper = artifacts.require("SwapHelper");
|
||||
|
||||
const scriptsAddressPath = "../ui/src/addresses";
|
||||
|
||||
module.exports = async function (deployer, network) {
|
||||
const routerAddress = "0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff"; // quickwap
|
||||
const feeTokenAddress = "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c"; // wUST
|
||||
const tokenBridgeAddress = "0x377D55a7928c046E18eEbb61977e714d2a76472a";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV2,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress
|
||||
);
|
||||
|
||||
// save the contract address somewhere
|
||||
await fsp.mkdir(scriptsAddressPath, { recursive: true });
|
||||
|
||||
await fsp.writeFile(
|
||||
`${scriptsAddressPath}/${network}.ts`,
|
||||
`export const SWAP_CONTRACT_ADDRESS = '${CrossChainSwapV2.address}';`
|
||||
);
|
||||
|
||||
//deployer.link(ConvertLib, MetaCoin);
|
||||
//deployer.deploy(MetaCoin);
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "contracts",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@truffle/hdwallet-provider": "^2.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v3-periphery": "^1.3.0",
|
||||
"dotenv": "^14.2.0",
|
||||
"solidity-bytes-utils": "github:GNSPS/solidity-bytes-utils#feat/update-0.7.0",
|
||||
"truffle": "^5.4.29"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
const HDWalletProvider = require("@truffle/hdwallet-provider");
|
||||
|
||||
require("dotenv").config({ path: ".env" });
|
||||
|
||||
/**
|
||||
* Use this file to configure your truffle project. It's seeded with some
|
||||
* common settings for different networks and features like migrations,
|
||||
* compilation and testing. Uncomment the ones you need or modify
|
||||
* them to suit your project as necessary.
|
||||
*
|
||||
* More information about configuration can be found at:
|
||||
*
|
||||
* trufflesuite.com/docs/advanced/configuration
|
||||
*
|
||||
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
|
||||
* to sign your transactions before they're sent to a remote public node. Infura accounts
|
||||
* are available for free at: infura.io/register.
|
||||
*
|
||||
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
|
||||
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
|
||||
* phrase from a file you've .gitignored so it doesn't accidentally become public.
|
||||
*
|
||||
*/
|
||||
|
||||
// const HDWalletProvider = require('@truffle/hdwallet-provider');
|
||||
//
|
||||
// const fs = require('fs');
|
||||
// const mnemonic = fs.readFileSync('.secret').toString().trim();
|
||||
|
||||
module.exports = {
|
||||
contracts_directory: "./contracts",
|
||||
contracts_build_directory: "./build/contracts",
|
||||
migrations_directory: "./migrations/ethereum",
|
||||
/**
|
||||
* Networks define how you connect to your ethereum client and let you set the
|
||||
* defaults web3 uses to send transactions. If you don't specify one truffle
|
||||
* will spin up a development blockchain for you on port 9545 when you
|
||||
* run `develop` or `test`. You can ask a truffle command to use a specific
|
||||
* network from the command line, e.g
|
||||
*
|
||||
* $ truffle test --network <network-name>
|
||||
*/
|
||||
|
||||
networks: {
|
||||
// Useful for testing. The `development` name is special - truffle uses it by default
|
||||
// if it's defined here and no other network is specified at the command line.
|
||||
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
|
||||
// tab if you use this network and you must also set the `host`, `port` and `network_id`
|
||||
// options below to some value.
|
||||
//
|
||||
development: {
|
||||
host: "127.0.0.1", // Localhost (default: none)
|
||||
port: 8545, // Standard Ethereum port (default: none)
|
||||
network_id: "*", // Any network (default: none)
|
||||
},
|
||||
// Another network with more advanced options...
|
||||
// advanced: {
|
||||
// port: 8777, // Custom port
|
||||
// network_id: 1342, // Custom network
|
||||
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
|
||||
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
|
||||
// from: <address>, // Account to send txs from (default: accounts[0])
|
||||
// websocket: true // Enable EventEmitter interface for web3 (default: false)
|
||||
// },
|
||||
// Useful for deploying to a public network.
|
||||
// NB: It's important to wrap the provider as a function.
|
||||
goerli: {
|
||||
provider: () =>
|
||||
new HDWalletProvider(
|
||||
process.env.ETH_PRIVATE_KEY,
|
||||
process.env.GOERLI_PROVIDER
|
||||
),
|
||||
network_id: 5,
|
||||
gas: 4465030,
|
||||
//confirmations: 2, // # of confs to wait between deployments. (default: 0)
|
||||
//timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
|
||||
//skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
|
||||
},
|
||||
// Useful for private networks
|
||||
// private: {
|
||||
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
|
||||
// network_id: 2111, // This network is yours, in the cloud.
|
||||
// production: true // Treats this network as if it was a public net. (default: false)
|
||||
// }
|
||||
},
|
||||
|
||||
// Set default mocha options here, use special reporters etc.
|
||||
mocha: {
|
||||
// timeout: 100000
|
||||
},
|
||||
|
||||
// Configure your compilers
|
||||
compilers: {
|
||||
solc: {
|
||||
version: "0.7.6", // Fetch exact version from solc-bin (default: truffle's version)
|
||||
// docker: true, // Use '0.5.1' you've installed locally with docker (default: false)
|
||||
// settings: { // See the solidity docs for advice about optimization and evmVersion
|
||||
// optimizer: {
|
||||
// enabled: false,
|
||||
// runs: 200
|
||||
// },
|
||||
// evmVersion: 'byzantium'
|
||||
// }
|
||||
},
|
||||
},
|
||||
|
||||
// Truffle DB is currently disabled by default; to enable it, change enabled:
|
||||
// false to enabled: true. The default storage location can also be
|
||||
// overridden by specifying the adapter settings, as shown in the commented code below.
|
||||
//
|
||||
// NOTE: It is not possible to migrate your contracts to truffle DB and you should
|
||||
// make a backup of your artifacts to a safe location before enabling this feature.
|
||||
//
|
||||
// After you backed up your artifacts you can utilize db by running migrate as follows:
|
||||
// $ truffle migrate --reset --compile-all
|
||||
//
|
||||
// db: {
|
||||
// enabled: false,
|
||||
// host: '127.0.0.1',
|
||||
// adapter: {
|
||||
// name: 'sqlite',
|
||||
// settings: {
|
||||
// directory: '.db'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Use this file to configure your truffle project. It's seeded with some
|
||||
* common settings for different networks and features like migrations,
|
||||
* compilation and testing. Uncomment the ones you need or modify
|
||||
* them to suit your project as necessary.
|
||||
*
|
||||
* More information about configuration can be found at:
|
||||
*
|
||||
* trufflesuite.com/docs/advanced/configuration
|
||||
*
|
||||
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
|
||||
* to sign your transactions before they're sent to a remote public node. Infura accounts
|
||||
* are available for free at: infura.io/register.
|
||||
*
|
||||
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
|
||||
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
|
||||
* phrase from a file you've .gitignored so it doesn't accidentally become public.
|
||||
*
|
||||
*/
|
||||
|
||||
// const HDWalletProvider = require('@truffle/hdwallet-provider');
|
||||
// const infuraKey = 'fj4jll3k.....';
|
||||
//
|
||||
// const fs = require('fs');
|
||||
// const mnemonic = fs.readFileSync('.secret').toString().trim();
|
||||
|
||||
module.exports = {
|
||||
contracts_directory: "./contracts",
|
||||
contracts_build_directory: "./build/contracts",
|
||||
migrations_directory: "./migrations/development",
|
||||
|
||||
/**
|
||||
* Networks define how you connect to your ethereum client and let you set the
|
||||
* defaults web3 uses to send transactions. If you don't specify one truffle
|
||||
* will spin up a development blockchain for you on port 9545 when you
|
||||
* run `develop` or `test`. You can ask a truffle command to use a specific
|
||||
* network from the command line, e.g
|
||||
*
|
||||
* $ truffle test --network <network-name>
|
||||
*/
|
||||
|
||||
networks: {
|
||||
development: {
|
||||
host: "127.0.0.1", // Localhost (default: none)
|
||||
port: 8545, // Standard Ethereum port (default: none)
|
||||
network_id: "*", // Any network (default: none)
|
||||
},
|
||||
},
|
||||
|
||||
// Set default mocha options here, use special reporters etc.
|
||||
mocha: {
|
||||
// timeout: 100000
|
||||
},
|
||||
|
||||
// Configure your compilers
|
||||
compilers: {
|
||||
solc: {
|
||||
version: "0.7.6", // Fetch exact version from solc-bin (default: truffle's version)
|
||||
// docker: true, // Use '0.5.1' you've installed locally with docker (default: false)
|
||||
// settings: { // See the solidity docs for advice about optimization and evmVersion
|
||||
// optimizer: {
|
||||
// enabled: false,
|
||||
// runs: 200
|
||||
// },
|
||||
// evmVersion: 'byzantium'
|
||||
// }
|
||||
},
|
||||
},
|
||||
|
||||
// Truffle DB is enabled in this project by default. Enabling Truffle DB surfaces access to the @truffle/db package
|
||||
// for querying data about the contracts, deployments, and networks in this project
|
||||
//db: {
|
||||
// enabled: true
|
||||
//}
|
||||
};
|
|
@ -0,0 +1,127 @@
|
|||
const HDWalletProvider = require("@truffle/hdwallet-provider");
|
||||
|
||||
require("dotenv").config({ path: ".env" });
|
||||
|
||||
/**
|
||||
* Use this file to configure your truffle project. It's seeded with some
|
||||
* common settings for different networks and features like migrations,
|
||||
* compilation and testing. Uncomment the ones you need or modify
|
||||
* them to suit your project as necessary.
|
||||
*
|
||||
* More information about configuration can be found at:
|
||||
*
|
||||
* trufflesuite.com/docs/advanced/configuration
|
||||
*
|
||||
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
|
||||
* to sign your transactions before they're sent to a remote public node. Infura accounts
|
||||
* are available for free at: infura.io/register.
|
||||
*
|
||||
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
|
||||
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
|
||||
* phrase from a file you've .gitignored so it doesn't accidentally become public.
|
||||
*
|
||||
*/
|
||||
|
||||
// const HDWalletProvider = require('@truffle/hdwallet-provider');
|
||||
//
|
||||
// const fs = require('fs');
|
||||
// const mnemonic = fs.readFileSync('.secret').toString().trim();
|
||||
|
||||
module.exports = {
|
||||
contracts_directory: "./contracts",
|
||||
contracts_build_directory: "./build/contracts",
|
||||
migrations_directory: "./migrations/polygon",
|
||||
/**
|
||||
* Networks define how you connect to your ethereum client and let you set the
|
||||
* defaults web3 uses to send transactions. If you don't specify one truffle
|
||||
* will spin up a development blockchain for you on port 9545 when you
|
||||
* run `develop` or `test`. You can ask a truffle command to use a specific
|
||||
* network from the command line, e.g
|
||||
*
|
||||
* $ truffle test --network <network-name>
|
||||
*/
|
||||
|
||||
networks: {
|
||||
// Useful for testing. The `development` name is special - truffle uses it by default
|
||||
// if it's defined here and no other network is specified at the command line.
|
||||
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
|
||||
// tab if you use this network and you must also set the `host`, `port` and `network_id`
|
||||
// options below to some value.
|
||||
//
|
||||
development: {
|
||||
host: "127.0.0.1", // Localhost (default: none)
|
||||
port: 8545, // Standard Ethereum port (default: none)
|
||||
network_id: "*", // Any network (default: none)
|
||||
},
|
||||
// Another network with more advanced options...
|
||||
// advanced: {
|
||||
// port: 8777, // Custom port
|
||||
// network_id: 1342, // Custom network
|
||||
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
|
||||
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
|
||||
// from: <address>, // Account to send txs from (default: accounts[0])
|
||||
// websocket: true // Enable EventEmitter interface for web3 (default: false)
|
||||
// },
|
||||
// Useful for deploying to a public network.
|
||||
// NB: It's important to wrap the provider as a function.
|
||||
mumbai: {
|
||||
provider: () =>
|
||||
new HDWalletProvider(
|
||||
process.env.ETH_PRIVATE_KEY,
|
||||
process.env.MUMBAI_PROVIDER
|
||||
),
|
||||
network_id: 80001,
|
||||
gas: 4465030,
|
||||
//confirmations: 2, // # of confs to wait between deployments. (default: 0)
|
||||
//timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
|
||||
//skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
|
||||
},
|
||||
// Useful for private networks
|
||||
// private: {
|
||||
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
|
||||
// network_id: 2111, // This network is yours, in the cloud.
|
||||
// production: true // Treats this network as if it was a public net. (default: false)
|
||||
// }
|
||||
},
|
||||
|
||||
// Set default mocha options here, use special reporters etc.
|
||||
mocha: {
|
||||
// timeout: 100000
|
||||
},
|
||||
|
||||
// Configure your compilers
|
||||
compilers: {
|
||||
solc: {
|
||||
version: "0.7.6", // Fetch exact version from solc-bin (default: truffle's version)
|
||||
// docker: true, // Use '0.5.1' you've installed locally with docker (default: false)
|
||||
// settings: { // See the solidity docs for advice about optimization and evmVersion
|
||||
// optimizer: {
|
||||
// enabled: false,
|
||||
// runs: 200
|
||||
// },
|
||||
// evmVersion: 'byzantium'
|
||||
// }
|
||||
},
|
||||
},
|
||||
|
||||
// Truffle DB is currently disabled by default; to enable it, change enabled:
|
||||
// false to enabled: true. The default storage location can also be
|
||||
// overridden by specifying the adapter settings, as shown in the commented code below.
|
||||
//
|
||||
// NOTE: It is not possible to migrate your contracts to truffle DB and you should
|
||||
// make a backup of your artifacts to a safe location before enabling this feature.
|
||||
//
|
||||
// After you backed up your artifacts you can utilize db by running migrate as follows:
|
||||
// $ truffle migrate --reset --compile-all
|
||||
//
|
||||
// db: {
|
||||
// enabled: false,
|
||||
// host: '127.0.0.1',
|
||||
// adapter: {
|
||||
// name: 'sqlite',
|
||||
// settings: {
|
||||
// directory: '.db'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
REACT_APP_GOERLI_PROVIDER=https://goerli.infura.io/v3/YOUR-PROJECT-ID
|
||||
REACT_APP_MUMBAI_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID
|
|
@ -0,0 +1,24 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -0,0 +1,28 @@
|
|||
const { addBeforeLoader, loaderByName } = require("@craco/craco");
|
||||
|
||||
module.exports = {
|
||||
webpack: {
|
||||
configure: (webpackConfig) => {
|
||||
const wasmExtensionRegExp = /\.wasm$/;
|
||||
webpackConfig.resolve.extensions.push(".wasm");
|
||||
|
||||
webpackConfig.module.rules.forEach((rule) => {
|
||||
(rule.oneOf || []).forEach((oneOf) => {
|
||||
if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
|
||||
oneOf.exclude.push(wasmExtensionRegExp);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const wasmLoader = {
|
||||
test: /\.wasm$/,
|
||||
include: /node_modules\/(bridge|token-bridge)/,
|
||||
loaders: ["wasm-loader"],
|
||||
};
|
||||
|
||||
addBeforeLoader(webpackConfig, loaderByName("file-loader"), wasmLoader);
|
||||
|
||||
return webpackConfig;
|
||||
},
|
||||
},
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "Cross Chain Swap",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.1.6",
|
||||
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@types/node": "^16.11.19",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@uniswap/smart-order-router": "^2.1.1",
|
||||
"@uniswap/v2-core": "^1.0.1",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-periphery": "1.3",
|
||||
"@uniswap/v3-sdk": "^3.8.1",
|
||||
"ethers": "^5.5.3",
|
||||
"jsbi": "^3.2.5",
|
||||
"notistack": "^1.0.10",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"typescript": "^4.4.2",
|
||||
"use-debounce": "^7.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^6.3.0",
|
||||
"wasm-loader": "^1.3.0"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Cross Chain Swap</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"short_name": "Cross Chain Swap",
|
||||
"name": "Cross Chain Swap Example Program",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
|
@ -0,0 +1,5 @@
|
|||
import Home from "./views/Home";
|
||||
|
||||
export default function App() {
|
||||
return <Home />;
|
||||
}
|
|
@ -0,0 +1,751 @@
|
|||
[
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "WETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountADesired",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBDesired",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountAMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "addLiquidity",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenDesired",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "addLiquidityETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountToken",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "factory",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveOut",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getAmountIn",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveOut",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getAmountOut",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"name": "getAmountsIn",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"name": "getAmountsOut",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "quote",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountAMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidity",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountToken",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approveMax",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityETHWithPermit",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountToken",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountAMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approveMax",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityWithPermit",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapETHForExactTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactETHForTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactTokensForETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactTokensForTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountInMax",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapTokensForExactETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountInMax",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapTokensForExactTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,953 @@
|
|||
[
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "WETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountADesired",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBDesired",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountAMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "addLiquidity",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenDesired",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "addLiquidityETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountToken",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "factory",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveOut",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getAmountIn",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveOut",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getAmountOut",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"name": "getAmountsIn",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"name": "getAmountsOut",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "reserveB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "quote",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "pure",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountAMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidity",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountToken",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityETHSupportingFeeOnTransferTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approveMax",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityETHWithPermit",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountToken",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountTokenMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETHMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approveMax",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityETHWithPermitSupportingFeeOnTransferTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountETH",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenA",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenB",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "liquidity",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountAMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountBMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "approveMax",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "removeLiquidityWithPermit",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountA",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountB",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapETHForExactTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactETHForTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactETHForTokensSupportingFeeOnTransferTokens",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactTokensForETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactTokensForETHSupportingFeeOnTransferTokens",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactTokensForTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountIn",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOutMin",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapExactTokensForTokensSupportingFeeOnTransferTokens",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountInMax",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapTokensForExactETH",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountOut",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amountInMax",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address[]",
|
||||
"name": "path",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "swapTokensForExactTokens",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "amounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
|
@ -0,0 +1 @@
|
|||
*.json
|
|
@ -0,0 +1,224 @@
|
|||
{
|
||||
"abi": [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
goerli*.ts
|
||||
mumbai*.ts
|
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
makeStyles,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { ReactChild } from "react";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
position: "relative",
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing(2),
|
||||
textTransform: "none",
|
||||
width: "100%",
|
||||
},
|
||||
loader: {
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "50%",
|
||||
marginLeft: -12,
|
||||
marginBottom: 6,
|
||||
},
|
||||
error: {
|
||||
marginTop: theme.spacing(1),
|
||||
textAlign: "center",
|
||||
},
|
||||
}));
|
||||
|
||||
export default function ButtonWithLoader({
|
||||
disabled,
|
||||
onClick,
|
||||
showLoader,
|
||||
error,
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
showLoader?: boolean;
|
||||
error?: string;
|
||||
children: ReactChild;
|
||||
className?: string;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<>
|
||||
<div className={classes.root}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={className || classes.button}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
{showLoader ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
className={className || classes.loader}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{error ? (
|
||||
<Typography color="error" className={classes.error}>
|
||||
{error}
|
||||
</Typography>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import React from "react";
|
||||
|
||||
export default class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error(error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<Typography variant="h5" style={{ textAlign: "center", marginTop: 24 }}>
|
||||
"An unexpected error has occurred. Please refresh the page."
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { Typography } from "@material-ui/core";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import ToggleConnectedButton from "./ToggleConnectedButton";
|
||||
|
||||
const EthereumSignerKey = () => {
|
||||
const { connect, disconnect, signerAddress, providerError } =
|
||||
useEthereumProvider();
|
||||
return (
|
||||
<>
|
||||
<ToggleConnectedButton
|
||||
connect={connect}
|
||||
disconnect={disconnect}
|
||||
connected={!!signerAddress}
|
||||
pk={signerAddress || ""}
|
||||
/>
|
||||
{providerError ? (
|
||||
<Typography variant="body2" color="error">
|
||||
{providerError}
|
||||
</Typography>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EthereumSignerKey;
|
|
@ -0,0 +1,113 @@
|
|||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import SettingsIcon from "@material-ui/icons/Settings";
|
||||
import { makeStyles } from "@material-ui/styles";
|
||||
import { useState } from "react";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
topScrollPaper: {
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
topPaperScrollBody: {
|
||||
verticalAlign: "top",
|
||||
},
|
||||
button: {
|
||||
float: "right",
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const clamp = (value: number, min: number, max: number) => {
|
||||
if (isNaN(value)) {
|
||||
return value;
|
||||
}
|
||||
return Math.min(Math.max(min, value), max);
|
||||
};
|
||||
|
||||
export default function Settings({
|
||||
disabled,
|
||||
slippage,
|
||||
deadline,
|
||||
onSlippageChange,
|
||||
onDeadlineChange,
|
||||
}: {
|
||||
disabled: boolean;
|
||||
slippage: string;
|
||||
deadline: string;
|
||||
onSlippageChange: (slippage: string) => void;
|
||||
onDeadlineChange: (deadline: string) => void;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const [dialogIsOpen, setDialogIsOpen] = useState(false);
|
||||
|
||||
const dialog = (
|
||||
<Dialog
|
||||
open={dialogIsOpen}
|
||||
aria-labelledby="simple-dialog-title"
|
||||
onClose={() => setDialogIsOpen(false)}
|
||||
maxWidth="xs"
|
||||
scroll="paper"
|
||||
>
|
||||
<DialogTitle id="simple-dialog-title">Transaction Settings</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Slippage tolerance"
|
||||
value={slippage}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
||||
}}
|
||||
margin="normal"
|
||||
type="number"
|
||||
onChange={(event) => {
|
||||
onSlippageChange(
|
||||
clamp(parseFloat(event.target.value), 0, 100).toString()
|
||||
);
|
||||
}}
|
||||
></TextField>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Transaction deadline"
|
||||
value={deadline}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">minutes</InputAdornment>
|
||||
),
|
||||
}}
|
||||
margin="normal"
|
||||
type="number"
|
||||
onChange={(event) => {
|
||||
onDeadlineChange(
|
||||
clamp(parseFloat(event.target.value), 1, 100).toString()
|
||||
);
|
||||
}}
|
||||
></TextField>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
className={classes.button}
|
||||
onClick={() => {
|
||||
setDialogIsOpen(true);
|
||||
}}
|
||||
disabled={disabled}
|
||||
disableRipple
|
||||
endIcon={<SettingsIcon />}
|
||||
/>
|
||||
{dialog}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { Button, makeStyles, Tooltip } from "@material-ui/core";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
button: {
|
||||
display: "block",
|
||||
margin: `${theme.spacing(1)}px auto`,
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
},
|
||||
}));
|
||||
|
||||
const ToggleConnectedButton = ({
|
||||
connect,
|
||||
disconnect,
|
||||
connected,
|
||||
pk,
|
||||
}: {
|
||||
connect(): any;
|
||||
disconnect(): any;
|
||||
connected: boolean;
|
||||
pk: string;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const is0x = pk.startsWith("0x");
|
||||
return connected ? (
|
||||
<Tooltip title={pk}>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
className={classes.button}
|
||||
>
|
||||
Disconnect {pk.substring(0, is0x ? 6 : 3)}...
|
||||
{pk.substr(pk.length - (is0x ? 4 : 3))}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={connect}
|
||||
className={classes.button}
|
||||
>
|
||||
Connect Wallet
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleConnectedButton;
|
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import { TokenInfo } from "../utils/consts";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
select: {
|
||||
"& .MuiSelect-root": {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
},
|
||||
listItemIcon: {
|
||||
minWidth: 40,
|
||||
},
|
||||
icon: {
|
||||
height: 24,
|
||||
maxWidth: 24,
|
||||
},
|
||||
}));
|
||||
|
||||
const createTokenMenuItem = ({ name, logo }: TokenInfo, classes: any) => (
|
||||
<MenuItem key={name} value={name}>
|
||||
<ListItemIcon className={classes.listItemIcon}>
|
||||
<img src={logo} alt={name} className={classes.icon} />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{name}</ListItemText>
|
||||
</MenuItem>
|
||||
);
|
||||
|
||||
interface TokenSelectProps {
|
||||
tokens: TokenInfo[];
|
||||
value: string;
|
||||
onChange: (event: any) => void;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export default function TokenSelect({
|
||||
tokens,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: TokenSelectProps) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<TextField
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
select
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
className={classes.select}
|
||||
disabled={disabled}
|
||||
>
|
||||
{tokens.map((token) => createTokenMenuItem(token, classes))}
|
||||
</TextField>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
import detectEthereumProvider from "@metamask/detect-provider";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import React, {
|
||||
ReactChildren,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
export type Provider = ethers.providers.Web3Provider | undefined;
|
||||
export type Signer = ethers.Signer | undefined;
|
||||
|
||||
interface IEthereumProviderContext {
|
||||
connect(): void;
|
||||
disconnect(): void;
|
||||
provider: Provider;
|
||||
chainId: number | undefined;
|
||||
signer: Signer;
|
||||
signerAddress: string | undefined;
|
||||
providerError: string | null;
|
||||
}
|
||||
|
||||
const EthereumProviderContext = React.createContext<IEthereumProviderContext>({
|
||||
connect: () => {},
|
||||
disconnect: () => {},
|
||||
provider: undefined,
|
||||
chainId: undefined,
|
||||
signer: undefined,
|
||||
signerAddress: undefined,
|
||||
providerError: null,
|
||||
});
|
||||
export const EthereumProviderProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactChildren;
|
||||
}) => {
|
||||
const [providerError, setProviderError] = useState<string | null>(null);
|
||||
const [provider, setProvider] = useState<Provider>(undefined);
|
||||
const [chainId, setChainId] = useState<number | undefined>(undefined);
|
||||
const [signer, setSigner] = useState<Signer>(undefined);
|
||||
const [signerAddress, setSignerAddress] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const connect = useCallback(() => {
|
||||
setProviderError(null);
|
||||
detectEthereumProvider()
|
||||
.then((detectedProvider) => {
|
||||
if (detectedProvider) {
|
||||
const provider = new ethers.providers.Web3Provider(
|
||||
// @ts-ignore
|
||||
detectedProvider,
|
||||
"any"
|
||||
);
|
||||
provider
|
||||
.send("eth_requestAccounts", [])
|
||||
.then(() => {
|
||||
setProviderError(null);
|
||||
setProvider(provider);
|
||||
provider
|
||||
.getNetwork()
|
||||
.then((network) => {
|
||||
setChainId(network.chainId);
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while getting the network"
|
||||
);
|
||||
});
|
||||
const signer = provider.getSigner();
|
||||
setSigner(signer);
|
||||
signer
|
||||
.getAddress()
|
||||
.then((address) => {
|
||||
setSignerAddress(address);
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while getting the signer address"
|
||||
);
|
||||
});
|
||||
// TODO: try using ethers directly
|
||||
// @ts-ignore
|
||||
if (detectedProvider && detectedProvider.on) {
|
||||
// @ts-ignore
|
||||
detectedProvider.on("chainChanged", (chainId) => {
|
||||
try {
|
||||
setChainId(BigNumber.from(chainId).toNumber());
|
||||
} catch (e) {}
|
||||
});
|
||||
// @ts-ignore
|
||||
detectedProvider.on("accountsChanged", (accounts) => {
|
||||
try {
|
||||
const signer = provider.getSigner();
|
||||
setSigner(signer);
|
||||
signer
|
||||
.getAddress()
|
||||
.then((address) => {
|
||||
setSignerAddress(address);
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while getting the signer address"
|
||||
);
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError(
|
||||
"An error occurred while requesting eth accounts"
|
||||
);
|
||||
});
|
||||
} else {
|
||||
setProviderError("Please install MetaMask");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setProviderError("Please install MetaMask");
|
||||
});
|
||||
}, []);
|
||||
const disconnect = useCallback(() => {
|
||||
setProviderError(null);
|
||||
setProvider(undefined);
|
||||
setChainId(undefined);
|
||||
setSigner(undefined);
|
||||
setSignerAddress(undefined);
|
||||
}, []);
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
connect,
|
||||
disconnect,
|
||||
provider,
|
||||
chainId,
|
||||
signer,
|
||||
signerAddress,
|
||||
providerError,
|
||||
}),
|
||||
[
|
||||
connect,
|
||||
disconnect,
|
||||
provider,
|
||||
chainId,
|
||||
signer,
|
||||
signerAddress,
|
||||
providerError,
|
||||
]
|
||||
);
|
||||
return (
|
||||
<EthereumProviderContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</EthereumProviderContext.Provider>
|
||||
);
|
||||
};
|
||||
export const useEthereumProvider = () => {
|
||||
return useContext(EthereumProviderContext);
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
import { ChainId, CHAIN_ID_SOLANA, isEVMChain } from "@certusone/wormhole-sdk";
|
||||
import { hexlify, hexStripZeros } from "@ethersproject/bytes";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
// import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
import { getEvmChainId } from "../utils/consts";
|
||||
|
||||
const CLUSTER = "testnet"; // TODO: change this
|
||||
|
||||
const createWalletStatus = (
|
||||
isReady: boolean,
|
||||
statusMessage: string = "",
|
||||
forceNetworkSwitch: () => void,
|
||||
walletAddress?: string
|
||||
) => ({
|
||||
isReady,
|
||||
statusMessage,
|
||||
forceNetworkSwitch,
|
||||
walletAddress,
|
||||
});
|
||||
|
||||
function useIsWalletReady(
|
||||
chainId: ChainId,
|
||||
enableNetworkAutoswitch: boolean = true
|
||||
): {
|
||||
isReady: boolean;
|
||||
statusMessage: string;
|
||||
walletAddress?: string;
|
||||
forceNetworkSwitch: () => void;
|
||||
} {
|
||||
const autoSwitch = enableNetworkAutoswitch;
|
||||
// const solanaWallet = useSolanaWallet();
|
||||
// const solPK = solanaWallet?.publicKey;
|
||||
const {
|
||||
provider,
|
||||
signerAddress,
|
||||
chainId: evmChainId,
|
||||
} = useEthereumProvider();
|
||||
const hasEthInfo = !!provider && !!signerAddress;
|
||||
const correctEvmNetwork = getEvmChainId(chainId);
|
||||
const hasCorrectEvmNetwork = evmChainId === correctEvmNetwork;
|
||||
|
||||
const forceNetworkSwitch = useCallback(() => {
|
||||
if (provider && correctEvmNetwork) {
|
||||
if (!isEVMChain(chainId)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
provider.send("wallet_switchEthereumChain", [
|
||||
{ chainId: hexStripZeros(hexlify(correctEvmNetwork)) },
|
||||
]);
|
||||
} catch (e) {}
|
||||
}
|
||||
}, [provider, correctEvmNetwork, chainId]);
|
||||
|
||||
return useMemo(() => {
|
||||
//if (chainId === CHAIN_ID_SOLANA && solPK) {
|
||||
// return createWalletStatus(
|
||||
// true,
|
||||
// undefined,
|
||||
// forceNetworkSwitch,
|
||||
// solPK.toString()
|
||||
// );
|
||||
//}
|
||||
if (isEVMChain(chainId) && hasEthInfo && signerAddress) {
|
||||
if (hasCorrectEvmNetwork) {
|
||||
return createWalletStatus(
|
||||
true,
|
||||
undefined,
|
||||
forceNetworkSwitch,
|
||||
signerAddress
|
||||
);
|
||||
} else {
|
||||
if (provider && correctEvmNetwork && autoSwitch) {
|
||||
forceNetworkSwitch();
|
||||
}
|
||||
return createWalletStatus(
|
||||
false,
|
||||
`Wallet is not connected to ${CLUSTER}. Expected Chain ID: ${correctEvmNetwork}`,
|
||||
forceNetworkSwitch,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return createWalletStatus(
|
||||
false,
|
||||
"Wallet not connected",
|
||||
forceNetworkSwitch,
|
||||
undefined
|
||||
);
|
||||
}, [
|
||||
chainId,
|
||||
autoSwitch,
|
||||
forceNetworkSwitch,
|
||||
// solPK,
|
||||
hasEthInfo,
|
||||
correctEvmNetwork,
|
||||
hasCorrectEvmNetwork,
|
||||
provider,
|
||||
signerAddress,
|
||||
]);
|
||||
}
|
||||
|
||||
export default useIsWalletReady;
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1920 1920" enable-background="new 0 0 1920 1920" xml:space="preserve">
|
||||
<g>
|
||||
<polygon fill="#8A92B2" points="959.8,80.7 420.1,976.3 959.8,731 "/>
|
||||
<polygon fill="#62688F" points="959.8,731 420.1,976.3 959.8,1295.4 "/>
|
||||
<polygon fill="#62688F" points="1499.6,976.3 959.8,80.7 959.8,731 "/>
|
||||
<polygon fill="#454A75" points="959.8,1295.4 1499.6,976.3 959.8,731 "/>
|
||||
<polygon fill="#8A92B2" points="420.1,1078.7 959.8,1839.3 959.8,1397.6 "/>
|
||||
<polygon fill="#62688F" points="959.8,1397.6 959.8,1839.3 1499.9,1078.7 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 807 B |
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 38.4 33.5" style="enable-background:new 0 0 38.4 33.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#8247E5;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M29,10.2c-0.7-0.4-1.6-0.4-2.4,0L21,13.5l-3.8,2.1l-5.5,3.3c-0.7,0.4-1.6,0.4-2.4,0L5,16.3
|
||||
c-0.7-0.4-1.2-1.2-1.2-2.1v-5c0-0.8,0.4-1.6,1.2-2.1l4.3-2.5c0.7-0.4,1.6-0.4,2.4,0L16,7.2c0.7,0.4,1.2,1.2,1.2,2.1v3.3l3.8-2.2V7
|
||||
c0-0.8-0.4-1.6-1.2-2.1l-8-4.7c-0.7-0.4-1.6-0.4-2.4,0L1.2,5C0.4,5.4,0,6.2,0,7v9.4c0,0.8,0.4,1.6,1.2,2.1l8.1,4.7
|
||||
c0.7,0.4,1.6,0.4,2.4,0l5.5-3.2l3.8-2.2l5.5-3.2c0.7-0.4,1.6-0.4,2.4,0l4.3,2.5c0.7,0.4,1.2,1.2,1.2,2.1v5c0,0.8-0.4,1.6-1.2,2.1
|
||||
L29,28.8c-0.7,0.4-1.6,0.4-2.4,0l-4.3-2.5c-0.7-0.4-1.2-1.2-1.2-2.1V21l-3.8,2.2v3.3c0,0.8,0.4,1.6,1.2,2.1l8.1,4.7
|
||||
c0.7,0.4,1.6,0.4,2.4,0l8.1-4.7c0.7-0.4,1.2-1.2,1.2-2.1V17c0-0.8-0.4-1.6-1.2-2.1L29,10.2z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,8 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 73.18 86.09">
|
||||
<defs>
|
||||
<style>.cls-1{fill:red;}.cls-2{fill:#0073ff;}.cls-3{fill:#00f3d7;}</style>
|
||||
</defs>
|
||||
<path class="cls-1" d="M30.29,43.05A47.76,47.76,0,0,0,16.72,9.63a1.7,1.7,0,0,0-1.2-.5H4.34A1.67,1.67,0,0,0,2.67,10.8V22.29A1.69,1.69,0,0,0,4.24,24a19.15,19.15,0,0,1,0,38.18A1.68,1.68,0,0,0,2.67,63.8V75.29A1.68,1.68,0,0,0,4.34,77H15.52a1.66,1.66,0,0,0,1.2-.51A47.75,47.75,0,0,0,30.29,43.05Z"/>
|
||||
<path class="cls-2" d="M70.51,63.8a1.68,1.68,0,0,0-1.57-1.66,19.15,19.15,0,0,1,0-38.18,1.69,1.69,0,0,0,1.57-1.67V10.8a1.67,1.67,0,0,0-1.67-1.67H57.66a1.7,1.7,0,0,0-1.2.5,47.93,47.93,0,0,0,0,66.83,1.66,1.66,0,0,0,1.2.51H68.84a1.68,1.68,0,0,0,1.67-1.68Z"/>
|
||||
<path class="cls-3" d="M28.06,3.14a1.89,1.89,0,0,0-1.75,2.58,102.89,102.89,0,0,1,7.05,37.33,102.87,102.87,0,0,1-7,37.32A1.89,1.89,0,0,0,28.06,83h17a1.88,1.88,0,0,0,1.74-2.58,102.33,102.33,0,0,1,0-74.65,1.88,1.88,0,0,0-1.74-2.58Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 993 B |
|
@ -0,0 +1,23 @@
|
|||
import { CssBaseline } from "@material-ui/core";
|
||||
import { ThemeProvider } from "@material-ui/core/styles";
|
||||
import { SnackbarProvider } from "notistack";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
import ErrorBoundary from "./components/ErrorBoundary";
|
||||
import { EthereumProviderProvider } from "./contexts/EthereumProviderContext";
|
||||
import { theme } from "./muiTheme";
|
||||
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline>
|
||||
<EthereumProviderProvider>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<App />
|
||||
</SnackbarProvider>
|
||||
</EthereumProviderProvider>
|
||||
</CssBaseline>
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>,
|
||||
document.getElementById("root")
|
||||
);
|
|
@ -0,0 +1,160 @@
|
|||
import { createTheme, responsiveFontSizes } from "@material-ui/core";
|
||||
|
||||
export const COLORS = {
|
||||
blue: "#1975e6",
|
||||
blueWithTransparency: "rgba(25, 117, 230, 0.8)",
|
||||
gray: "#4e4e54",
|
||||
green: "#0ac2af",
|
||||
greenWithTransparency: "rgba(10, 194, 175, 0.8)",
|
||||
lightGreen: "rgba(51, 242, 223, 1)",
|
||||
lightBlue: "#83b9fc",
|
||||
nearBlack: "#000008",
|
||||
nearBlackWithMinorTransparency: "rgba(0,0,0,.25)",
|
||||
red: "#aa0818",
|
||||
darkRed: "#810612",
|
||||
};
|
||||
|
||||
export const theme = responsiveFontSizes(
|
||||
createTheme({
|
||||
palette: {
|
||||
type: "dark",
|
||||
background: {
|
||||
default: COLORS.nearBlack,
|
||||
paper: COLORS.nearBlack,
|
||||
},
|
||||
divider: COLORS.gray,
|
||||
text: {
|
||||
primary: "rgba(255,255,255,0.98)",
|
||||
},
|
||||
primary: {
|
||||
main: COLORS.blueWithTransparency,
|
||||
light: COLORS.lightBlue,
|
||||
},
|
||||
secondary: {
|
||||
main: COLORS.greenWithTransparency,
|
||||
light: COLORS.lightGreen,
|
||||
},
|
||||
error: {
|
||||
main: COLORS.red,
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: "'Sora', sans-serif",
|
||||
h1: {
|
||||
fontWeight: "200",
|
||||
},
|
||||
h2: {
|
||||
fontWeight: "200",
|
||||
},
|
||||
h4: {
|
||||
fontWeight: "500",
|
||||
},
|
||||
},
|
||||
overrides: {
|
||||
MuiCssBaseline: {
|
||||
"@global": {
|
||||
"*": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: `${COLORS.gray} ${COLORS.nearBlackWithMinorTransparency}`,
|
||||
},
|
||||
"*::-webkit-scrollbar": {
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: COLORS.gray,
|
||||
borderRadius: "4px",
|
||||
},
|
||||
"*::-webkit-scrollbar-corner": {
|
||||
// this hides an annoying white box which appears when both scrollbars are present
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAccordion: {
|
||||
root: {
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
"&:before": {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
rounded: {
|
||||
"&:first-child": {
|
||||
borderTopLeftRadius: "16px",
|
||||
borderTopRightRadius: "16px",
|
||||
},
|
||||
"&:last-child": {
|
||||
borderBottomLeftRadius: "16px",
|
||||
borderBottomRightRadius: "16px",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAlert: {
|
||||
root: {
|
||||
borderRadius: "8px",
|
||||
border: "1px solid",
|
||||
},
|
||||
},
|
||||
MuiButton: {
|
||||
root: {
|
||||
borderRadius: "5px",
|
||||
textTransform: "none",
|
||||
},
|
||||
},
|
||||
MuiLink: {
|
||||
root: {
|
||||
color: COLORS.lightBlue,
|
||||
},
|
||||
},
|
||||
MuiPaper: {
|
||||
rounded: {
|
||||
borderRadius: "16px",
|
||||
},
|
||||
},
|
||||
MuiStepper: {
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
MuiStep: {
|
||||
root: {
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
borderRadius: "16px",
|
||||
padding: 16,
|
||||
},
|
||||
},
|
||||
MuiStepConnector: {
|
||||
lineVertical: {
|
||||
borderLeftWidth: 0,
|
||||
},
|
||||
},
|
||||
MuiStepContent: {
|
||||
root: {
|
||||
borderLeftWidth: 0,
|
||||
},
|
||||
},
|
||||
MuiStepLabel: {
|
||||
label: {
|
||||
fontSize: 16,
|
||||
fontWeight: "300",
|
||||
"&.MuiStepLabel-active": {
|
||||
fontWeight: "300",
|
||||
},
|
||||
"&.MuiStepLabel-completed": {
|
||||
fontWeight: "300",
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
root: {
|
||||
fontSize: 18,
|
||||
fontWeight: "300",
|
||||
padding: 12,
|
||||
textTransform: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="react-scripts" />
|
|
@ -0,0 +1,322 @@
|
|||
import { ethers } from "ethers";
|
||||
import { UniEvmToken } from "./uniswap-core";
|
||||
import { QuickswapRouter } from "./quickswap";
|
||||
import { SingleAmmSwapRouter as UniswapV3Router } from "./uniswap-v3";
|
||||
import {
|
||||
ETH_NETWORK_CHAIN_ID,
|
||||
POLYGON_NETWORK_CHAIN_ID,
|
||||
} from "../utils/consts";
|
||||
|
||||
export { PROTOCOL as PROTOCOL_UNISWAP_V2 } from "./uniswap-v2";
|
||||
export { PROTOCOL as PROTOCOL_UNISWAP_V3 } from "./uniswap-v3";
|
||||
|
||||
export enum QuoteType {
|
||||
ExactIn = 1,
|
||||
ExactOut,
|
||||
}
|
||||
|
||||
function makeRouter(provider: ethers.providers.Provider, id: number) {
|
||||
switch (id) {
|
||||
case ETH_NETWORK_CHAIN_ID: {
|
||||
return new UniswapV3Router(provider);
|
||||
}
|
||||
case POLYGON_NETWORK_CHAIN_ID: {
|
||||
return new QuickswapRouter(provider);
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized chain id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getUstAddress(id: number): string {
|
||||
switch (id) {
|
||||
case ETH_NETWORK_CHAIN_ID: {
|
||||
return "0x36Ed51Afc79619b299b238898E72ce482600568a";
|
||||
}
|
||||
case POLYGON_NETWORK_CHAIN_ID: {
|
||||
return "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c";
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized chain id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function splitSlippageInHalf(totalSlippage: string): string {
|
||||
const divisor = ethers.FixedNumber.from("2");
|
||||
return ethers.FixedNumber.from(totalSlippage)
|
||||
.divUnsafe(divisor)
|
||||
.round(4)
|
||||
.toString();
|
||||
}
|
||||
|
||||
interface RelayerFee {
|
||||
amount: ethers.BigNumber;
|
||||
tokenAddress: string;
|
||||
}
|
||||
|
||||
export interface ExactInParameters {
|
||||
protocol: string;
|
||||
amountIn: ethers.BigNumber;
|
||||
minAmountOut: ethers.BigNumber;
|
||||
deadline: ethers.BigNumber;
|
||||
poolFee: string;
|
||||
path: [string, string];
|
||||
}
|
||||
|
||||
export interface ExactInCrossParameters {
|
||||
src: ExactInParameters;
|
||||
dst: ExactInParameters;
|
||||
relayerFee: RelayerFee;
|
||||
}
|
||||
|
||||
export interface ExactOutParameters {
|
||||
protocol: string;
|
||||
amountOut: ethers.BigNumber;
|
||||
maxAmountIn: ethers.BigNumber;
|
||||
deadline: ethers.BigNumber;
|
||||
poolFee: string;
|
||||
path: [string, string];
|
||||
}
|
||||
|
||||
export interface ExactOutCrossParameters {
|
||||
src: ExactOutParameters;
|
||||
dst: ExactOutParameters;
|
||||
relayerFee: RelayerFee;
|
||||
}
|
||||
|
||||
export class UniswapToUniswapQuoter {
|
||||
// providers
|
||||
srcProvider: ethers.providers.Provider;
|
||||
dstProvider: ethers.providers.Provider;
|
||||
|
||||
// networks
|
||||
srcNetwork: ethers.providers.Network;
|
||||
dstNetwork: ethers.providers.Network;
|
||||
|
||||
// routers
|
||||
srcRouter: UniswapV3Router | QuickswapRouter;
|
||||
dstRouter: UniswapV3Router | QuickswapRouter;
|
||||
|
||||
// tokens
|
||||
srcTokenIn: UniEvmToken;
|
||||
srcTokenOut: UniEvmToken;
|
||||
dstTokenIn: UniEvmToken;
|
||||
dstTokenOut: UniEvmToken;
|
||||
|
||||
constructor(
|
||||
srcProvider: ethers.providers.Provider,
|
||||
dstProvider: ethers.providers.Provider
|
||||
) {
|
||||
this.srcProvider = srcProvider;
|
||||
this.dstProvider = dstProvider;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
[this.srcNetwork, this.dstNetwork] = await Promise.all([
|
||||
this.srcProvider.getNetwork(),
|
||||
this.dstProvider.getNetwork(),
|
||||
]);
|
||||
|
||||
this.srcRouter = makeRouter(this.srcProvider, this.srcNetwork.chainId);
|
||||
this.dstRouter = makeRouter(this.dstProvider, this.dstNetwork.chainId);
|
||||
return;
|
||||
}
|
||||
|
||||
sameChain(): boolean {
|
||||
return this.srcNetwork.chainId === this.dstNetwork.chainId;
|
||||
}
|
||||
|
||||
async makeSrcTokens(
|
||||
tokenInAddress: string
|
||||
): Promise<[UniEvmToken, UniEvmToken]> {
|
||||
const ustOutAddress = getUstAddress(this.srcNetwork.chainId);
|
||||
|
||||
const router = this.srcRouter;
|
||||
|
||||
[this.srcTokenIn, this.srcTokenOut] = await Promise.all([
|
||||
router.makeToken(tokenInAddress),
|
||||
router.makeToken(ustOutAddress),
|
||||
]);
|
||||
return [this.srcTokenIn, this.srcTokenOut];
|
||||
}
|
||||
|
||||
async makeDstTokens(
|
||||
tokenOutAddress: string
|
||||
): Promise<[UniEvmToken, UniEvmToken]> {
|
||||
const ustInAddress = getUstAddress(this.dstNetwork.chainId);
|
||||
|
||||
const router = this.dstRouter;
|
||||
|
||||
[this.dstTokenIn, this.dstTokenOut] = await Promise.all([
|
||||
router.makeToken(ustInAddress),
|
||||
router.makeToken(tokenOutAddress),
|
||||
]);
|
||||
return [this.dstTokenIn, this.dstTokenOut];
|
||||
}
|
||||
|
||||
async computeAndVerifySrcPoolAddress(): Promise<string> {
|
||||
return this.srcRouter.computeAndVerifyPoolAddress(
|
||||
this.srcTokenIn,
|
||||
this.srcTokenOut
|
||||
);
|
||||
}
|
||||
|
||||
async computeAndVerifyDstPoolAddress(): Promise<string> {
|
||||
return this.dstRouter.computeAndVerifyPoolAddress(
|
||||
this.dstTokenIn,
|
||||
this.dstTokenOut
|
||||
);
|
||||
}
|
||||
|
||||
async computeExactInParameters(
|
||||
amountIn: string,
|
||||
slippage: string,
|
||||
relayerFeeUst: string
|
||||
): Promise<ExactInCrossParameters> {
|
||||
const singleSlippage = splitSlippageInHalf(slippage);
|
||||
|
||||
// src quote
|
||||
const srcRouter = this.srcRouter;
|
||||
const srcTokenIn = this.srcTokenIn;
|
||||
const srcTokenOut = this.srcTokenOut;
|
||||
const srcMinAmountOut = await srcRouter.fetchQuoteAmountOut(
|
||||
srcTokenIn,
|
||||
srcTokenOut,
|
||||
amountIn,
|
||||
singleSlippage
|
||||
);
|
||||
|
||||
// dst quote
|
||||
const dstRouter = this.dstRouter;
|
||||
const dstAmountIn = this.srcTokenOut.formatAmount(srcMinAmountOut);
|
||||
if (Number(dstAmountIn) < Number(relayerFeeUst)) {
|
||||
throw Error(
|
||||
`srcAmountOut <= relayerFeeUst. ${dstAmountIn} vs ${relayerFeeUst}`
|
||||
);
|
||||
}
|
||||
|
||||
const dstTokenIn = this.dstTokenIn;
|
||||
const dstTokenOut = this.dstTokenOut;
|
||||
const dstAmountInAfterFee = dstTokenIn.subtractAmounts(
|
||||
dstAmountIn,
|
||||
relayerFeeUst
|
||||
);
|
||||
|
||||
const dstMinAmountOut = await dstRouter.fetchQuoteAmountOut(
|
||||
dstTokenIn,
|
||||
dstTokenOut,
|
||||
dstAmountInAfterFee,
|
||||
singleSlippage
|
||||
);
|
||||
|
||||
const srcParameters: ExactInParameters = {
|
||||
protocol: srcRouter.getProtocol(),
|
||||
amountIn: srcTokenIn.computeUnitAmount(amountIn),
|
||||
minAmountOut: srcMinAmountOut,
|
||||
poolFee: srcRouter.getPoolFee(),
|
||||
deadline: srcRouter.getTradeDeadline(),
|
||||
path: [srcTokenIn.getAddress(), srcTokenOut.getAddress()],
|
||||
};
|
||||
|
||||
const dstParameters: ExactInParameters = {
|
||||
protocol: dstRouter.getProtocol(),
|
||||
amountIn: dstTokenIn.computeUnitAmount(dstAmountInAfterFee),
|
||||
minAmountOut: dstMinAmountOut,
|
||||
poolFee: dstRouter.getPoolFee(),
|
||||
deadline: dstRouter.getTradeDeadline(),
|
||||
path: [dstTokenIn.getAddress(), dstTokenOut.getAddress()],
|
||||
};
|
||||
|
||||
const params: ExactInCrossParameters = {
|
||||
src: srcParameters,
|
||||
dst: dstParameters,
|
||||
relayerFee: {
|
||||
amount: dstTokenIn.computeUnitAmount(relayerFeeUst),
|
||||
tokenAddress: this.dstTokenIn.getAddress(),
|
||||
},
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
async computeExactOutParameters(
|
||||
amountOut: string,
|
||||
slippage: string,
|
||||
relayerFeeUst: string
|
||||
): Promise<ExactOutCrossParameters> {
|
||||
const singleSlippage = splitSlippageInHalf(slippage);
|
||||
|
||||
// dst quote first
|
||||
const dstRouter = this.dstRouter;
|
||||
const dstTokenIn = this.dstTokenIn;
|
||||
const dstTokenOut = this.dstTokenOut;
|
||||
const dstMaxAmountIn = await dstRouter.fetchQuoteAmountIn(
|
||||
dstTokenIn,
|
||||
dstTokenOut,
|
||||
amountOut,
|
||||
singleSlippage
|
||||
);
|
||||
|
||||
// src quote
|
||||
const srcRouter = this.srcRouter;
|
||||
const srcAmountOut = this.dstTokenIn.formatAmount(dstMaxAmountIn);
|
||||
if (Number(srcAmountOut) < Number(relayerFeeUst)) {
|
||||
throw Error(
|
||||
`dstAmountIn <= relayerFeeUst. ${srcAmountOut} vs ${relayerFeeUst}`
|
||||
);
|
||||
}
|
||||
|
||||
const srcTokenIn = this.srcTokenIn;
|
||||
const srcTokenOut = this.srcTokenOut;
|
||||
const srcAmountOutBeforeFee = srcTokenOut.addAmounts(
|
||||
srcAmountOut,
|
||||
relayerFeeUst
|
||||
);
|
||||
|
||||
const srcMaxAmountIn = await srcRouter.fetchQuoteAmountIn(
|
||||
srcTokenIn,
|
||||
srcTokenOut,
|
||||
srcAmountOutBeforeFee,
|
||||
singleSlippage
|
||||
);
|
||||
|
||||
const srcParameters: ExactOutParameters = {
|
||||
protocol: srcRouter.getProtocol(),
|
||||
amountOut: srcTokenOut.computeUnitAmount(srcAmountOutBeforeFee),
|
||||
maxAmountIn: srcMaxAmountIn,
|
||||
poolFee: srcRouter.getPoolFee(),
|
||||
deadline: srcRouter.getTradeDeadline(),
|
||||
path: [srcTokenIn.getAddress(), srcTokenOut.getAddress()],
|
||||
};
|
||||
|
||||
const dstParameters: ExactOutParameters = {
|
||||
protocol: dstRouter.getProtocol(),
|
||||
amountOut: dstTokenOut.computeUnitAmount(amountOut),
|
||||
maxAmountIn: dstMaxAmountIn,
|
||||
poolFee: dstRouter.getPoolFee(),
|
||||
deadline: dstRouter.getTradeDeadline(),
|
||||
path: [dstTokenIn.getAddress(), dstTokenOut.getAddress()],
|
||||
};
|
||||
|
||||
const params: ExactOutCrossParameters = {
|
||||
src: srcParameters,
|
||||
dst: dstParameters,
|
||||
relayerFee: {
|
||||
amount: dstTokenIn.computeUnitAmount(relayerFeeUst),
|
||||
tokenAddress: this.dstTokenIn.getAddress(),
|
||||
},
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
setDeadlines(deadline: string): void {
|
||||
this.srcRouter.setDeadline(deadline);
|
||||
this.dstRouter.setDeadline(deadline);
|
||||
return;
|
||||
}
|
||||
|
||||
estimateUstFee(gasPriceInNativeCurrency: string): string {
|
||||
return "0";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import { ethers } from "ethers";
|
||||
|
||||
import { GenericToken } from "./generic";
|
||||
|
||||
// erc20 spec
|
||||
import { abi as Erc20Abi } from "../abi/erc20.json";
|
||||
import {
|
||||
TransactionReceipt,
|
||||
TransactionRequest,
|
||||
TransactionResponse,
|
||||
} from "@ethersproject/abstract-provider";
|
||||
import { APPROVAL_GAS_LIMIT } from "../utils/consts";
|
||||
|
||||
export class EvmToken extends GenericToken {
|
||||
token: ethers.Contract;
|
||||
decimals: number;
|
||||
|
||||
async initialize(provider: ethers.providers.Provider, tokenAddress: string) {
|
||||
this.token = await makeErc20Contract(provider, tokenAddress);
|
||||
this.decimals = await this.token.decimals();
|
||||
}
|
||||
|
||||
static async create(
|
||||
provider: ethers.providers.Provider,
|
||||
tokenAddress: string
|
||||
): Promise<EvmToken> {
|
||||
const o = new EvmToken();
|
||||
await o.initialize(provider, tokenAddress);
|
||||
return o;
|
||||
}
|
||||
|
||||
getAddress(): string {
|
||||
return this.token.address;
|
||||
}
|
||||
|
||||
getDecimals(): number {
|
||||
return this.decimals;
|
||||
}
|
||||
|
||||
getContract(): ethers.Contract {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
async getBalanceOf(signer: ethers.Wallet) {
|
||||
const decimals = this.getDecimals();
|
||||
const balanceBeforeDecimals = await this.token.balanceOf(signer.address);
|
||||
return ethers.utils.formatUnits(balanceBeforeDecimals.toString(), decimals);
|
||||
}
|
||||
|
||||
computeUnitAmount(amount: string): ethers.BigNumber {
|
||||
return ethers.utils.parseUnits(amount, this.getDecimals());
|
||||
}
|
||||
|
||||
formatAmount(unitAmount: ethers.BigNumber): string {
|
||||
return ethers.utils.formatUnits(unitAmount, this.getDecimals());
|
||||
}
|
||||
|
||||
addAmounts(left: string, right: string): string {
|
||||
const sum = ethers.FixedNumber.from(left).addUnsafe(
|
||||
ethers.FixedNumber.from(right)
|
||||
);
|
||||
return sum.round(this.getDecimals()).toString();
|
||||
}
|
||||
|
||||
subtractAmounts(left: string, right: string): string {
|
||||
const sum = ethers.FixedNumber.from(left).subUnsafe(
|
||||
ethers.FixedNumber.from(right)
|
||||
);
|
||||
return sum.round(this.getDecimals()).toString();
|
||||
}
|
||||
}
|
||||
|
||||
export async function makeErc20Contract(
|
||||
provider: ethers.providers.Provider,
|
||||
tokenAddress: string
|
||||
): Promise<ethers.Contract> {
|
||||
return new ethers.Contract(tokenAddress, Erc20Abi, provider);
|
||||
}
|
||||
|
||||
export async function approveContractTokenSpend(
|
||||
provider: ethers.providers.Provider,
|
||||
signer: ethers.Wallet,
|
||||
tokenContract: ethers.Contract,
|
||||
smartContractAddress: string,
|
||||
swapAmount: ethers.BigNumber
|
||||
): Promise<TransactionReceipt> {
|
||||
// build transaction for token spending
|
||||
const unsignedTx: TransactionRequest =
|
||||
await tokenContract.populateTransaction.approve(
|
||||
smartContractAddress,
|
||||
swapAmount
|
||||
);
|
||||
const nonce = await provider.getTransactionCount(signer.address, "latest");
|
||||
|
||||
const gasPrice = await signer.getGasPrice();
|
||||
const parsedGasPrice = ethers.utils.hexlify(parseInt(gasPrice.toString()));
|
||||
|
||||
unsignedTx.nonce = nonce;
|
||||
unsignedTx.gasLimit = ethers.BigNumber.from(APPROVAL_GAS_LIMIT);
|
||||
unsignedTx.gasPrice = ethers.BigNumber.from(parsedGasPrice);
|
||||
|
||||
// sign and send transaction
|
||||
const tx: TransactionResponse = await signer.sendTransaction(unsignedTx);
|
||||
return tx.wait();
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
export abstract class DexRouter {
|
||||
abstract makeToken(tokenAddress: string): any;
|
||||
abstract quoteLot(tokenA: any, tokenB: any, amount: string): Promise<any>;
|
||||
abstract setSlippage(slippage: string): void;
|
||||
}
|
||||
|
||||
export abstract class GenericToken {
|
||||
abstract getAddress(): string;
|
||||
|
||||
abstract getDecimals(): number;
|
||||
}
|
||||
|
||||
// TODO: wrap SwapRoute and other routes
|
||||
export class GenericRoute {
|
||||
route: any;
|
||||
|
||||
constructor(route: any) {
|
||||
this.route = route;
|
||||
}
|
||||
|
||||
getRoute(): any {
|
||||
return this.route;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { ethers } from "ethers";
|
||||
import { QUICKSWAP_FACTORY_ADDRESS } from "../utils/consts";
|
||||
import { SingleAmmSwapRouter } from "./uniswap-v2";
|
||||
|
||||
export { PROTOCOL } from "./uniswap-v2";
|
||||
|
||||
export class QuickswapRouter extends SingleAmmSwapRouter {
|
||||
constructor(provider: ethers.providers.Provider) {
|
||||
super(provider);
|
||||
super.setFactoryAddress(QUICKSWAP_FACTORY_ADDRESS);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
import { ethers } from "ethers";
|
||||
import { CurrencyAmount, Token } from "@uniswap/sdk-core";
|
||||
|
||||
import { EvmToken } from "./evm";
|
||||
|
||||
export function computeTradeDeadline(deadline: string): ethers.BigNumber {
|
||||
return ethers.BigNumber.from(Math.floor(Date.now() / 1000)).add(deadline);
|
||||
}
|
||||
|
||||
export class UniEvmToken {
|
||||
erc20: EvmToken;
|
||||
uniToken: Token;
|
||||
|
||||
constructor(chainId: number, erc20: EvmToken) {
|
||||
this.erc20 = erc20;
|
||||
|
||||
const address = this.getAddress();
|
||||
const decimals = this.getDecimals();
|
||||
|
||||
this.uniToken = new Token(chainId, address, decimals);
|
||||
}
|
||||
|
||||
getUniToken(): Token {
|
||||
return this.uniToken;
|
||||
}
|
||||
|
||||
getEvmToken(): EvmToken {
|
||||
return this.erc20;
|
||||
}
|
||||
|
||||
getDecimals(): number {
|
||||
return this.erc20.getDecimals();
|
||||
}
|
||||
|
||||
getContract(): ethers.Contract {
|
||||
return this.erc20.getContract();
|
||||
}
|
||||
|
||||
getAddress(): string {
|
||||
return this.erc20.getAddress();
|
||||
}
|
||||
|
||||
async getBalanceOf(signer: ethers.Wallet) {
|
||||
return this.erc20.getBalanceOf(signer);
|
||||
}
|
||||
|
||||
computeUnitAmount(amount: string): ethers.BigNumber {
|
||||
return this.erc20.computeUnitAmount(amount);
|
||||
}
|
||||
|
||||
formatAmount(unitAmount: ethers.BigNumber): string {
|
||||
return this.erc20.formatAmount(unitAmount);
|
||||
}
|
||||
|
||||
computeCurrencyAmount(amount: string): CurrencyAmount<Token> {
|
||||
const unitAmount = this.computeUnitAmount(amount);
|
||||
return CurrencyAmount.fromRawAmount(
|
||||
this.getUniToken(),
|
||||
unitAmount.toString()
|
||||
);
|
||||
}
|
||||
|
||||
addAmounts(left: string, right: string): string {
|
||||
return this.erc20.addAmounts(left, right);
|
||||
}
|
||||
|
||||
subtractAmounts(left: string, right: string): string {
|
||||
return this.erc20.subtractAmounts(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
export async function makeUniEvmToken(
|
||||
provider: ethers.providers.Provider,
|
||||
chainId: number,
|
||||
tokenAddress: string
|
||||
): Promise<UniEvmToken> {
|
||||
const erc20 = await EvmToken.create(provider, tokenAddress);
|
||||
return new UniEvmToken(chainId, erc20);
|
||||
}
|
||||
|
||||
export abstract class UniswapRouterCore {
|
||||
provider: ethers.providers.Provider;
|
||||
|
||||
// params
|
||||
deadline: string = "";
|
||||
|
||||
constructor(provider: ethers.providers.Provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public async makeToken(tokenAddress: string): Promise<UniEvmToken> {
|
||||
const network = await this.provider.getNetwork();
|
||||
return makeUniEvmToken(this.provider, network.chainId, tokenAddress);
|
||||
}
|
||||
|
||||
abstract computePoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): string;
|
||||
|
||||
abstract computeAndVerifyPoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): Promise<string>;
|
||||
|
||||
abstract fetchQuoteAmountOut(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber>;
|
||||
|
||||
abstract fetchQuoteAmountIn(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber>;
|
||||
|
||||
abstract getProtocol(): string;
|
||||
|
||||
public getPoolFee(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
public setDeadline(deadline: string): void {
|
||||
this.deadline = deadline;
|
||||
}
|
||||
|
||||
public getTradeDeadline(): ethers.BigNumber {
|
||||
return computeTradeDeadline(this.deadline);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
import { ethers } from "ethers";
|
||||
import { CurrencyAmount, TradeType } from "@uniswap/sdk-core";
|
||||
import { abi as IUniswapV2PairABI } from "@uniswap/v2-core/build/UniswapV2Pair.json";
|
||||
import { computePairAddress, Pair, Route, Trade } from "@uniswap/v2-sdk";
|
||||
|
||||
import { UniEvmToken, UniswapRouterCore } from "./uniswap-core";
|
||||
|
||||
export const PROTOCOL = "UniswapV2";
|
||||
|
||||
export class SingleAmmSwapRouter extends UniswapRouterCore {
|
||||
factoryAddress: string;
|
||||
pairContract: ethers.Contract;
|
||||
pair: Pair;
|
||||
|
||||
setFactoryAddress(factoryAddress: string) {
|
||||
this.factoryAddress = factoryAddress;
|
||||
return;
|
||||
}
|
||||
|
||||
computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string {
|
||||
if (this.factoryAddress === undefined) {
|
||||
throw Error("factoryAddress is undefined. use setFactoryAddress");
|
||||
}
|
||||
|
||||
return computePairAddress({
|
||||
factoryAddress: this.factoryAddress,
|
||||
tokenA: tokenIn.getUniToken(),
|
||||
tokenB: tokenOut.getUniToken(),
|
||||
});
|
||||
}
|
||||
|
||||
async computeAndVerifyPoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): Promise<string> {
|
||||
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
|
||||
// verify by attempting to call factory()
|
||||
const poolContract = new ethers.Contract(
|
||||
pairAddress,
|
||||
IUniswapV2PairABI,
|
||||
this.provider
|
||||
);
|
||||
await poolContract.factory();
|
||||
|
||||
return pairAddress;
|
||||
}
|
||||
|
||||
async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise<Pair> {
|
||||
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
|
||||
const pairContract = new ethers.Contract(
|
||||
pairAddress,
|
||||
IUniswapV2PairABI,
|
||||
this.provider
|
||||
);
|
||||
|
||||
const [token0, reserves] = await Promise.all([
|
||||
pairContract.token0(),
|
||||
pairContract.getReserves(),
|
||||
]);
|
||||
|
||||
const reserve0 = reserves._reserve0.toString();
|
||||
const reserve1 = reserves._reserve1.toString();
|
||||
|
||||
if (token0.toLowerCase() === tokenIn.getAddress().toLowerCase()) {
|
||||
return new Pair(
|
||||
CurrencyAmount.fromRawAmount(tokenIn.getUniToken(), reserve0),
|
||||
CurrencyAmount.fromRawAmount(tokenOut.getUniToken(), reserve1)
|
||||
);
|
||||
}
|
||||
|
||||
return new Pair(
|
||||
CurrencyAmount.fromRawAmount(tokenOut.getUniToken(), reserve0),
|
||||
CurrencyAmount.fromRawAmount(tokenIn.getUniToken(), reserve1)
|
||||
);
|
||||
}
|
||||
|
||||
async fetchQuoteAmountOut(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountIn: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
// create pool
|
||||
const pair = await this.createPool(tokenIn, tokenOut);
|
||||
// let's get that quote
|
||||
|
||||
const route = new Route(
|
||||
[pair],
|
||||
tokenIn.getUniToken(),
|
||||
tokenOut.getUniToken()
|
||||
);
|
||||
const currencyAmountIn = tokenIn.computeCurrencyAmount(amountIn);
|
||||
|
||||
const quote = new Trade(route, currencyAmountIn, TradeType.EXACT_INPUT);
|
||||
|
||||
const decimals = tokenOut.getDecimals();
|
||||
const minAmountOut = ethers.FixedNumber.from(
|
||||
quote.outputAmount.toSignificant(decimals)
|
||||
);
|
||||
|
||||
// calculate output amount with slippage
|
||||
const slippageMultiplier = ethers.FixedNumber.from("1").subUnsafe(
|
||||
ethers.FixedNumber.from(slippage)
|
||||
);
|
||||
const minAmountOutWithSlippage = minAmountOut
|
||||
.mulUnsafe(slippageMultiplier)
|
||||
.round(decimals);
|
||||
|
||||
return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString());
|
||||
}
|
||||
|
||||
async fetchQuoteAmountIn(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
// create pool
|
||||
const pair = await this.createPool(tokenIn, tokenOut);
|
||||
// let's get that quote
|
||||
|
||||
const route = new Route(
|
||||
[pair],
|
||||
tokenIn.getUniToken(),
|
||||
tokenOut.getUniToken()
|
||||
);
|
||||
const currencyAmountOut = tokenOut.computeCurrencyAmount(amountOut);
|
||||
|
||||
const quote = new Trade(route, currencyAmountOut, TradeType.EXACT_OUTPUT);
|
||||
|
||||
const decimals = tokenIn.getDecimals();
|
||||
const maxAmountIn = ethers.FixedNumber.from(
|
||||
quote.inputAmount.toSignificant(decimals)
|
||||
);
|
||||
|
||||
const slippageDivisor = ethers.FixedNumber.from("1").subUnsafe(
|
||||
ethers.FixedNumber.from(slippage)
|
||||
);
|
||||
const maxAmountInWithSlippage = maxAmountIn
|
||||
.divUnsafe(slippageDivisor)
|
||||
.round(decimals);
|
||||
|
||||
return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString());
|
||||
}
|
||||
|
||||
getProtocol(): string {
|
||||
return PROTOCOL;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
import { ethers } from "ethers";
|
||||
import JSBI from "jsbi";
|
||||
import { CurrencyAmount, Token, TradeType } from "@uniswap/sdk-core";
|
||||
import { abi as IUniswapV3PoolABI } from "@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json";
|
||||
import {
|
||||
computePoolAddress,
|
||||
FeeAmount,
|
||||
nearestUsableTick,
|
||||
Pool,
|
||||
Route,
|
||||
TickMath,
|
||||
TICK_SPACINGS,
|
||||
Trade,
|
||||
} from "@uniswap/v3-sdk";
|
||||
|
||||
import { UniEvmToken, UniswapRouterCore } from "./uniswap-core";
|
||||
import { UNISWAP_V3_FACTORY_ADDRESS } from "../utils/consts";
|
||||
|
||||
export const PROTOCOL = "UniswapV3";
|
||||
|
||||
export class SingleAmmSwapRouter extends UniswapRouterCore {
|
||||
poolContract: ethers.Contract;
|
||||
pool: Pool;
|
||||
poolFee: FeeAmount;
|
||||
|
||||
constructor(provider: ethers.providers.Provider) {
|
||||
super(provider);
|
||||
|
||||
// set fee amount for our example
|
||||
this.poolFee = FeeAmount.MEDIUM;
|
||||
}
|
||||
|
||||
getPoolFee(): string {
|
||||
return this.poolFee.toString();
|
||||
}
|
||||
|
||||
computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string {
|
||||
return computePoolAddress({
|
||||
factoryAddress: UNISWAP_V3_FACTORY_ADDRESS,
|
||||
fee: this.poolFee,
|
||||
tokenA: tokenIn.getUniToken(),
|
||||
tokenB: tokenOut.getUniToken(),
|
||||
});
|
||||
}
|
||||
|
||||
async computeAndVerifyPoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): Promise<string> {
|
||||
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
|
||||
// verify by attempting to call factory()
|
||||
const poolContract = new ethers.Contract(
|
||||
pairAddress,
|
||||
IUniswapV3PoolABI,
|
||||
this.provider
|
||||
);
|
||||
await poolContract.factory();
|
||||
|
||||
return pairAddress;
|
||||
}
|
||||
|
||||
async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise<Pool> {
|
||||
const poolAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
|
||||
const poolContract = new ethers.Contract(
|
||||
poolAddress,
|
||||
IUniswapV3PoolABI,
|
||||
this.provider
|
||||
);
|
||||
this.poolContract = poolContract;
|
||||
|
||||
const [liquidity, slot] = await Promise.all([
|
||||
poolContract.liquidity(),
|
||||
poolContract.slot0(),
|
||||
]);
|
||||
|
||||
// grab necessary data from slot
|
||||
const sqrtPriceX96 = slot[0];
|
||||
const tick = slot[1];
|
||||
|
||||
// create JSBI version of liquidity numbers
|
||||
const bigLiq = JSBI.BigInt(liquidity);
|
||||
const negBigLiq = JSBI.multiply(bigLiq, JSBI.BigInt(-1));
|
||||
|
||||
const tickConstructorArgs = [
|
||||
{
|
||||
index: nearestUsableTick(
|
||||
TickMath.MIN_TICK,
|
||||
TICK_SPACINGS[this.poolFee]
|
||||
),
|
||||
liquidityNet: bigLiq,
|
||||
liquidityGross: bigLiq,
|
||||
},
|
||||
{
|
||||
index: nearestUsableTick(
|
||||
TickMath.MAX_TICK,
|
||||
TICK_SPACINGS[this.poolFee]
|
||||
),
|
||||
liquidityNet: negBigLiq,
|
||||
liquidityGross: bigLiq,
|
||||
},
|
||||
];
|
||||
|
||||
return new Pool(
|
||||
tokenIn.getUniToken(),
|
||||
tokenOut.getUniToken(),
|
||||
this.poolFee,
|
||||
sqrtPriceX96.toString(), //note the description discrepancy - sqrtPriceX96 and sqrtRatioX96 are interchangable values
|
||||
liquidity,
|
||||
tick,
|
||||
tickConstructorArgs
|
||||
);
|
||||
}
|
||||
|
||||
async computeTradeExactIn(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amount: string
|
||||
): Promise<Trade<Token, Token, TradeType.EXACT_INPUT>> {
|
||||
// create pool
|
||||
const pool = await this.createPool(tokenIn, tokenOut);
|
||||
// let's get that quote
|
||||
const amountIn = tokenIn.computeUnitAmount(amount);
|
||||
|
||||
const route = new Route(
|
||||
[pool],
|
||||
tokenIn.getUniToken(),
|
||||
tokenOut.getUniToken()
|
||||
);
|
||||
return Trade.fromRoute(
|
||||
route,
|
||||
CurrencyAmount.fromRawAmount(tokenIn.getUniToken(), amountIn.toString()),
|
||||
TradeType.EXACT_INPUT
|
||||
);
|
||||
}
|
||||
|
||||
async computeTradeExactOut(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amount: string
|
||||
): Promise<Trade<Token, Token, TradeType.EXACT_OUTPUT>> {
|
||||
// create pool
|
||||
const pool = await this.createPool(tokenIn, tokenOut);
|
||||
// let's get that quote
|
||||
const amountOut = tokenOut.computeUnitAmount(amount);
|
||||
|
||||
const route = new Route(
|
||||
[pool],
|
||||
tokenIn.getUniToken(),
|
||||
tokenOut.getUniToken()
|
||||
);
|
||||
return Trade.fromRoute(
|
||||
route,
|
||||
CurrencyAmount.fromRawAmount(
|
||||
tokenOut.getUniToken(),
|
||||
amountOut.toString()
|
||||
),
|
||||
TradeType.EXACT_OUTPUT
|
||||
);
|
||||
}
|
||||
|
||||
async fetchQuoteAmountOut(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountIn: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
// get the quote
|
||||
const trade = await this.computeTradeExactIn(tokenIn, tokenOut, amountIn);
|
||||
|
||||
const decimals = tokenOut.getDecimals();
|
||||
|
||||
// calculate output amount with slippage
|
||||
const minAmountOut = ethers.FixedNumber.from(
|
||||
trade.outputAmount.toSignificant(decimals)
|
||||
);
|
||||
|
||||
const slippageMultiplier = ethers.FixedNumber.from("1").subUnsafe(
|
||||
ethers.FixedNumber.from(slippage)
|
||||
);
|
||||
const minAmountOutWithSlippage = minAmountOut
|
||||
.mulUnsafe(slippageMultiplier)
|
||||
.round(decimals);
|
||||
|
||||
return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString());
|
||||
}
|
||||
|
||||
async fetchQuoteAmountIn(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
// get the quote
|
||||
const trade = await this.computeTradeExactOut(tokenIn, tokenOut, amountOut);
|
||||
|
||||
const decimals = tokenIn.getDecimals();
|
||||
|
||||
// calculate output amount with slippage
|
||||
const maxAmountIn = ethers.FixedNumber.from(
|
||||
trade.inputAmount.toSignificant(decimals)
|
||||
);
|
||||
|
||||
const slippageDivisor = ethers.FixedNumber.from("1").subUnsafe(
|
||||
ethers.FixedNumber.from(slippage)
|
||||
);
|
||||
const maxAmountInWithSlippage = maxAmountIn
|
||||
.divUnsafe(slippageDivisor)
|
||||
.round(decimals);
|
||||
|
||||
return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString());
|
||||
}
|
||||
|
||||
getProtocol(): string {
|
||||
return PROTOCOL;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,585 @@
|
|||
//@ts-nocheck
|
||||
import { ethers } from "ethers";
|
||||
import { TransactionReceipt } from "@ethersproject/abstract-provider";
|
||||
import {
|
||||
CHAIN_ID_POLYGON as WORMHOLE_CHAIN_ID_POLYGON,
|
||||
CHAIN_ID_ETH as WORMHOLE_CHAIN_ID_ETHEREUM,
|
||||
ChainId,
|
||||
getEmitterAddressEth,
|
||||
hexToUint8Array,
|
||||
nativeToHexString,
|
||||
parseSequenceFromLogEth,
|
||||
getSignedVAAWithRetry,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { grpc } from "@improbable-eng/grpc-web";
|
||||
import { UniEvmToken } from "../route/uniswap-core";
|
||||
import {
|
||||
PROTOCOL_UNISWAP_V2,
|
||||
PROTOCOL_UNISWAP_V3,
|
||||
ExactInCrossParameters,
|
||||
ExactOutCrossParameters,
|
||||
QuoteType,
|
||||
UniswapToUniswapQuoter,
|
||||
} from "../route/cross-quote";
|
||||
import {
|
||||
TOKEN_BRIDGE_ADDRESS_POLYGON,
|
||||
CORE_BRIDGE_ADDRESS_ETHEREUM,
|
||||
CORE_BRIDGE_ADDRESS_POLYGON,
|
||||
TOKEN_BRIDGE_ADDRESS_ETHEREUM,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
POLYGON_NETWORK_CHAIN_ID,
|
||||
ETH_NETWORK_CHAIN_ID,
|
||||
WETH_TOKEN_INFO,
|
||||
WMATIC_TOKEN_INFO,
|
||||
} from "../utils/consts";
|
||||
import { abi as SWAP_CONTRACT_V2_ABI } from "../abi/contracts/CrossChainSwapV2.json";
|
||||
import { abi as SWAP_CONTRACT_V3_ABI } from "../abi/contracts/CrossChainSwapV3.json";
|
||||
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../addresses/goerli";
|
||||
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../addresses/mumbai";
|
||||
|
||||
interface SwapContractParameters {
|
||||
address: string;
|
||||
}
|
||||
|
||||
interface WormholeParameters {
|
||||
chainId: ChainId;
|
||||
coreBridgeAddress: string;
|
||||
tokenBridgeAddress: string;
|
||||
}
|
||||
|
||||
interface ExecutionParameters {
|
||||
crossChainSwap: SwapContractParameters;
|
||||
wormhole: WormholeParameters;
|
||||
}
|
||||
|
||||
const EXECUTION_PARAMETERS_ETHEREUM: ExecutionParameters = {
|
||||
crossChainSwap: {
|
||||
address: CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM,
|
||||
},
|
||||
wormhole: {
|
||||
chainId: WORMHOLE_CHAIN_ID_ETHEREUM,
|
||||
coreBridgeAddress: CORE_BRIDGE_ADDRESS_ETHEREUM,
|
||||
tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_ETHEREUM,
|
||||
},
|
||||
};
|
||||
|
||||
const EXECUTION_PARAMETERS_POLYGON: ExecutionParameters = {
|
||||
crossChainSwap: {
|
||||
address: CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON,
|
||||
},
|
||||
wormhole: {
|
||||
chainId: WORMHOLE_CHAIN_ID_POLYGON,
|
||||
coreBridgeAddress: CORE_BRIDGE_ADDRESS_POLYGON,
|
||||
tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_POLYGON,
|
||||
},
|
||||
};
|
||||
|
||||
const CROSSCHAINSWAP_GAS_PARAMETERS_UNISWAP_V3 = {
|
||||
gasLimit: "550000",
|
||||
maxFeePerGas: "250000000000",
|
||||
maxPriorityFeePerGas: "1690000000",
|
||||
};
|
||||
|
||||
const CROSSCHAINSWAP_GAS_PARAMETERS_UNISWAP_V2 = {
|
||||
gasLimit: "350000",
|
||||
maxFeePerGas: "250000000000",
|
||||
maxPriorityFeePerGas: "1690000000",
|
||||
};
|
||||
|
||||
function makeExecutionParameters(id: number): ExecutionParameters {
|
||||
switch (id) {
|
||||
case ETH_NETWORK_CHAIN_ID: {
|
||||
return EXECUTION_PARAMETERS_ETHEREUM;
|
||||
}
|
||||
case POLYGON_NETWORK_CHAIN_ID: {
|
||||
return EXECUTION_PARAMETERS_POLYGON;
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized chain id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function approveContractTokenSpend(
|
||||
provider: ethers.providers.Provider,
|
||||
signer: ethers.Signer,
|
||||
tokenContract: ethers.Contract,
|
||||
swapContractAddress: string,
|
||||
amount: ethers.BigNumber
|
||||
): Promise<TransactionReceipt> {
|
||||
// build transaction for token spending
|
||||
const unsignedTx = await tokenContract.populateTransaction.approve(
|
||||
swapContractAddress,
|
||||
amount
|
||||
);
|
||||
|
||||
// TODO: pass this in?
|
||||
const address = await signer.getAddress();
|
||||
console.log("address", address);
|
||||
|
||||
console.log("signer", signer);
|
||||
|
||||
// gas calcs
|
||||
const gas_limit = "0x100000";
|
||||
const gasPrice = await signer.getGasPrice();
|
||||
const parsedGasPrice = ethers.utils.hexlify(parseInt(gasPrice.toString()));
|
||||
|
||||
console.log("gettingTranscationCount", provider);
|
||||
|
||||
unsignedTx.nonce = await provider.getTransactionCount(address, "latest");
|
||||
unsignedTx.gasLimit = ethers.BigNumber.from(ethers.utils.hexlify(gas_limit));
|
||||
unsignedTx.gasPrice = ethers.BigNumber.from(parsedGasPrice);
|
||||
console.log("done gettingTranscationCount");
|
||||
|
||||
// sign and send transaction
|
||||
const tx = await signer.sendTransaction(unsignedTx);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
function makeCrossChainSwapV3Contract(
|
||||
contractAddress: string,
|
||||
provider: ethers.providers.Provider
|
||||
): ethers.Contract {
|
||||
return new ethers.Contract(contractAddress, SWAP_CONTRACT_V3_ABI, provider);
|
||||
}
|
||||
|
||||
function makeCrossChainSwapV2Contract(
|
||||
contractAddress: string,
|
||||
provider: ethers.providers.Provider
|
||||
): ethers.Contract {
|
||||
return new ethers.Contract(contractAddress, SWAP_CONTRACT_V2_ABI, provider);
|
||||
}
|
||||
|
||||
function makeCrossChainSwapContract(
|
||||
provider: ethers.providers.Provider,
|
||||
protocol: string,
|
||||
contractAddress: string
|
||||
): ethers.Contract {
|
||||
if (protocol === PROTOCOL_UNISWAP_V2) {
|
||||
return makeCrossChainSwapV2Contract(contractAddress, provider);
|
||||
} else {
|
||||
return makeCrossChainSwapV3Contract(contractAddress, provider);
|
||||
}
|
||||
}
|
||||
|
||||
function addressToBytes32(
|
||||
address: string,
|
||||
wormholeChainId: ChainId
|
||||
): Uint8Array {
|
||||
return hexToUint8Array(nativeToHexString(address, wormholeChainId));
|
||||
}
|
||||
|
||||
async function approveAndSwapExactIn(
|
||||
srcProvider: ethers.providers.Provider,
|
||||
srcWallet: ethers.Signer,
|
||||
srcTokenIn: UniEvmToken,
|
||||
quoteParams: ExactInCrossParameters,
|
||||
srcExecutionParams: ExecutionParameters,
|
||||
dstExecutionParams: ExecutionParameters
|
||||
): Promise<TransactionReceipt> {
|
||||
const swapContractParams = srcExecutionParams.crossChainSwap;
|
||||
|
||||
const protocol = quoteParams.src.protocol;
|
||||
const swapContract = makeCrossChainSwapContract(
|
||||
srcProvider,
|
||||
protocol,
|
||||
swapContractParams.address
|
||||
);
|
||||
const contractWithSigner = swapContract.connect(srcWallet);
|
||||
|
||||
// approve and swap this amount
|
||||
const amountIn = quoteParams.src.amountIn;
|
||||
|
||||
// approve swap contract to spend our tokens
|
||||
console.info("approving contract to spend token in");
|
||||
await approveContractTokenSpend(
|
||||
srcProvider,
|
||||
srcWallet,
|
||||
srcTokenIn.getContract(),
|
||||
swapContract.address,
|
||||
amountIn
|
||||
);
|
||||
|
||||
const address = await srcWallet.getAddress();
|
||||
|
||||
const swapParams = [
|
||||
amountIn,
|
||||
quoteParams.src.minAmountOut,
|
||||
quoteParams.dst.minAmountOut,
|
||||
// srcWallet.address,
|
||||
address,
|
||||
quoteParams.src.deadline,
|
||||
quoteParams.dst.poolFee || quoteParams.src.poolFee,
|
||||
];
|
||||
|
||||
const pathArray = quoteParams.src.path.concat(quoteParams.dst.path);
|
||||
|
||||
const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
|
||||
const dstContractAddress = addressToBytes32(
|
||||
dstExecutionParams.crossChainSwap.address,
|
||||
dstWormholeChainId
|
||||
);
|
||||
const bridgeNonce = 69;
|
||||
|
||||
// do the swap
|
||||
if (protocol === PROTOCOL_UNISWAP_V2) {
|
||||
console.info("swapExactInToV3");
|
||||
const tx = await contractWithSigner.swapExactInToV3(
|
||||
swapParams,
|
||||
pathArray,
|
||||
quoteParams.relayerFee.amount,
|
||||
dstWormholeChainId,
|
||||
dstContractAddress,
|
||||
bridgeNonce,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS_UNISWAP_V2
|
||||
);
|
||||
return tx.wait();
|
||||
} else {
|
||||
console.info("swapExactInToV2");
|
||||
const tx = await contractWithSigner.swapExactInToV2(
|
||||
swapParams,
|
||||
pathArray,
|
||||
quoteParams.relayerFee.amount,
|
||||
dstWormholeChainId,
|
||||
dstContractAddress,
|
||||
bridgeNonce,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS_UNISWAP_V3
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
}
|
||||
|
||||
async function swapExactInFromVaa(
|
||||
dstProvider: ethers.providers.Provider,
|
||||
dstWallet: ethers.Signer,
|
||||
dstExecutionParams: ExecutionParameters,
|
||||
dstProtocol: string,
|
||||
signedVAA: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const swapContractParams = dstExecutionParams.crossChainSwap;
|
||||
|
||||
const swapContract = makeCrossChainSwapContract(
|
||||
dstProvider,
|
||||
dstProtocol,
|
||||
swapContractParams.address
|
||||
);
|
||||
const contractWithSigner = swapContract.connect(dstWallet);
|
||||
|
||||
if (dstProtocol === PROTOCOL_UNISWAP_V3) {
|
||||
console.info("swapExactInFromV2");
|
||||
const tx = await contractWithSigner.swapExactInFromV2(
|
||||
signedVAA,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS_UNISWAP_V3
|
||||
);
|
||||
return tx.wait();
|
||||
} else {
|
||||
console.info("swapExactInFromV3");
|
||||
const tx = await contractWithSigner.swapExactInFromV3(
|
||||
signedVAA,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS_UNISWAP_V2
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
}
|
||||
|
||||
interface CrossChainSwapTokens {
|
||||
srcIn: UniEvmToken;
|
||||
srcOut: UniEvmToken;
|
||||
dstIn: UniEvmToken;
|
||||
dstOut: UniEvmToken;
|
||||
}
|
||||
|
||||
interface VaaSearchParams {
|
||||
sequence: string;
|
||||
emitterAddress: string;
|
||||
}
|
||||
|
||||
export function makeProvider(tokenAddress: string) {
|
||||
switch (tokenAddress) {
|
||||
case WETH_TOKEN_INFO.address: {
|
||||
const url = process.env.REACT_APP_GOERLI_PROVIDER;
|
||||
if (!url) {
|
||||
throw new Error("Could not find REACT_APP_GOERLI_PROVIDER");
|
||||
}
|
||||
return new ethers.providers.StaticJsonRpcProvider(url);
|
||||
}
|
||||
case WMATIC_TOKEN_INFO.address: {
|
||||
const url = process.env.REACT_APP_MUMBAI_PROVIDER;
|
||||
if (!url) {
|
||||
throw new Error("Could not find REACT_APP_MUMBAI_PROVIDER");
|
||||
}
|
||||
return new ethers.providers.StaticJsonRpcProvider(url);
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized token address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class UniswapToUniswapExecutor {
|
||||
// quoting
|
||||
quoter: UniswapToUniswapQuoter;
|
||||
cachedExactInParams: ExactInCrossParameters;
|
||||
cachedExactOutParams: ExactOutCrossParameters;
|
||||
quoteType: QuoteType;
|
||||
tokens: CrossChainSwapTokens;
|
||||
|
||||
// swapping
|
||||
slippage: string;
|
||||
relayerFeeAmount: string;
|
||||
srcExecutionParams: ExecutionParameters;
|
||||
dstExecutionParams: ExecutionParameters;
|
||||
|
||||
// vaa handling
|
||||
transportFactory: grpc.TransportFactory;
|
||||
vaaSearchParams: VaaSearchParams;
|
||||
vaaBytes: Uint8Array;
|
||||
srcReceipt: TransactionReceipt;
|
||||
dstReceipt: TransactionReceipt;
|
||||
|
||||
async initialize(
|
||||
tokenInAddress: string,
|
||||
tokenOutAddress: string
|
||||
): Promise<void> {
|
||||
this.clearState();
|
||||
|
||||
const srcProvider = makeProvider(tokenInAddress);
|
||||
const dstProvider = makeProvider(tokenOutAddress);
|
||||
|
||||
this.quoter = new UniswapToUniswapQuoter(srcProvider, dstProvider);
|
||||
await this.quoter.initialize();
|
||||
|
||||
await this.makeTokens(tokenInAddress, tokenOutAddress);
|
||||
|
||||
// now that we have a chain id for each network, get contract info for each chain
|
||||
this.srcExecutionParams = makeExecutionParameters(
|
||||
this.quoter.srcNetwork.chainId
|
||||
);
|
||||
this.dstExecutionParams = makeExecutionParameters(
|
||||
this.quoter.dstNetwork.chainId
|
||||
);
|
||||
}
|
||||
|
||||
setSlippage(slippage: string): void {
|
||||
this.slippage = slippage;
|
||||
}
|
||||
|
||||
setRelayerFee(amount: string): void {
|
||||
this.relayerFeeAmount = amount;
|
||||
}
|
||||
|
||||
areSwapParametersUndefined(): boolean {
|
||||
return this.slippage === undefined || this.relayerFeeAmount === undefined;
|
||||
}
|
||||
|
||||
setDeadlines(deadline: string): void {
|
||||
this.quoter.setDeadlines(deadline);
|
||||
}
|
||||
|
||||
async makeTokens(
|
||||
tokenInAddress: string,
|
||||
tokenOutAddress: string
|
||||
): Promise<void> {
|
||||
const quoter = this.quoter;
|
||||
|
||||
const [srcTokenIn, srcTokenOut] = await quoter.makeSrcTokens(
|
||||
tokenInAddress
|
||||
);
|
||||
const [dstTokenIn, dstTokenOut] = await quoter.makeDstTokens(
|
||||
tokenOutAddress
|
||||
);
|
||||
|
||||
this.tokens = {
|
||||
srcIn: srcTokenIn,
|
||||
srcOut: srcTokenOut,
|
||||
dstIn: dstTokenIn,
|
||||
dstOut: dstTokenOut,
|
||||
};
|
||||
}
|
||||
|
||||
getTokens(): CrossChainSwapTokens {
|
||||
return this.tokens;
|
||||
}
|
||||
|
||||
async computeAndVerifySrcPoolAddress(): Promise<string> {
|
||||
return this.quoter.computeAndVerifySrcPoolAddress();
|
||||
}
|
||||
|
||||
async computeAndVerifyDstPoolAddress(): Promise<string> {
|
||||
return this.quoter.computeAndVerifyDstPoolAddress();
|
||||
}
|
||||
|
||||
async computeQuoteExactIn(amountIn: string): Promise<ExactInCrossParameters> {
|
||||
if (this.areSwapParametersUndefined()) {
|
||||
throw Error("undefined swap parameters");
|
||||
}
|
||||
|
||||
this.clearCachedParams();
|
||||
|
||||
this.cachedExactInParams = await this.quoter.computeExactInParameters(
|
||||
amountIn,
|
||||
this.slippage,
|
||||
this.relayerFeeAmount
|
||||
);
|
||||
this.quoteType = QuoteType.ExactIn;
|
||||
return this.cachedExactInParams;
|
||||
}
|
||||
|
||||
async computeQuoteExactOut(
|
||||
amountOut: string
|
||||
): Promise<ExactOutCrossParameters> {
|
||||
if (this.areSwapParametersUndefined()) {
|
||||
throw Error("undefined swap parameters");
|
||||
}
|
||||
|
||||
this.clearCachedParams();
|
||||
|
||||
this.cachedExactOutParams = await this.quoter.computeExactOutParameters(
|
||||
amountOut,
|
||||
this.slippage,
|
||||
this.relayerFeeAmount
|
||||
);
|
||||
this.quoteType = QuoteType.ExactOut;
|
||||
return this.cachedExactOutParams;
|
||||
}
|
||||
|
||||
clearCachedParams(): void {
|
||||
this.cachedExactInParams = undefined;
|
||||
this.cachedExactOutParams = undefined;
|
||||
this.quoteType = undefined;
|
||||
}
|
||||
|
||||
getSrcProvider(): ethers.providers.Provider {
|
||||
return this.quoter.srcProvider;
|
||||
}
|
||||
|
||||
getDstProvider(): ethers.providers.Provider {
|
||||
return this.quoter.dstProvider;
|
||||
}
|
||||
|
||||
async approveAndSwapExactIn(
|
||||
wallet: ethers.Signer
|
||||
): Promise<TransactionReceipt> {
|
||||
return approveAndSwapExactIn(
|
||||
this.getSrcProvider(),
|
||||
wallet,
|
||||
this.tokens.srcIn,
|
||||
this.cachedExactInParams,
|
||||
this.srcExecutionParams,
|
||||
this.dstExecutionParams
|
||||
);
|
||||
}
|
||||
|
||||
async approveAndSwapExactOut(
|
||||
wallet: ethers.Wallet
|
||||
): Promise<TransactionReceipt> {
|
||||
throw Error("ExactOut not supported yet");
|
||||
}
|
||||
|
||||
async approveAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> {
|
||||
const quoteType = this.quoteType;
|
||||
|
||||
if (quoteType === QuoteType.ExactIn) {
|
||||
this.srcReceipt = await this.approveAndSwapExactIn(wallet);
|
||||
} else if (quoteType === QuoteType.ExactOut) {
|
||||
this.srcReceipt = await this.approveAndSwapExactOut(wallet);
|
||||
} else {
|
||||
throw Error("no quote found");
|
||||
}
|
||||
|
||||
this.fetchAndSetEmitterAndSequence();
|
||||
return this.srcReceipt;
|
||||
}
|
||||
|
||||
fetchAndSetEmitterAndSequence(): void {
|
||||
const receipt = this.srcReceipt;
|
||||
if (receipt === undefined) {
|
||||
throw Error("no swap receipt found");
|
||||
}
|
||||
|
||||
const wormholeParams = this.srcExecutionParams.wormhole;
|
||||
|
||||
this.vaaSearchParams = {
|
||||
sequence: parseSequenceFromLogEth(
|
||||
receipt,
|
||||
wormholeParams.coreBridgeAddress
|
||||
),
|
||||
emitterAddress: getEmitterAddressEth(wormholeParams.tokenBridgeAddress),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
async fetchSignedVaaFromSwap(): Promise<void> {
|
||||
if (this.vaaBytes !== undefined) {
|
||||
// console.warn("vaaBytes are defined");
|
||||
return;
|
||||
}
|
||||
const vaaSearchParams = this.vaaSearchParams;
|
||||
if (vaaSearchParams === undefined) {
|
||||
throw Error("no vaa search params found");
|
||||
}
|
||||
const sequence = vaaSearchParams.sequence;
|
||||
const emitterAddress = vaaSearchParams.emitterAddress;
|
||||
console.info(`sequence: ${sequence}, emitterAddress: ${emitterAddress}`);
|
||||
// wait for VAA to be signed
|
||||
const vaaResponse = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
this.srcExecutionParams.wormhole.chainId,
|
||||
vaaSearchParams.emitterAddress,
|
||||
vaaSearchParams.sequence
|
||||
// TODO: this is where we passed the transport
|
||||
);
|
||||
// grab vaaBytes
|
||||
this.vaaBytes = vaaResponse.vaaBytes;
|
||||
return;
|
||||
}
|
||||
|
||||
async fetchVaaAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> {
|
||||
await this.fetchSignedVaaFromSwap();
|
||||
|
||||
const quoteType = this.quoteType;
|
||||
|
||||
if (quoteType === QuoteType.ExactIn) {
|
||||
this.dstReceipt = await this.swapExactInFromVaa(wallet);
|
||||
} else if (quoteType === QuoteType.ExactOut) {
|
||||
this.dstReceipt = await this.swapExactOutFromVaa(wallet);
|
||||
} else {
|
||||
throw Error("no quote found");
|
||||
}
|
||||
|
||||
// console.info("clearing state");
|
||||
this.clearState();
|
||||
|
||||
return this.dstReceipt;
|
||||
}
|
||||
|
||||
async swapExactInFromVaa(wallet: ethers.Signer): Promise<TransactionReceipt> {
|
||||
return swapExactInFromVaa(
|
||||
this.getDstProvider(),
|
||||
wallet,
|
||||
this.dstExecutionParams,
|
||||
this.cachedExactInParams.dst.protocol,
|
||||
this.vaaBytes
|
||||
);
|
||||
}
|
||||
|
||||
async swapExactOutFromVaa(
|
||||
wallet: ethers.Wallet
|
||||
): Promise<TransactionReceipt> {
|
||||
throw Error("ExactOut not supported yet");
|
||||
}
|
||||
|
||||
clearState(): void {
|
||||
// TODO: after the whole swap, clear the state of everything
|
||||
this.vaaBytes = undefined;
|
||||
|
||||
// clear src receipt only
|
||||
this.srcReceipt = undefined;
|
||||
|
||||
// clear params
|
||||
this.cachedExactInParams = undefined;
|
||||
this.cachedExactOutParams = undefined;
|
||||
this.quoteType = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_POLYGON,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import ethIcon from "../icons/eth.svg";
|
||||
import polygonIcon from "../icons/polygon.svg";
|
||||
|
||||
export interface TokenInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
address: string;
|
||||
chainId: ChainId;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
export const WMATIC_TOKEN_INFO: TokenInfo = {
|
||||
id: "WMATIC",
|
||||
name: "WMATIC",
|
||||
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889",
|
||||
chainId: CHAIN_ID_POLYGON,
|
||||
logo: polygonIcon,
|
||||
};
|
||||
|
||||
export const WETH_TOKEN_INFO: TokenInfo = {
|
||||
id: "WETH",
|
||||
name: "WETH",
|
||||
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
||||
chainId: CHAIN_ID_ETH,
|
||||
logo: ethIcon,
|
||||
};
|
||||
|
||||
export const TOKEN_INFOS = [WMATIC_TOKEN_INFO, WETH_TOKEN_INFO];
|
||||
|
||||
export const ETH_NETWORK_CHAIN_ID = 5;
|
||||
|
||||
export const POLYGON_NETWORK_CHAIN_ID = 80001;
|
||||
|
||||
export const getEvmChainId = (chainId: ChainId) =>
|
||||
chainId === CHAIN_ID_ETH
|
||||
? ETH_NETWORK_CHAIN_ID
|
||||
: chainId === CHAIN_ID_POLYGON
|
||||
? POLYGON_NETWORK_CHAIN_ID
|
||||
: undefined;
|
||||
|
||||
export const RELAYER_FEE_UST = "0.0001";
|
||||
|
||||
export const WORMHOLE_RPC_HOSTS = [
|
||||
"https://wormhole-v2-testnet-api.certus.one",
|
||||
];
|
||||
|
||||
export const CORE_BRIDGE_ADDRESS_ETHEREUM =
|
||||
"0x706abc4E45D419950511e474C7B9Ed348A4a716c";
|
||||
|
||||
export const CORE_BRIDGE_ADDRESS_POLYGON =
|
||||
"0x0CBE91CF822c73C2315FB05100C2F714765d5c20";
|
||||
|
||||
export const TOKEN_BRIDGE_ADDRESS_ETHEREUM =
|
||||
"0xF890982f9310df57d00f659cf4fd87e65adEd8d7";
|
||||
|
||||
export const TOKEN_BRIDGE_ADDRESS_POLYGON =
|
||||
"0x377D55a7928c046E18eEbb61977e714d2a76472a";
|
||||
|
||||
export const QUICKSWAP_FACTORY_ADDRESS =
|
||||
"0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32";
|
||||
|
||||
export const UNISWAP_V3_FACTORY_ADDRESS =
|
||||
"0x1F98431c8aD98523631AE4a59f267346ea31F984";
|
||||
|
||||
export const APPROVAL_GAS_LIMIT = "100000";
|
|
@ -0,0 +1,11 @@
|
|||
const MM_ERR_WITH_INFO_START =
|
||||
"VM Exception while processing transaction: revert ";
|
||||
const parseError = (e: any) =>
|
||||
e?.data?.message?.startsWith(MM_ERR_WITH_INFO_START)
|
||||
? e.data.message.replace(MM_ERR_WITH_INFO_START, "")
|
||||
: e?.response?.data?.error // terra error
|
||||
? e.response.data.error
|
||||
: e?.message
|
||||
? e.message
|
||||
: "An unknown error occurred";
|
||||
export default parseError;
|
|
@ -0,0 +1,335 @@
|
|||
import {
|
||||
Container,
|
||||
makeStyles,
|
||||
Paper,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { ChainId } from "@certusone/wormhole-sdk";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import ButtonWithLoader from "../components/ButtonWithLoader";
|
||||
import EthereumSignerKey from "../components/EthereumSignerKey";
|
||||
import TokenSelect from "../components/TokenSelect";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import {
|
||||
getEvmChainId,
|
||||
RELAYER_FEE_UST,
|
||||
TOKEN_INFOS,
|
||||
WETH_TOKEN_INFO,
|
||||
WMATIC_TOKEN_INFO,
|
||||
} from "../utils/consts";
|
||||
import { COLORS } from "../muiTheme";
|
||||
import Wormhole from "../icons/wormhole-network.svg";
|
||||
import { UniswapToUniswapExecutor } from "../swapper/swapper";
|
||||
import { Web3Provider } from "@ethersproject/providers";
|
||||
import { hexlify, hexStripZeros } from "ethers/lib/utils";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { Alert } from "@material-ui/lab";
|
||||
import parseError from "../utils/parseError";
|
||||
import Settings from "../components/Settings";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
bg: {
|
||||
background:
|
||||
"linear-gradient(160deg, rgba(69,74,117,.1) 0%, rgba(138,146,178,.1) 33%, rgba(69,74,117,.1) 66%, rgba(98,104,143,.1) 100%), linear-gradient(45deg, rgba(153,69,255,.1) 0%, rgba(121,98,231,.1) 20%, rgba(0,209,140,.1) 100%)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: "100vh",
|
||||
},
|
||||
centeredContainer: {
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
},
|
||||
mainPaper: {
|
||||
padding: "2rem",
|
||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||
},
|
||||
numberField: {
|
||||
flexGrow: 1,
|
||||
"& > * > .MuiInputBase-input": {
|
||||
textAlign: "center",
|
||||
height: "100%",
|
||||
flexGrow: "1",
|
||||
fontSize: "3rem",
|
||||
fontFamily: "Roboto Mono, monospace",
|
||||
caretShape: "block",
|
||||
"&::-webkit-outer-spin-button, &::-webkit-inner-spin-button": {
|
||||
"-webkit-appearance": "none",
|
||||
"-moz-appearance": "none",
|
||||
margin: 0,
|
||||
},
|
||||
"&[type=number]": {
|
||||
"-webkit-appearance": "textfield",
|
||||
"-moz-appearance": "textfield",
|
||||
},
|
||||
},
|
||||
"& > * > input::-webkit-inner-spin-button": {
|
||||
webkitAppearance: "none",
|
||||
margin: "0",
|
||||
},
|
||||
},
|
||||
gradientButton: {
|
||||
backgroundImage: `linear-gradient(45deg, ${COLORS.blue} 0%, ${COLORS.nearBlack}20 50%, ${COLORS.blue}30 62%, ${COLORS.nearBlack}50 120%)`,
|
||||
transition: "0.75s",
|
||||
backgroundSize: "200% auto",
|
||||
boxShadow: "0 0 20px #222",
|
||||
"&:hover": {
|
||||
backgroundPosition:
|
||||
"right center" /* change the direction of the change here */,
|
||||
},
|
||||
width: "100%",
|
||||
height: "3rem",
|
||||
marginTop: "1rem",
|
||||
},
|
||||
disabled: {
|
||||
background: COLORS.gray,
|
||||
},
|
||||
spacer: {
|
||||
height: "1rem",
|
||||
},
|
||||
titleBar: {
|
||||
marginTop: "10rem",
|
||||
"& > *": {
|
||||
margin: ".5rem",
|
||||
alignSelf: "flex-end",
|
||||
},
|
||||
},
|
||||
tokenSelectWrapper: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
wormholeIcon: {
|
||||
height: 60,
|
||||
filter: "contrast(0)",
|
||||
transition: "filter 0.5s",
|
||||
"&:hover": {
|
||||
filter: "contrast(1)",
|
||||
},
|
||||
verticalAlign: "middle",
|
||||
margin: "1rem",
|
||||
display: "inline-block",
|
||||
},
|
||||
}));
|
||||
|
||||
const switchProviderNetwork = async (
|
||||
provider: Web3Provider,
|
||||
chainId: ChainId
|
||||
) => {
|
||||
const evmChainId = getEvmChainId(chainId);
|
||||
if (evmChainId === undefined) {
|
||||
throw new Error("Unknown chainId");
|
||||
}
|
||||
await provider.send("wallet_switchEthereumChain", [
|
||||
{ chainId: hexStripZeros(hexlify(evmChainId)) },
|
||||
]);
|
||||
const network = await provider.getNetwork();
|
||||
if (network.chainId !== evmChainId) {
|
||||
throw new Error("Could not switch network");
|
||||
}
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
const classes = useStyles();
|
||||
const [sourceTokenInfo, setSourceTokenInfo] = useState(WMATIC_TOKEN_INFO);
|
||||
const [targetTokenInfo, setTargetTokenInfo] = useState(WETH_TOKEN_INFO);
|
||||
const [amountIn, setAmountIn] = useState("0.0");
|
||||
const [amountOut, setAmountOut] = useState("0.0");
|
||||
const [deadline, setDeadline] = useState("30");
|
||||
const [slippage, setSlippage] = useState("1");
|
||||
const [executor, setExecutor] = useState<UniswapToUniswapExecutor | null>(
|
||||
null
|
||||
);
|
||||
const [isSwapping, setIsSwapping] = useState(false);
|
||||
const [isComputingQuote, setIsComputingQuote] = useState(false);
|
||||
const [hasQuote, setHasQuote] = useState(false);
|
||||
const { provider, signer } = useEthereumProvider();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const computeQuote = useCallback(() => {
|
||||
(async () => {
|
||||
setHasQuote(false);
|
||||
setIsComputingQuote(true);
|
||||
try {
|
||||
if (
|
||||
parseFloat(amountIn) > 0 &&
|
||||
!isNaN(parseFloat(deadline)) &&
|
||||
!isNaN(parseFloat(slippage))
|
||||
) {
|
||||
setAmountOut("0.0");
|
||||
const executor = new UniswapToUniswapExecutor();
|
||||
await executor.initialize(
|
||||
sourceTokenInfo.address,
|
||||
targetTokenInfo.address
|
||||
);
|
||||
await executor.computeAndVerifySrcPoolAddress().catch((e) => {
|
||||
throw new Error("failed to verify source pool address");
|
||||
});
|
||||
await executor.computeAndVerifyDstPoolAddress().catch((e) => {
|
||||
throw new Error("failed to verify dest pool address");
|
||||
});
|
||||
executor.setDeadlines((parseFloat(deadline) * 60).toString());
|
||||
executor.setSlippage((parseFloat(slippage) / 100).toString());
|
||||
executor.setRelayerFee(RELAYER_FEE_UST);
|
||||
const quote = await executor.computeQuoteExactIn(amountIn);
|
||||
setExecutor(executor);
|
||||
setAmountOut(
|
||||
parseFloat(
|
||||
executor.tokens.dstOut.formatAmount(quote.dst.minAmountOut)
|
||||
).toFixed(8)
|
||||
);
|
||||
setHasQuote(true);
|
||||
} else {
|
||||
setAmountOut("0.0");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(null, {
|
||||
content: <Alert severity="error">{parseError(e)}</Alert>,
|
||||
});
|
||||
}
|
||||
setIsComputingQuote(false);
|
||||
})();
|
||||
}, [
|
||||
sourceTokenInfo,
|
||||
targetTokenInfo,
|
||||
amountIn,
|
||||
deadline,
|
||||
slippage,
|
||||
enqueueSnackbar,
|
||||
]);
|
||||
|
||||
const debouncedComputeQuote = useDebouncedCallback(computeQuote, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
debouncedComputeQuote();
|
||||
}, [
|
||||
sourceTokenInfo,
|
||||
targetTokenInfo,
|
||||
amountIn,
|
||||
deadline,
|
||||
slippage,
|
||||
debouncedComputeQuote,
|
||||
]);
|
||||
|
||||
const handleAmountChange = useCallback((event) => {
|
||||
setAmountIn(event.target.value);
|
||||
}, []);
|
||||
|
||||
const handleSlippageChange = useCallback((slippage) => {
|
||||
setSlippage(slippage);
|
||||
}, []);
|
||||
|
||||
const handleDeadlineChange = useCallback((deadline) => {
|
||||
setDeadline(deadline);
|
||||
}, []);
|
||||
|
||||
const handleSourceChange = useCallback((event) => {
|
||||
if (event.target.value === WMATIC_TOKEN_INFO.name) {
|
||||
setSourceTokenInfo(WMATIC_TOKEN_INFO);
|
||||
setTargetTokenInfo(WETH_TOKEN_INFO);
|
||||
} else {
|
||||
setSourceTokenInfo(WETH_TOKEN_INFO);
|
||||
setTargetTokenInfo(WMATIC_TOKEN_INFO);
|
||||
}
|
||||
setAmountIn("0.0");
|
||||
setAmountOut("0.0");
|
||||
}, []);
|
||||
|
||||
const handleSwapClick = useCallback(async () => {
|
||||
if (provider && signer && executor) {
|
||||
try {
|
||||
setIsSwapping(true);
|
||||
await switchProviderNetwork(provider, sourceTokenInfo.chainId);
|
||||
const sourceReceipt = await executor.approveAndSwap(signer);
|
||||
console.info(`src transaction: ${sourceReceipt.transactionHash}`);
|
||||
await switchProviderNetwork(provider, targetTokenInfo.chainId);
|
||||
const targetReceipt = await executor.fetchVaaAndSwap(signer);
|
||||
console.info(`dst transaction: ${targetReceipt.transactionHash}`);
|
||||
enqueueSnackbar(null, {
|
||||
content: <Alert severity="success">Success!</Alert>,
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
enqueueSnackbar(null, {
|
||||
content: <Alert severity="error">{parseError(e)}</Alert>,
|
||||
});
|
||||
}
|
||||
setIsSwapping(false);
|
||||
}
|
||||
}, [
|
||||
provider,
|
||||
signer,
|
||||
executor,
|
||||
enqueueSnackbar,
|
||||
sourceTokenInfo,
|
||||
targetTokenInfo,
|
||||
]);
|
||||
|
||||
const readyToSwap = provider && signer && hasQuote;
|
||||
|
||||
return (
|
||||
<div className={classes.bg}>
|
||||
<Container className={classes.centeredContainer} maxWidth="sm">
|
||||
<div className={classes.titleBar}></div>
|
||||
<Typography variant="h4" color="textSecondary">
|
||||
Cross Chain Swap Demo
|
||||
</Typography>
|
||||
<div className={classes.spacer} />
|
||||
<Paper className={classes.mainPaper}>
|
||||
<Settings
|
||||
disabled={isSwapping || isComputingQuote}
|
||||
slippage={slippage}
|
||||
deadline={deadline}
|
||||
onSlippageChange={handleSlippageChange}
|
||||
onDeadlineChange={handleDeadlineChange}
|
||||
/>
|
||||
<TokenSelect
|
||||
tokens={TOKEN_INFOS}
|
||||
value={sourceTokenInfo.name}
|
||||
onChange={handleSourceChange}
|
||||
disabled={isSwapping || isComputingQuote}
|
||||
></TokenSelect>
|
||||
<Typography variant="subtitle1">Send</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
value={amountIn}
|
||||
disabled={isSwapping || isComputingQuote}
|
||||
InputProps={{ disableUnderline: true }}
|
||||
className={classes.numberField}
|
||||
onChange={handleAmountChange}
|
||||
></TextField>
|
||||
<div className={classes.spacer} />
|
||||
<TokenSelect
|
||||
tokens={TOKEN_INFOS}
|
||||
value={targetTokenInfo.name}
|
||||
onChange={() => {}}
|
||||
disabled={true}
|
||||
></TokenSelect>
|
||||
<Typography variant="subtitle1">Receive (estimated)</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
value={amountOut}
|
||||
autoFocus={true}
|
||||
InputProps={{ disableUnderline: true }}
|
||||
className={classes.numberField}
|
||||
inputProps={{ readOnly: true }}
|
||||
></TextField>
|
||||
{!isSwapping && <EthereumSignerKey />}
|
||||
<ButtonWithLoader
|
||||
disabled={!readyToSwap || isSwapping}
|
||||
showLoader={isSwapping}
|
||||
onClick={handleSwapClick}
|
||||
>
|
||||
Swap
|
||||
</ButtonWithLoader>
|
||||
</Paper>
|
||||
<div className={classes.spacer} />
|
||||
<Typography variant="subtitle1" color="textSecondary">
|
||||
{"powered by wormhole"}
|
||||
</Typography>
|
||||
<img src={Wormhole} alt="Wormhole" className={classes.wormholeIcon} />
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
Loading…
Reference in New Issue