Resolve merge conflict
This commit is contained in:
commit
20cff589b0
|
@ -6,7 +6,7 @@ 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).
|
||||
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 any combination of ETH (Goerli testnet), AVAX (Fuji testnet), MATIC (Mumbai testnet) and BNB (BSC testnet). We wrote example smart contracts to interact with Uniswap V3 and Uniswap V2 forks. 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.
|
||||
|
||||
|
@ -49,8 +49,8 @@ cp .env.sample .env
|
|||
Then deploy the example contracts:
|
||||
|
||||
```
|
||||
./deploy_to_goerli.sh
|
||||
./deploy_to_mumbai.sh
|
||||
./deploy_v2.sh
|
||||
./deploy_v3.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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
GOERLI_PROVIDER=https://goerli.infura.io/v3/YOUR-PROJECT-ID
|
||||
MUMBAI_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID
|
||||
BSC_PROVIDER=https://data-seed-prebsc-1-s1.binance.org:8545
|
||||
FUJI_PROVIDER=https://api.avax-test.network/ext/bc/C/rpc
|
||||
GOERLI_PROVIDER="https://goerli.infura.io/v3/YOUR-PROJECT-ID"
|
||||
MUMBAI_PROVIDER="https://polygon-mumbai.infura.io/v3/YOUR-PROJECT-ID"
|
||||
BSC_PROVIDER="https://data-seed-prebsc-1-s1.binance.org:8545"
|
||||
FUJI_PROVIDER="https://api.avax-test.network/ext/bc/C/rpc"
|
||||
ETH_PRIVATE_KEY=
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
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/avalanche',
|
||||
/**
|
||||
* 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.
|
||||
fuji: {
|
||||
provider: () => new HDWalletProvider(
|
||||
process.env.ETH_PRIVATE_KEY,
|
||||
process.env.FUJI_PROVIDER
|
||||
),
|
||||
network_id: 43113,
|
||||
skipDryRun: true
|
||||
},
|
||||
// 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,124 @@
|
|||
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/bsc',
|
||||
/**
|
||||
* 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.
|
||||
bsc: {
|
||||
provider: () => new HDWalletProvider(
|
||||
process.env.ETH_PRIVATE_KEY,
|
||||
process.env.BSC_PROVIDER
|
||||
),
|
||||
network_id: 97,
|
||||
skipDryRun: true,
|
||||
timeoutBlocks: 200
|
||||
},
|
||||
// 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'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
};
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
npx truffle compile --config truffle-config.ethereum.js
|
||||
npx truffle compile --config truffle-config.polygon.js
|
||||
npx truffle compile --config cfg/truffle-config.ethereum.js
|
||||
npx truffle compile --config cfg/truffle-config.polygon.js
|
||||
|
||||
CONTRACTS="../react/src/abi/contracts"
|
||||
|
||||
|
|
|
@ -44,7 +44,8 @@ contract CrossChainSwapV2 {
|
|||
uint8 public immutable typeExactOut = 2;
|
||||
uint8 public immutable typeNativeSwap = 1;
|
||||
uint8 public immutable typeTokenSwap = 2;
|
||||
uint16 public immutable expectedVaaLength = 262;
|
||||
uint16 public immutable expectedVaaLength = 274;
|
||||
uint8 public immutable terraChainId = 3;
|
||||
IUniswapV2Router02 public immutable swapRouter;
|
||||
address public immutable feeTokenAddress;
|
||||
address public immutable tokenBridgeAddress;
|
||||
|
@ -385,17 +386,26 @@ contract CrossChainSwapV2 {
|
|||
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,
|
||||
typeNativeSwap
|
||||
);
|
||||
// create payload variable
|
||||
bytes memory payload;
|
||||
|
||||
// UST is native to Terra - no need for swap instructions
|
||||
if (targetChainId == terraChainId) {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetChainRecipient
|
||||
);
|
||||
} else {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetAmountOutMinimum,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactIn,
|
||||
typeNativeSwap
|
||||
);
|
||||
}
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(
|
||||
|
@ -761,17 +771,26 @@ contract CrossChainSwapV2 {
|
|||
typeNativeSwap
|
||||
);
|
||||
|
||||
// encode payload for second swap
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOut,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactOut,
|
||||
typeNativeSwap
|
||||
);
|
||||
// create payload variable
|
||||
bytes memory payload;
|
||||
|
||||
// UST is native to Terra - no need for swap instructions
|
||||
if (targetChainId == terraChainId) {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetChainRecipient
|
||||
);
|
||||
} else {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetAmountOut,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactOut,
|
||||
typeNativeSwap
|
||||
);
|
||||
}
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(
|
||||
|
|
|
@ -48,7 +48,8 @@ contract CrossChainSwapV3 {
|
|||
uint8 public immutable typeExactOut = 2;
|
||||
uint8 public immutable typeNativeSwap = 1;
|
||||
uint8 public immutable typeTokenSwap = 2;
|
||||
uint16 public immutable expectedVaaLength = 262;
|
||||
uint16 public immutable expectedVaaLength = 274;
|
||||
uint8 public immutable terraChainId = 3;
|
||||
IUniswapRouter public immutable swapRouter;
|
||||
address public immutable feeTokenAddress;
|
||||
address public immutable tokenBridgeAddress;
|
||||
|
@ -403,17 +404,26 @@ contract CrossChainSwapV3 {
|
|||
typeNativeSwap
|
||||
);
|
||||
|
||||
// encode payload for second swap
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOutMinimum,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactIn,
|
||||
typeNativeSwap
|
||||
);
|
||||
// create payload variable
|
||||
bytes memory payload;
|
||||
|
||||
// UST is native to Terra - no need for swap instructions
|
||||
if (targetChainId == terraChainId) {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetChainRecipient
|
||||
);
|
||||
} else {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetAmountOutMinimum,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactIn,
|
||||
typeNativeSwap
|
||||
);
|
||||
}
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(
|
||||
|
@ -788,17 +798,26 @@ contract CrossChainSwapV3 {
|
|||
typeNativeSwap
|
||||
);
|
||||
|
||||
// encode payload for second swap
|
||||
bytes memory payload = abi.encodePacked(
|
||||
swapParams.targetAmountOut,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactOut,
|
||||
typeNativeSwap
|
||||
);
|
||||
// create payload variable
|
||||
bytes memory payload;
|
||||
|
||||
// UST is native to Terra - no need for swap instructions
|
||||
if (targetChainId == terraChainId) {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetChainRecipient
|
||||
);
|
||||
} else {
|
||||
payload = abi.encodePacked(
|
||||
swapParams.targetAmountOut,
|
||||
swapParams.targetChainRecipient,
|
||||
path[2],
|
||||
path[3],
|
||||
swapParams.deadline,
|
||||
swapParams.poolFee,
|
||||
typeExactOut,
|
||||
typeNativeSwap
|
||||
);
|
||||
}
|
||||
|
||||
// approve token bridge to spend feeTokens (UST)
|
||||
TransferHelper.safeApprove(
|
||||
|
|
|
@ -18,7 +18,7 @@ library SwapHelper {
|
|||
uint256 amountIn;
|
||||
uint256 amountOutMinimum;
|
||||
uint256 targetAmountOutMinimum;
|
||||
address targetChainRecipient;
|
||||
bytes32 targetChainRecipient;
|
||||
uint256 deadline;
|
||||
uint24 poolFee;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ library SwapHelper {
|
|||
uint256 amountOut;
|
||||
uint256 amountInMaximum;
|
||||
uint256 targetAmountOut;
|
||||
address targetChainRecipient;
|
||||
bytes32 targetChainRecipient;
|
||||
uint256 deadline;
|
||||
uint24 poolFee;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ library SwapHelper {
|
|||
index += 32;
|
||||
|
||||
decoded.estimatedAmount = encodedVm.payload.toUint256(index);
|
||||
index += 32;
|
||||
index += 44;
|
||||
|
||||
decoded.recipientAddress = encodedVm.payload.toAddress(index);
|
||||
index += 20;
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/bash
|
||||
npx truffle migrate --config truffle-config.polygon.js --network mumbai --reset
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
npx truffle migrate --config cfg/truffle-config.avalanche.js --network fuji --reset
|
||||
npx truffle migrate --config cfg/truffle-config.bsc.js --network bsc --reset
|
||||
npx truffle migrate --config cfg/truffle-config.polygon.js --network mumbai --reset
|
|
@ -1,2 +1,4 @@
|
|||
#!/bin/bash
|
||||
npx truffle migrate --config truffle-config.ethereum.js --network goerli --reset
|
||||
set -euo pipefail
|
||||
|
||||
npx truffle migrate --config truffle-config.ethereum.js --network goerli --reset
|
|
@ -0,0 +1,5 @@
|
|||
const Migrations = artifacts.require("Migrations");
|
||||
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
const fsp = require("fs/promises");
|
||||
|
||||
const CrossChainSwapV2 = artifacts.require("CrossChainSwapV2");
|
||||
const SwapHelper = artifacts.require("SwapHelper");
|
||||
|
||||
const scriptsAddressPath = "../react/src/addresses";
|
||||
|
||||
module.exports = async function(deployer, network) {
|
||||
const routerAddress = "0x7e3411b04766089cfaa52db688855356a12f05d1"; // hurricaneswap
|
||||
const feeTokenAddress = "0xe09ed38e5cd1014444846f62376ac88c5232cde9"; // wUST
|
||||
const tokenBridgeAddress = "0x61E44E506Ca5659E6c0bba9b678586fA2d729756";
|
||||
const wrappedAvaxAddress = "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV2,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
wrappedAvaxAddress
|
||||
);
|
||||
|
||||
// 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);
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
const Migrations = artifacts.require("Migrations");
|
||||
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Migrations);
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
const fsp = require("fs/promises");
|
||||
|
||||
const CrossChainSwapV2 = artifacts.require("CrossChainSwapV2");
|
||||
const SwapHelper = artifacts.require("SwapHelper");
|
||||
|
||||
const scriptsAddressPath = "../react/src/addresses";
|
||||
|
||||
module.exports = async function(deployer, network) {
|
||||
const routerAddress = "0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3"; // pancakeswap
|
||||
const feeTokenAddress = "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1"; // wUST
|
||||
const tokenBridgeAddress = "0x9dcF9D205C9De35334D646BeE44b2D2859712A09";
|
||||
const wrappedBnbAddress = "0xae13d989dac2f0debff460ac112a837c89baa7cd";
|
||||
|
||||
await deployer.deploy(SwapHelper);
|
||||
await deployer.link(SwapHelper, CrossChainSwapV2);
|
||||
await deployer.deploy(
|
||||
CrossChainSwapV2,
|
||||
routerAddress,
|
||||
feeTokenAddress,
|
||||
tokenBridgeAddress,
|
||||
wrappedBnbAddress
|
||||
);
|
||||
|
||||
// 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);
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
.env
|
||||
node_modules/
|
||||
scripts/*.js
|
||||
scripts/src/*.js
|
||||
src
|
||||
package-lock.json
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"homepage": "https://certusone.github.io/wormhole-nativeswap-example",
|
||||
"name": "NativeSwap",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm_js_in_src.sh"
|
||||
},
|
||||
"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",
|
||||
"@terra-money/terra.js": "^2.0.14",
|
||||
"@terra-money/wallet-provider": "^2.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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^6.3.0",
|
||||
"gh-pages": "^3.2.3",
|
||||
"wasm-loader": "^1.3.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
rm src/addresses/*.js
|
||||
rm src/route/*.js
|
||||
rm src/swapper/*.js
|
||||
rm src/utils/*.js
|
|
@ -0,0 +1,36 @@
|
|||
import { ethers } from "ethers";
|
||||
|
||||
import {
|
||||
ETH_TOKEN_INFO,
|
||||
MATIC_TOKEN_INFO,
|
||||
AVAX_TOKEN_INFO,
|
||||
BNB_TOKEN_INFO,
|
||||
} from "../../src/utils/consts";
|
||||
|
||||
export function makeProvider(tokenAddress: string) {
|
||||
switch (tokenAddress) {
|
||||
case ETH_TOKEN_INFO.address: {
|
||||
return new ethers.providers.StaticJsonRpcProvider(
|
||||
process.env.GOERLI_PROVIDER
|
||||
);
|
||||
}
|
||||
case MATIC_TOKEN_INFO.address: {
|
||||
return new ethers.providers.StaticJsonRpcProvider(
|
||||
process.env.MUMBAI_PROVIDER
|
||||
);
|
||||
}
|
||||
case AVAX_TOKEN_INFO.address: {
|
||||
return new ethers.providers.StaticJsonRpcProvider(
|
||||
process.env.FUJI_PROVIDER
|
||||
);
|
||||
}
|
||||
case BNB_TOKEN_INFO.address: {
|
||||
return new ethers.providers.StaticJsonRpcProvider(
|
||||
process.env.BSC_PROVIDER
|
||||
);
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized token address");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
root=$(dirname $0)
|
||||
script="${root}/swap-with-vaa.js"
|
||||
|
||||
echo `which node`
|
||||
|
||||
node $script --in ETH --out MATIC
|
||||
node $script --in ETH --out BNB
|
||||
node $script --in ETH --out AVAX
|
||||
node $script --in MATIC --out BNB
|
||||
node $script --in MATIC --out AVAX
|
||||
node $script --in BNB --out MATIC
|
||||
|
||||
echo "done"
|
|
@ -0,0 +1,470 @@
|
|||
import yargs from "yargs";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
|
||||
import {
|
||||
ExactInCrossParameters,
|
||||
ExactOutCrossParameters,
|
||||
UniswapToUniswapQuoter,
|
||||
} from "../src/route/cross-quote";
|
||||
import { UniswapToUniswapExecutor } from "../src/swapper/swapper";
|
||||
import {
|
||||
ETH_TOKEN_INFO,
|
||||
MATIC_TOKEN_INFO,
|
||||
AVAX_TOKEN_INFO,
|
||||
BNB_TOKEN_INFO,
|
||||
UST_TOKEN_INFO,
|
||||
} from "../src/utils/consts";
|
||||
|
||||
import { makeProvider } from "./src/provider";
|
||||
|
||||
require("dotenv").config({ path: ".env" });
|
||||
|
||||
// swap related parameters (configurable in UI)
|
||||
const SWAP_AMOUNT_IN_MATIC = "0.0069";
|
||||
const SWAP_AMOUNT_IN_ETH = "0.000907";
|
||||
const SWAP_AMOUNT_IN_AVAX = "0.0075";
|
||||
const SWAP_AMOUNT_IN_BNB = "0.0015";
|
||||
const SWAP_AMOUNT_IN_UST = "3.40";
|
||||
|
||||
const SWAP_DEADLINE = "1800";
|
||||
const SWAP_SLIPPAGE = "0.01";
|
||||
|
||||
// token bridge things
|
||||
const BRIDGE_RELAYER_FEE_UST = "0.25";
|
||||
|
||||
interface Arguments {
|
||||
in: string;
|
||||
out: string;
|
||||
}
|
||||
|
||||
function parseArgs(): Arguments {
|
||||
const parsed = yargs(process.argv.slice(2))
|
||||
.option("in", {
|
||||
string: true,
|
||||
description: "Name of inbound token",
|
||||
required: true,
|
||||
})
|
||||
.option("out", {
|
||||
string: true,
|
||||
description: "Name of outbound token",
|
||||
required: true,
|
||||
})
|
||||
.help("h")
|
||||
.alias("h", "help").argv;
|
||||
|
||||
const args: Arguments = {
|
||||
in: parsed.in,
|
||||
out: parsed.out,
|
||||
};
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function makeEvmWallet(
|
||||
provider: ethers.providers.Provider
|
||||
): ethers.Wallet {
|
||||
return new ethers.Wallet(process.env.ETH_PRIVATE_KEY, provider);
|
||||
}
|
||||
|
||||
/*
|
||||
async function fetchTokenBalance(signer, contract) {
|
||||
const decimals = await contract.decimals();
|
||||
const balanceBeforeDecimals = (await contract.balanceOf(signer.address)).toString();
|
||||
const balance = ethers.utils.formatUnits(balanceBeforeDecimals, decimals);
|
||||
return balance;
|
||||
}
|
||||
*/
|
||||
|
||||
// only exist as placeholder for actual wallet connection
|
||||
function determineWalletFromToken(tokenAddress: string): ethers.Wallet {
|
||||
return makeEvmWallet(makeProvider(tokenAddress));
|
||||
}
|
||||
|
||||
function determineAmountFromToken(tokenAddress: string): string {
|
||||
switch (tokenAddress) {
|
||||
case ETH_TOKEN_INFO.address: {
|
||||
return SWAP_AMOUNT_IN_ETH;
|
||||
}
|
||||
case MATIC_TOKEN_INFO.address: {
|
||||
return SWAP_AMOUNT_IN_MATIC;
|
||||
}
|
||||
case AVAX_TOKEN_INFO.address: {
|
||||
return SWAP_AMOUNT_IN_AVAX;
|
||||
}
|
||||
case BNB_TOKEN_INFO.address: {
|
||||
return SWAP_AMOUNT_IN_BNB;
|
||||
}
|
||||
case UST_TOKEN_INFO.address: {
|
||||
return SWAP_AMOUNT_IN_UST;
|
||||
}
|
||||
default: {
|
||||
throw Error("you suck");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function logExactInParameters(
|
||||
quoter: UniswapToUniswapQuoter,
|
||||
params: ExactInCrossParameters
|
||||
): void {
|
||||
console.info(`amountIn: ${params.amountIn}`);
|
||||
console.info(`minAmountOut: ${params.minAmountOut}`);
|
||||
|
||||
const src = params.src;
|
||||
if (src === undefined) {
|
||||
console.warn(` src is undefined (ust?)`);
|
||||
} else {
|
||||
console.info(`src`);
|
||||
console.info(` protocol: ${src.protocol}`);
|
||||
//console.info(` amountIn: ${quoter.srcTokenIn.formatAmount(src.amountIn)}`);
|
||||
console.info(
|
||||
` amountIn: ${quoter.srcRouter.formatAmountIn(
|
||||
src.amountIn.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(
|
||||
// ` minAmountOut: ${quoter.srcTokenOut.formatAmount(src.minAmountOut)}`
|
||||
` minAmountOut: ${quoter.srcRouter.formatAmountOut(
|
||||
src.minAmountOut.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(` poolFee: ${src.poolFee}`);
|
||||
console.info(` deadline: ${src.deadline.toString()}`);
|
||||
console.info(` path: ${src.path}`);
|
||||
}
|
||||
|
||||
const dst = params.dst;
|
||||
console.info(`dst`);
|
||||
if (dst === undefined) {
|
||||
console.warn(` dst is undefined (ust?)`);
|
||||
} else {
|
||||
console.info(` protocol: ${dst.protocol}`);
|
||||
//console.info(` amountIn: ${quoter.dstTokenIn.formatAmount(dst.amountIn)}`);
|
||||
console.info(
|
||||
` amountIn: ${quoter.dstRouter.formatAmountIn(
|
||||
dst.amountIn.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(
|
||||
// ` minAmountOut: ${quoter.dstTokenOut.formatAmount(dst.minAmountOut)}`
|
||||
` minAmountOut: ${quoter.dstRouter.formatAmountOut(
|
||||
dst.minAmountOut.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(` poolFee: ${dst.poolFee}`);
|
||||
console.info(` deadline: ${dst.deadline.toString()}`);
|
||||
console.info(` path: ${dst.path}`);
|
||||
|
||||
const relayerFee = params.relayerFee;
|
||||
console.info(`relayerFee`);
|
||||
console.info(` tokenAddress: ${relayerFee.tokenAddress}`);
|
||||
console.info(
|
||||
` amount: ${quoter.dstRouter.formatAmountIn(relayerFee.amount)}`
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async function swapEverythingExactIn(
|
||||
swapper: UniswapToUniswapExecutor,
|
||||
tokenInAddress: string,
|
||||
tokenOutAddress: string,
|
||||
isNative: boolean,
|
||||
amountIn: string,
|
||||
recipientAddress: string
|
||||
): Promise<void> {
|
||||
const isTerraSrc = tokenInAddress === UST_TOKEN_INFO.address;
|
||||
|
||||
if (isTerraSrc) {
|
||||
throw Error("cannot use terra source yet");
|
||||
}
|
||||
// connect src wallet
|
||||
const srcWallet = determineWalletFromToken(tokenInAddress);
|
||||
console.info(`sender: ${await srcWallet.getAddress()}`);
|
||||
console.info(`recipient: ${recipientAddress}`);
|
||||
|
||||
// tokens selected, let's initialize
|
||||
await swapper.initialize(tokenInAddress, tokenOutAddress, isNative);
|
||||
console.info(`quoter initialized`);
|
||||
|
||||
// verify pool address on src and dst
|
||||
await swapper
|
||||
.computeAndVerifySrcPoolAddress()
|
||||
.then((address) => {
|
||||
console.info(`srcPool: ${address}`);
|
||||
return address;
|
||||
})
|
||||
.catch((response) => {
|
||||
console.error(
|
||||
`failed to find a pool address for src. how to handle in the front-end?`
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
await swapper
|
||||
.computeAndVerifyDstPoolAddress()
|
||||
.then((address) => {
|
||||
console.info(`dstPool: ${address}`);
|
||||
return address;
|
||||
})
|
||||
.catch((response) => {
|
||||
console.error(
|
||||
`failed to find a pool address for dst. how to handle in the front-end?`
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// set deadline
|
||||
swapper.setDeadlines(SWAP_DEADLINE);
|
||||
swapper.setSlippage(SWAP_SLIPPAGE);
|
||||
swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST);
|
||||
|
||||
const exactInParameters: ExactInCrossParameters =
|
||||
await swapper.computeQuoteExactIn(amountIn);
|
||||
|
||||
console.info("exactInParameters");
|
||||
logExactInParameters(swapper.quoter, exactInParameters);
|
||||
|
||||
// do the src swap
|
||||
if (isTerraSrc) {
|
||||
// do terra method
|
||||
throw Error("terra src not implemented yet");
|
||||
} else {
|
||||
console.info("approveAndSwap");
|
||||
const srcSwapReceipt = await swapper.evmApproveAndSwap(
|
||||
srcWallet,
|
||||
recipientAddress
|
||||
);
|
||||
console.info(`src transaction: ${srcSwapReceipt.transactionHash}`);
|
||||
}
|
||||
|
||||
// do the dst swap after fetching vaa
|
||||
// connect dst wallet
|
||||
const dstWallet = determineWalletFromToken(tokenOutAddress);
|
||||
|
||||
console.info("fetchVaaAndSwap");
|
||||
//const dstSwapReceipt = await swapper.fetchVaaAndSwap(dstWallet);
|
||||
//console.info(`dst transaction: ${dstSwapReceipt.transactionHash}`);
|
||||
console.warn("jk");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function logExactOutParameters(
|
||||
quoter: UniswapToUniswapQuoter,
|
||||
params: ExactOutCrossParameters
|
||||
): void {
|
||||
const src = params.src;
|
||||
console.info(`src`);
|
||||
console.info(` protocol: ${src.protocol}`);
|
||||
console.info(
|
||||
` amountOut: ${quoter.srcRouter.formatAmountOut(
|
||||
src.amountOut.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(
|
||||
` maxAmountIn: ${quoter.srcRouter.formatAmountIn(
|
||||
src.maxAmountIn.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(` poolFee: ${src.poolFee}`);
|
||||
console.info(` deadline: ${src.deadline.toString()}`);
|
||||
console.info(` path: ${src.path}`);
|
||||
|
||||
const dst = params.dst;
|
||||
console.info(`dst`);
|
||||
console.info(` protocol: ${dst.protocol}`);
|
||||
console.info(
|
||||
` amountOut: ${quoter.dstRouter.formatAmountOut(
|
||||
dst.amountOut.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(
|
||||
` maxAmountIn: ${quoter.dstRouter.formatAmountIn(
|
||||
dst.maxAmountIn.toString()
|
||||
)}`
|
||||
);
|
||||
console.info(` poolFee: ${dst.poolFee}`);
|
||||
console.info(` deadline: ${dst.deadline.toString()}`);
|
||||
console.info(` path: ${dst.path}`);
|
||||
|
||||
const relayerFee = params.relayerFee;
|
||||
console.info(`relayerFee`);
|
||||
console.info(` tokenAddress: ${relayerFee.tokenAddress}`);
|
||||
console.info(
|
||||
` amount: ${quoter.dstRouter.formatAmountIn(
|
||||
relayerFee.amount.toString()
|
||||
)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
async function swapEverythingExactOut(
|
||||
swapper: UniswapToUniswapExecutor,
|
||||
tokenInAddress: string,
|
||||
tokenOutAddress: string,
|
||||
isNative: boolean,
|
||||
amountOut: string,
|
||||
recipientAddress: string
|
||||
): Promise<void> {
|
||||
// connect src wallet
|
||||
const srcWallet = determineWalletFromToken(tokenInAddress);
|
||||
console.info(`wallet pubkey: ${await srcWallet.getAddress()}`);
|
||||
|
||||
// tokens selected, let's initialize
|
||||
await swapper.initialize(tokenInAddress, tokenOutAddress, isNative);
|
||||
console.info(`quoter initialized`);
|
||||
|
||||
// verify pool address on src and dst
|
||||
await swapper
|
||||
.computeAndVerifySrcPoolAddress()
|
||||
.then((address) => {
|
||||
console.info(`srcPool: ${address}`);
|
||||
return address;
|
||||
})
|
||||
.catch((response) => {
|
||||
console.error(
|
||||
`failed to find a pool address for src. how to handle in the front-end?`
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
await swapper
|
||||
.computeAndVerifyDstPoolAddress()
|
||||
.then((address) => {
|
||||
console.info(`dstPool: ${address}`);
|
||||
return address;
|
||||
})
|
||||
.catch((response) => {
|
||||
console.error(
|
||||
`failed to find a pool address for dst. how to handle in the front-end?`
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// set deadline
|
||||
swapper.setDeadlines(SWAP_DEADLINE);
|
||||
swapper.setSlippage(SWAP_SLIPPAGE);
|
||||
swapper.setRelayerFee(BRIDGE_RELAYER_FEE_UST);
|
||||
|
||||
const exactOutParameters: ExactOutCrossParameters =
|
||||
await swapper.computeQuoteExactOut(amountOut);
|
||||
|
||||
console.info("exactOutParameters");
|
||||
logExactOutParameters(swapper.quoter, exactOutParameters);
|
||||
|
||||
// do the src swap
|
||||
console.info("approveAndSwap");
|
||||
const srcSwapReceipt = await swapper.evmApproveAndSwap(
|
||||
srcWallet,
|
||||
recipientAddress
|
||||
);
|
||||
console.info(`src transaction: ${srcSwapReceipt.transactionHash}`);
|
||||
|
||||
// do the dst swap after fetching vaa
|
||||
// connect dst wallet
|
||||
const dstWallet = determineWalletFromToken(tokenOutAddress);
|
||||
|
||||
console.info("fetchVaaAndSwap");
|
||||
//const dstSwapReceipt = await swapper.fetchVaaAndSwap(dstWallet);
|
||||
//console.info(`dst transaction: ${dstSwapReceipt.transactionHash}`);
|
||||
console.warn("jk");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function getTokenInfo(name: string) {
|
||||
switch (name) {
|
||||
case "ETH": {
|
||||
return ETH_TOKEN_INFO;
|
||||
}
|
||||
case "MATIC": {
|
||||
return MATIC_TOKEN_INFO;
|
||||
}
|
||||
case "UST": {
|
||||
return UST_TOKEN_INFO;
|
||||
}
|
||||
case "AVAX": {
|
||||
return AVAX_TOKEN_INFO;
|
||||
}
|
||||
case "BNB": {
|
||||
return BNB_TOKEN_INFO;
|
||||
}
|
||||
default: {
|
||||
throw Error("invalid token name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs();
|
||||
|
||||
const testExactIn = true;
|
||||
const isNative = true;
|
||||
|
||||
const swapper = new UniswapToUniswapExecutor();
|
||||
swapper.setTransport(NodeHttpTransport());
|
||||
|
||||
const tokenIn = getTokenInfo(args.in);
|
||||
const tokenOut = getTokenInfo(args.out);
|
||||
//const tokenOut = UST_TOKEN_INFO;
|
||||
|
||||
const recipientAddress = "0x4e2dfAD7D7d0076b5A0A41223E4Bee390C33251C";
|
||||
//const recipientAddress = "terra1vewnsxcy5fqjslyyy409cw8js550esen38n8ey";
|
||||
|
||||
if (testExactIn) {
|
||||
console.info(`testing exact in. native=${isNative}`);
|
||||
|
||||
console.info(`${tokenIn.name} -> ${tokenOut.name}`);
|
||||
await swapEverythingExactIn(
|
||||
swapper,
|
||||
tokenIn.address,
|
||||
tokenOut.address,
|
||||
isNative,
|
||||
determineAmountFromToken(tokenIn.address),
|
||||
recipientAddress
|
||||
);
|
||||
|
||||
if (tokenOut.address === UST_TOKEN_INFO.address) {
|
||||
console.warn("not pinging back");
|
||||
} else {
|
||||
console.info(`${tokenOut.name} -> ${tokenIn.name}`);
|
||||
await swapEverythingExactIn(
|
||||
swapper,
|
||||
tokenOut.address,
|
||||
tokenIn.address,
|
||||
isNative,
|
||||
determineAmountFromToken(tokenOut.address),
|
||||
recipientAddress
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.info(`testing exact out. native=${isNative}`);
|
||||
|
||||
console.info(`${tokenIn.name} -> ${tokenOut.name}`);
|
||||
await swapEverythingExactOut(
|
||||
swapper,
|
||||
tokenIn.address,
|
||||
tokenOut.address,
|
||||
isNative,
|
||||
determineAmountFromToken(tokenOut.address),
|
||||
recipientAddress
|
||||
);
|
||||
|
||||
console.info(`${tokenOut.name} -> ${tokenIn.name}`);
|
||||
await swapEverythingExactOut(
|
||||
swapper,
|
||||
tokenOut.address,
|
||||
tokenIn.address,
|
||||
isNative,
|
||||
determineAmountFromToken(tokenIn.address),
|
||||
recipientAddress
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
main();
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"files": [
|
||||
"scripts/swap-with-vaa.ts"
|
||||
]
|
||||
}
|
|
@ -1,2 +1,4 @@
|
|||
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
|
||||
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"
|
||||
REACT_APP_FUJI_PROVIDER="https://api.avax-test.network/ext/bc/C/rpc"
|
||||
REACT_APP_BSC_PROVIDER="https://data-seed-prebsc-1-s1.binance.org:8545"
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@terra-money/terra.js": "^2.0.14",
|
||||
"@terra-money/wallet-provider": "^2.2.0",
|
||||
"@types/node": "^16.11.19",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
|
@ -1893,6 +1895,36 @@
|
|||
"rxjs": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@certusone/wormhole-sdk/node_modules/@terra-money/wallet-provider": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz",
|
||||
"integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==",
|
||||
"dependencies": {
|
||||
"@terra-dev/browser-check": "^2.5.3",
|
||||
"@terra-dev/chrome-extension": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet-modal": "^2.5.3",
|
||||
"@terra-dev/use-wallet": "^2.5.3",
|
||||
"@terra-dev/wallet-types": "^2.5.3",
|
||||
"@terra-dev/walletconnect": "^2.5.3",
|
||||
"@terra-dev/web-connector-controller": "^0.8.1",
|
||||
"@terra-dev/web-connector-interface": "^0.8.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"rxjs": "^7.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@terra-money/terra.js": "^2.0.0",
|
||||
"react": "^17.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-router-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@cnakazawa/watch": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz",
|
||||
|
@ -4494,12 +4526,24 @@
|
|||
"@terra-money/terra.js": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@terra-money/terra.js": {
|
||||
"version": "2.1.23",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.1.23.tgz",
|
||||
"integrity": "sha512-nSAR35zqjKUn1Jzqevf30s47XRlW/VXU01YgK3n9ndmX15lkdlgFvqaV7UezK0xAmCpm+7xWIrtBTMmZpVBkMQ==",
|
||||
"node_modules/@terra-dev/web-extension": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@terra-dev/web-extension/-/web-extension-0.6.0.tgz",
|
||||
"integrity": "sha512-IyIWHLfweZCb5nHuMyzavnMYposnZMvpsA/89zZPIgIooxhxE//uZD+Ty+ptt4nvkbOgEFKdKIKe5rIHqgVLpA==",
|
||||
"dependencies": {
|
||||
"@terra-money/terra.js": "^1.8.0 || ^2.0.0",
|
||||
"bowser": "^2.11.0",
|
||||
"rxjs": "^7.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@terra-money/terra.js": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.0.14.tgz",
|
||||
"integrity": "sha512-GeMadRIPaOedODa5a0pJ2+76l7MeFSIfSJZ2vvWPRco6MRIQLw/k0cZpPKMLm2Zo54li/oY1mrR+r3uxLM7q3Q==",
|
||||
"dependencies": {
|
||||
"@terra-money/terra.proto": "^0.1.7",
|
||||
"axios": "^0.21.1",
|
||||
"bech32": "^2.0.0",
|
||||
"bip32": "^2.0.6",
|
||||
|
@ -4514,7 +4558,7 @@
|
|||
"ws": "^7.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@terra-money/terra.js/node_modules/axios": {
|
||||
|
@ -4525,38 +4569,27 @@
|
|||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@terra-money/terra.proto": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz",
|
||||
"integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==",
|
||||
"dependencies": {
|
||||
"google-protobuf": "^3.17.3",
|
||||
"long": "^4.0.0",
|
||||
"protobufjs": "~6.11.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@terra-money/wallet-provider": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz",
|
||||
"integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz",
|
||||
"integrity": "sha512-K8NLpJ/yak8Pq6jQpjVr7yWDIbxjTp42OXaAS+xlTufqQwWbCR7coAGbm2FpYX43j4uymuSAICZvsOS1qrFeYA==",
|
||||
"dependencies": {
|
||||
"@terra-dev/browser-check": "^2.5.3",
|
||||
"@terra-dev/chrome-extension": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet-modal": "^2.5.3",
|
||||
"@terra-dev/use-wallet": "^2.5.3",
|
||||
"@terra-dev/wallet-types": "^2.5.3",
|
||||
"@terra-dev/walletconnect": "^2.5.3",
|
||||
"@terra-dev/web-connector-controller": "^0.8.1",
|
||||
"@terra-dev/web-connector-interface": "^0.8.1",
|
||||
"@terra-dev/browser-check": "^2.2.0",
|
||||
"@terra-dev/chrome-extension": "^2.2.0",
|
||||
"@terra-dev/readonly-wallet": "^2.2.0",
|
||||
"@terra-dev/readonly-wallet-modal": "^2.2.0",
|
||||
"@terra-dev/use-wallet": "^2.2.0",
|
||||
"@terra-dev/wallet-types": "^2.2.0",
|
||||
"@terra-dev/walletconnect": "^2.2.0",
|
||||
"@terra-dev/web-extension": "^0.6.0",
|
||||
"@terra-money/terra.js": "^2.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"rxjs": "^7.4.0"
|
||||
"rxjs": "^7.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@terra-money/terra.js": "^2.0.0",
|
||||
"react": "^17.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
|
@ -12324,7 +12357,8 @@
|
|||
"node_modules/google-protobuf": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.3.tgz",
|
||||
"integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A=="
|
||||
"integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.9",
|
||||
|
@ -27356,6 +27390,26 @@
|
|||
"js-base64": "^3.6.1",
|
||||
"protobufjs": "^6.11.2",
|
||||
"rxjs": "^7.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@terra-money/wallet-provider": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz",
|
||||
"integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==",
|
||||
"requires": {
|
||||
"@terra-dev/browser-check": "^2.5.3",
|
||||
"@terra-dev/chrome-extension": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet-modal": "^2.5.3",
|
||||
"@terra-dev/use-wallet": "^2.5.3",
|
||||
"@terra-dev/wallet-types": "^2.5.3",
|
||||
"@terra-dev/walletconnect": "^2.5.3",
|
||||
"@terra-dev/web-connector-controller": "^0.8.1",
|
||||
"@terra-dev/web-connector-interface": "^0.8.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"rxjs": "^7.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@cnakazawa/watch": {
|
||||
|
@ -29148,12 +29202,21 @@
|
|||
"rxjs": "^7.4.0"
|
||||
}
|
||||
},
|
||||
"@terra-money/terra.js": {
|
||||
"version": "2.1.23",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.1.23.tgz",
|
||||
"integrity": "sha512-nSAR35zqjKUn1Jzqevf30s47XRlW/VXU01YgK3n9ndmX15lkdlgFvqaV7UezK0xAmCpm+7xWIrtBTMmZpVBkMQ==",
|
||||
"@terra-dev/web-extension": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@terra-dev/web-extension/-/web-extension-0.6.0.tgz",
|
||||
"integrity": "sha512-IyIWHLfweZCb5nHuMyzavnMYposnZMvpsA/89zZPIgIooxhxE//uZD+Ty+ptt4nvkbOgEFKdKIKe5rIHqgVLpA==",
|
||||
"requires": {
|
||||
"@terra-money/terra.js": "^1.8.0 || ^2.0.0",
|
||||
"bowser": "^2.11.0",
|
||||
"rxjs": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@terra-money/terra.js": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-2.0.14.tgz",
|
||||
"integrity": "sha512-GeMadRIPaOedODa5a0pJ2+76l7MeFSIfSJZ2vvWPRco6MRIQLw/k0cZpPKMLm2Zo54li/oY1mrR+r3uxLM7q3Q==",
|
||||
"requires": {
|
||||
"@terra-money/terra.proto": "^0.1.7",
|
||||
"axios": "^0.21.1",
|
||||
"bech32": "^2.0.0",
|
||||
"bip32": "^2.0.6",
|
||||
|
@ -29178,32 +29241,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@terra-money/terra.proto": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz",
|
||||
"integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==",
|
||||
"requires": {
|
||||
"google-protobuf": "^3.17.3",
|
||||
"long": "^4.0.0",
|
||||
"protobufjs": "~6.11.2"
|
||||
}
|
||||
},
|
||||
"@terra-money/wallet-provider": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.5.3.tgz",
|
||||
"integrity": "sha512-v/5Z35gCo4nZyZCu3nYDFvhwuvlyDeNSSYmN9KUc9ewoIO9K/2fi3vxcOLcvqq5PYowwwod21vgaQ9QHFV+8eA==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@terra-money/wallet-provider/-/wallet-provider-2.2.0.tgz",
|
||||
"integrity": "sha512-K8NLpJ/yak8Pq6jQpjVr7yWDIbxjTp42OXaAS+xlTufqQwWbCR7coAGbm2FpYX43j4uymuSAICZvsOS1qrFeYA==",
|
||||
"requires": {
|
||||
"@terra-dev/browser-check": "^2.5.3",
|
||||
"@terra-dev/chrome-extension": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet": "^2.5.3",
|
||||
"@terra-dev/readonly-wallet-modal": "^2.5.3",
|
||||
"@terra-dev/use-wallet": "^2.5.3",
|
||||
"@terra-dev/wallet-types": "^2.5.3",
|
||||
"@terra-dev/walletconnect": "^2.5.3",
|
||||
"@terra-dev/web-connector-controller": "^0.8.1",
|
||||
"@terra-dev/web-connector-interface": "^0.8.1",
|
||||
"@terra-dev/browser-check": "^2.2.0",
|
||||
"@terra-dev/chrome-extension": "^2.2.0",
|
||||
"@terra-dev/readonly-wallet": "^2.2.0",
|
||||
"@terra-dev/readonly-wallet-modal": "^2.2.0",
|
||||
"@terra-dev/use-wallet": "^2.2.0",
|
||||
"@terra-dev/wallet-types": "^2.2.0",
|
||||
"@terra-dev/walletconnect": "^2.2.0",
|
||||
"@terra-dev/web-extension": "^0.6.0",
|
||||
"@terra-money/terra.js": "^2.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"rxjs": "^7.4.0"
|
||||
"rxjs": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
|
@ -35422,7 +35475,8 @@
|
|||
"google-protobuf": {
|
||||
"version": "3.19.3",
|
||||
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.19.3.tgz",
|
||||
"integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A=="
|
||||
"integrity": "sha512-3GRDj8o9XjcALYjgxNKeD7Wm6w/V8r1Jo4sLYMic9+VaIMLBx8TQeHP9yaoRoDymNONhnkmmveDPyjw/Fpw8+A==",
|
||||
"peer": true
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.9",
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@terra-money/terra.js": "^2.0.14",
|
||||
"@terra-money/wallet-provider": "^2.2.0",
|
||||
"@types/node": "^16.11.19",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
goerli.ts
|
||||
mumbai.ts
|
||||
*.ts
|
||||
*.js
|
||||
|
|
|
@ -5,7 +5,7 @@ import Github from "../icons/Github.svg";
|
|||
import Medium from "../icons/Medium.svg";
|
||||
import Telegram from "../icons/Telegram.svg";
|
||||
import Twitter from "../icons/Twitter.svg";
|
||||
import Wormhole from "../icons/wormhole-network.svg";
|
||||
import Wormhole from "../icons/wormhole_logo.svg";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
footer: {
|
||||
|
@ -19,7 +19,6 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
},
|
||||
builtWithContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
opacity: 0.5,
|
||||
|
@ -27,7 +26,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
wormholeIcon: {
|
||||
height: 48,
|
||||
width: 48,
|
||||
width: 192,
|
||||
filter: "contrast(0)",
|
||||
transition: "filter 0.5s",
|
||||
"&:hover": {
|
||||
|
@ -92,24 +91,18 @@ export default function Footer() {
|
|||
<img src={Twitter} alt="Twitter" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href="https://wormholenetwork.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img src={Wormhole} alt="Wormhole" className={classes.wormholeIcon} />
|
||||
</a>
|
||||
</div>
|
||||
<div className={classes.builtWithContainer}>
|
||||
<div>
|
||||
<a
|
||||
href="https://wormholenetwork.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src={Wormhole}
|
||||
alt="Wormhole"
|
||||
className={classes.wormholeIcon}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="body2">Open Source</Typography>
|
||||
<Typography variant="body2">Built with ❤</Typography>
|
||||
</div>
|
||||
<Typography variant="body2">Open Source</Typography>
|
||||
<Typography variant="body2">Built with ❤</Typography>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { ChainId, CHAIN_ID_POLYGON, isEVMChain } from "@certusone/wormhole-sdk";
|
||||
import { ChainId, isEVMChain } from "@certusone/wormhole-sdk";
|
||||
import { LinearProgress, makeStyles, Typography } from "@material-ui/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { getChainName } from "../utils/consts";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
|
@ -16,17 +17,19 @@ const useStyles = makeStyles((theme) => ({
|
|||
export default function TransactionProgress({
|
||||
chainId,
|
||||
txBlockNumber,
|
||||
step,
|
||||
hasSignedVAA,
|
||||
isTargetSwapComplete,
|
||||
}: {
|
||||
chainId: ChainId;
|
||||
txBlockNumber: number | undefined;
|
||||
step: number;
|
||||
hasSignedVAA: boolean;
|
||||
isTargetSwapComplete: boolean;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
const { provider } = useEthereumProvider();
|
||||
const [currentBlock, setCurrentBlock] = useState(0);
|
||||
useEffect(() => {
|
||||
if (step !== 1 || !txBlockNumber) return;
|
||||
if (hasSignedVAA || !txBlockNumber) return;
|
||||
if (isEVMChain(chainId) && provider) {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
|
@ -46,33 +49,30 @@ export default function TransactionProgress({
|
|||
cancelled = true;
|
||||
};
|
||||
}
|
||||
}, [step, chainId, provider, txBlockNumber]);
|
||||
const blockDiff =
|
||||
}, [hasSignedVAA, chainId, provider, txBlockNumber]);
|
||||
let blockDiff =
|
||||
txBlockNumber !== undefined && txBlockNumber && currentBlock
|
||||
? currentBlock - txBlockNumber
|
||||
: 0;
|
||||
const expectedBlocks = 15;
|
||||
blockDiff = Math.min(Math.max(blockDiff, 0), expectedBlocks);
|
||||
let value;
|
||||
let valueBuffer;
|
||||
let message;
|
||||
switch (step) {
|
||||
case 1:
|
||||
value = (blockDiff / expectedBlocks) * 50;
|
||||
valueBuffer = 50;
|
||||
message = `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${
|
||||
chainId === CHAIN_ID_POLYGON ? "Polygon" : "Ethereum"
|
||||
}...`;
|
||||
break;
|
||||
case 2:
|
||||
value = 50;
|
||||
valueBuffer = 100;
|
||||
message = "Waiting for relayer to complete swap...";
|
||||
break;
|
||||
case 3:
|
||||
value = 100;
|
||||
valueBuffer = 100;
|
||||
message = "";
|
||||
break;
|
||||
if (!hasSignedVAA) {
|
||||
value = (blockDiff / expectedBlocks) * 50;
|
||||
valueBuffer = 50;
|
||||
message = `Waiting for ${blockDiff} / ${expectedBlocks} confirmations on ${getChainName(
|
||||
chainId
|
||||
)}...`;
|
||||
} else if (!isTargetSwapComplete) {
|
||||
value = 50;
|
||||
valueBuffer = 100;
|
||||
message = "Waiting for relayer to complete swap...";
|
||||
} else {
|
||||
value = 100;
|
||||
valueBuffer = 100;
|
||||
message = "Success!";
|
||||
}
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { useTerraWallet } from "../contexts/TerraWalletContext";
|
||||
import ToggleConnectedButton from "./ToggleConnectedButton";
|
||||
|
||||
const TerraWalletKey = () => {
|
||||
const { connect, disconnect, connected, wallet } = useTerraWallet();
|
||||
const pk =
|
||||
(wallet &&
|
||||
wallet.wallets &&
|
||||
wallet.wallets.length > 0 &&
|
||||
wallet.wallets[0].terraAddress) ||
|
||||
"";
|
||||
return (
|
||||
<ToggleConnectedButton
|
||||
connect={connect}
|
||||
disconnect={disconnect}
|
||||
connected={connected}
|
||||
pk={pk}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TerraWalletKey;
|
|
@ -5,7 +5,20 @@ import {
|
|||
MenuItem,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import { TokenInfo } from "../utils/consts";
|
||||
import {
|
||||
AVAX_TOKEN_INFO,
|
||||
BNB_TOKEN_INFO,
|
||||
ETH_TOKEN_INFO,
|
||||
MATIC_TOKEN_INFO,
|
||||
TokenInfo,
|
||||
UST_TOKEN_INFO,
|
||||
} from "../utils/consts";
|
||||
|
||||
import ethIcon from "../icons/eth.svg";
|
||||
import polygonIcon from "../icons/polygon.svg";
|
||||
import terraIcon from "../icons/terra.svg";
|
||||
import bscIcon from "../icons/bsc.svg";
|
||||
import avaxIcon from "../icons/avax.svg";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
select: {
|
||||
|
@ -23,10 +36,27 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const createTokenMenuItem = ({ name, logo }: TokenInfo, classes: any) => (
|
||||
const getLogo = (name: string) => {
|
||||
switch (name) {
|
||||
case ETH_TOKEN_INFO.name:
|
||||
return ethIcon;
|
||||
case MATIC_TOKEN_INFO.name:
|
||||
return polygonIcon;
|
||||
case UST_TOKEN_INFO.name:
|
||||
return terraIcon;
|
||||
case AVAX_TOKEN_INFO.name:
|
||||
return avaxIcon;
|
||||
case BNB_TOKEN_INFO.name:
|
||||
return bscIcon;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const createTokenMenuItem = ({ name }: TokenInfo, classes: any) => (
|
||||
<MenuItem key={name} value={name}>
|
||||
<ListItemIcon className={classes.listItemIcon}>
|
||||
<img src={logo} alt={name} className={classes.icon} />
|
||||
<img src={getLogo(name)} alt={name} className={classes.icon} />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{name}</ListItemText>
|
||||
</MenuItem>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import {
|
||||
NetworkInfo,
|
||||
Wallet,
|
||||
WalletProvider,
|
||||
useWallet,
|
||||
} from "@terra-money/wallet-provider";
|
||||
import React, {
|
||||
ReactChildren,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
const testnet: NetworkInfo = {
|
||||
name: "testnet",
|
||||
chainID: "bombay-12",
|
||||
lcd: "https://bombay-lcd.terra.dev",
|
||||
};
|
||||
|
||||
const walletConnectChainIds: Record<number, NetworkInfo> = {
|
||||
0: testnet,
|
||||
};
|
||||
|
||||
interface ITerraWalletContext {
|
||||
connect(): void;
|
||||
disconnect(): void;
|
||||
connected: boolean;
|
||||
wallet: any;
|
||||
}
|
||||
|
||||
const TerraWalletContext = React.createContext<ITerraWalletContext>({
|
||||
connect: () => {},
|
||||
disconnect: () => {},
|
||||
connected: false,
|
||||
wallet: null,
|
||||
});
|
||||
|
||||
export const TerraWalletWrapper = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactChildren;
|
||||
}) => {
|
||||
// TODO: Use wallet instead of useConnectedWallet.
|
||||
const terraWallet = useWallet();
|
||||
const [, setWallet] = useState<Wallet | undefined>(undefined);
|
||||
const [connected, setConnected] = useState(false);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
const CHROME_EXTENSION = 1;
|
||||
if (terraWallet) {
|
||||
terraWallet.connect(terraWallet.availableConnectTypes[CHROME_EXTENSION]);
|
||||
setWallet(terraWallet);
|
||||
setConnected(true);
|
||||
}
|
||||
}, [terraWallet]);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
setConnected(false);
|
||||
setWallet(undefined);
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
connect,
|
||||
disconnect,
|
||||
connected,
|
||||
wallet: terraWallet,
|
||||
}),
|
||||
[connect, disconnect, connected, terraWallet]
|
||||
);
|
||||
|
||||
return (
|
||||
<TerraWalletContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</TerraWalletContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const TerraWalletProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactChildren;
|
||||
}) => {
|
||||
return (
|
||||
<WalletProvider
|
||||
defaultNetwork={testnet}
|
||||
walletConnectChainIds={walletConnectChainIds}
|
||||
>
|
||||
<TerraWalletWrapper>{children}</TerraWalletWrapper>
|
||||
</WalletProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTerraWallet = () => {
|
||||
return useContext(TerraWalletContext);
|
||||
};
|
|
@ -1,12 +1,10 @@
|
|||
import { ChainId, CHAIN_ID_SOLANA, isEVMChain } from "@certusone/wormhole-sdk";
|
||||
import { ChainId, CHAIN_ID_TERRA, isEVMChain } from "@certusone/wormhole-sdk";
|
||||
import { hexlify, hexStripZeros } from "@ethersproject/bytes";
|
||||
import { useConnectedWallet } from "@terra-money/wallet-provider";
|
||||
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 = "",
|
||||
|
@ -29,8 +27,8 @@ function useIsWalletReady(
|
|||
forceNetworkSwitch: () => void;
|
||||
} {
|
||||
const autoSwitch = enableNetworkAutoswitch;
|
||||
// const solanaWallet = useSolanaWallet();
|
||||
// const solPK = solanaWallet?.publicKey;
|
||||
const terraWallet = useConnectedWallet();
|
||||
const hasTerraWallet = !!terraWallet;
|
||||
const {
|
||||
provider,
|
||||
signerAddress,
|
||||
|
@ -54,14 +52,19 @@ function useIsWalletReady(
|
|||
}, [provider, correctEvmNetwork, chainId]);
|
||||
|
||||
return useMemo(() => {
|
||||
//if (chainId === CHAIN_ID_SOLANA && solPK) {
|
||||
// return createWalletStatus(
|
||||
// true,
|
||||
// undefined,
|
||||
// forceNetworkSwitch,
|
||||
// solPK.toString()
|
||||
// );
|
||||
//}
|
||||
if (
|
||||
chainId === CHAIN_ID_TERRA &&
|
||||
hasTerraWallet &&
|
||||
terraWallet?.walletAddress
|
||||
) {
|
||||
// TODO: terraWallet does not update on wallet changes
|
||||
return createWalletStatus(
|
||||
true,
|
||||
undefined,
|
||||
forceNetworkSwitch,
|
||||
terraWallet.walletAddress
|
||||
);
|
||||
}
|
||||
if (isEVMChain(chainId) && hasEthInfo && signerAddress) {
|
||||
if (hasCorrectEvmNetwork) {
|
||||
return createWalletStatus(
|
||||
|
@ -76,7 +79,7 @@ function useIsWalletReady(
|
|||
}
|
||||
return createWalletStatus(
|
||||
false,
|
||||
`Wallet is not connected to ${CLUSTER}. Expected Chain ID: ${correctEvmNetwork}`,
|
||||
`Wallet is not connected to testnet. Expected Chain ID: ${correctEvmNetwork}`,
|
||||
forceNetworkSwitch,
|
||||
undefined
|
||||
);
|
||||
|
@ -93,12 +96,13 @@ function useIsWalletReady(
|
|||
chainId,
|
||||
autoSwitch,
|
||||
forceNetworkSwitch,
|
||||
// solPK,
|
||||
hasTerraWallet,
|
||||
hasEthInfo,
|
||||
correctEvmNetwork,
|
||||
hasCorrectEvmNetwork,
|
||||
provider,
|
||||
signerAddress,
|
||||
terraWallet,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?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 254 254" style="enable-background:new 0 0 254 254;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E84142;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<circle class="st0" cx="127" cy="127" r="127"/>
|
||||
<path class="st1" d="M171.8,130.3c4.4-7.6,11.5-7.6,15.9,0l27.4,48.1c4.4,7.6,0.8,13.8-8,13.8h-55.2c-8.7,0-12.3-6.2-8-13.8
|
||||
L171.8,130.3z M118.8,37.7c4.4-7.6,11.4-7.6,15.8,0l6.1,11L155.1,74c3.5,7.2,3.5,15.7,0,22.9l-48.3,83.7
|
||||
c-4.4,6.8-11.7,11.1-19.8,11.6H46.9c-8.8,0-12.4-6.1-8-13.8L118.8,37.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 825 B |
|
@ -0,0 +1,12 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.3025 0L9.67897 6.12683L13.5847 8.39024L20.3025 4.52683L27.0204 8.39024L30.9261 6.12683L20.3025 0Z" fill="#F0B90B"/>
|
||||
<path d="M27.0204 11.5902L30.9261 13.8537V18.3805L24.2083 22.2439V29.9707L20.3025 32.2341L16.3968 29.9707V22.2439L9.67897 18.3805V13.8537L13.5847 11.5902L20.3025 15.4537L27.0204 11.5902Z" fill="#F0B90B"/>
|
||||
<path d="M30.9261 21.5805V26.1073L27.0204 28.3707V23.8439L30.9261 21.5805Z" fill="#F0B90B"/>
|
||||
<path d="M26.9814 31.5707L33.6992 27.7073V19.9805L37.605 17.7171V29.9707L26.9814 36.0976V31.5707Z" fill="#F0B90B"/>
|
||||
<path d="M33.6992 12.2537L29.7935 9.99025L33.6992 7.72683L37.605 9.99025V14.5171L33.6992 16.7805V12.2537Z" fill="#F0B90B"/>
|
||||
<path d="M16.3968 37.7366V33.2098L20.3025 35.4732L24.2083 33.2098V37.7366L20.3025 40L16.3968 37.7366Z" fill="#F0B90B"/>
|
||||
<path d="M13.5847 28.3707L9.67897 26.1073V21.5805L13.5847 23.8439V28.3707Z" fill="#F0B90B"/>
|
||||
<path d="M20.3025 12.2537L16.3968 9.99025L20.3025 7.72683L24.2083 9.99025L20.3025 12.2537Z" fill="#F0B90B"/>
|
||||
<path d="M10.8116 9.99025L6.90586 12.2537V16.7805L3.00012 14.5171V9.99025L6.90586 7.72683L10.8116 9.99025Z" fill="#F0B90B"/>
|
||||
<path d="M3.00012 17.7171L6.90586 19.9805V27.7073L13.6237 31.5707V36.0976L3.00012 29.9707V17.7171Z" fill="#F0B90B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.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 288.9 274" style="enable-background:new 0 0 288.9 274;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#2849A9;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#5795ED;}
|
||||
</style>
|
||||
<path class="st0" d="M151.1,0.3c33.7,0,64.9,12.1,88.7,32.9c31.8,24.5,22.6,113.9-9.6,90.3c-70.8-0.3-202.4-38.2-163.2-90.3
|
||||
c4-5.3,9-9.6,14.5-13.7h-0.3c0.9-0.5,1.9-1,2.8-1.6c0.9-0.5,1.9-1.1,2.8-1.6h0c2.8-1.6,5.6-3.1,8.7-4.3
|
||||
C112.5,4.6,131.3,0.3,151.1,0.3z M174.9,272.8c-14.2,0.9-42.6-21.4-50.7-50.9c-15.1-55.9,107.2-84.4,118.7-85.4
|
||||
c31.2,0.9,38.9,38.2,16.1,76.7C229.3,262.6,175.5,272.8,174.9,272.8z"/>
|
||||
<path class="st1" d="M14.8,77.9c9.9,2.8,70.5-16.5,88.4-43.8c0.3-0.3,14.2-21.7-12.7-22c-3.1,0-11.7,0.3-20.1,5.3
|
||||
c-4,2.5-7.7,5-11.4,7.8c-5.8,4.3-11.3,9.5-16.5,14.4h0l-0.2,0.2c-5.3,5-10.2,10.9-14.5,16.8c-4.3,5.9-8.3,12.4-11.7,18.9
|
||||
c-0.2,0.5-0.4,0.9-0.6,1.2C15.2,77,15,77.4,14.8,77.9z M86.5,272.8c1.9-2.8,3.1-36.6,1.9-45.3c-1.2-8.7-4-26.4-20.7-55.6
|
||||
c-2.8-4.7-16.1-26.4-26-39.7c-5.6-7.8-11.7-15-17.8-22.3h0c-5.1-6-10.2-12.1-15-18.4c-0.3,0.8-0.5,1.5-0.8,2.2s-0.5,1.4-0.8,2.2
|
||||
c-2.5,7.1-4.3,14.6-5.6,22.4S0,133.8,0,141.8c0,8.1,0.6,15.8,1.9,23.6s3.4,15.2,5.6,22.4c2.2,7.1,5.3,14.3,8.7,20.8
|
||||
s7.4,13,11.7,18.9c4.3,5.9,9.3,11.5,14.5,16.8c4.9,5.3,10.8,10.2,16.7,14.6h0h0c4.6,3.1,9.3,6.2,13.9,9c8.5,5,11.7,5,13.4,5
|
||||
C86.4,272.8,86.4,272.8,86.5,272.8z M288.9,141.8c0,18.9-3.7,36.9-10.2,53.4c-15.7,17-115.3-20.7-130.8-26.6c-1.2-0.5-2-0.7-2-0.8
|
||||
c-15.8-6.8-63.3-27.9-67.7-60.8c-6.2-47.5,89.6-80.7,131.9-82c4.9,0,20.4,0.3,29.4,7.5C269.8,59.2,288.9,98.4,288.9,141.8z
|
||||
M188.8,260.1c-3.7,12.1,10.2,16.5,22.6,10.6c24.7-13,45.1-33.2,59-57.1c0.9-1.2,0-2.5-1.5-2.2C255.6,212.6,195.6,236.5,188.8,260.1
|
||||
z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -1,8 +0,0 @@
|
|||
<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>
|
Before Width: | Height: | Size: 993 B |
|
@ -0,0 +1,23 @@
|
|||
<svg id="Group_2616" data-name="Group 2616" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="176.577" height="30.781" viewBox="0 0 176.577 30.781">
|
||||
<defs>
|
||||
<clipPath id="clip-path">
|
||||
<rect id="Rectangle_1331" data-name="Rectangle 1331" width="176.577" height="30.781" transform="translate(0 0)" fill="#fff"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="Group_2614" data-name="Group 2614" transform="translate(0 0)" clip-path="url(#clip-path)">
|
||||
<path id="Path_1571" data-name="Path 1571" d="M139.86,29.253l-4.039,11.421a.261.261,0,0,1-.268.177h-.306a.282.282,0,0,1-.273-.177l-3.028-8.01-3.023,8.01a.272.272,0,0,1-.268.177h-.311a.269.269,0,0,1-.268-.177l-4.039-11.417c-.062-.177.005-.287.177-.287h1.25a.26.26,0,0,1,.273.177l2.879,8.647,2.927-7.919a.277.277,0,0,1,.273-.177h.263a.271.271,0,0,1,.268.177l3.014,7.919,2.812-8.647a.26.26,0,0,1,.273-.177h1.231c.177,0,.244.11.182.283" transform="translate(-82.566 -19.288)" fill="#fff"/>
|
||||
<path id="Path_1572" data-name="Path 1572" d="M200.285,34.471a5.96,5.96,0,1,1-5.96-6.017,5.879,5.879,0,0,1,5.96,6.017m-1.653,0c0-2.769-1.806-4.676-4.307-4.676s-4.307,1.907-4.307,4.676,1.806,4.676,4.307,4.676,4.307-1.907,4.307-4.676" transform="translate(-125.412 -18.944)" fill="#fff"/>
|
||||
<path id="Path_1573" data-name="Path 1573" d="M253.32,40.186a.288.288,0,0,1-.254.474H251.94a.222.222,0,0,1-.182-.105l-2.826-4.264h-3.037v4.13a.243.243,0,0,1-.24.24h-1.1a.243.243,0,0,1-.239-.24V29.21a.244.244,0,0,1,.239-.24h4.326c3.028,0,4.523,1.591,4.523,3.66a3.445,3.445,0,0,1-2.841,3.469Zm-4.748-5.236c2.381,0,3.181-.987,3.181-2.319s-.8-2.319-3.181-2.319h-2.678v4.638Z" transform="translate(-162.664 -19.288)" fill="#fff"/>
|
||||
<path id="Path_1574" data-name="Path 1574" d="M303.514,28.4h.359a.226.226,0,0,1,.24.24v11.4a.226.226,0,0,1-.24.24h-1.1a.226.226,0,0,1-.24-.24V31.947l-4.432,6.415a.182.182,0,0,1-.331,0l-4.431-6.415v8.092a.226.226,0,0,1-.24.24h-1.1a.226.226,0,0,1-.24-.24v-11.4a.226.226,0,0,1,.24-.24h.355a.36.36,0,0,1,.3.163l5.28,7.373,5.284-7.373a.346.346,0,0,1,.292-.163" transform="translate(-194.255 -18.906)" fill="#fff"/>
|
||||
<path id="Path_1575" data-name="Path 1575" d="M361.126,29.21V40.42a.244.244,0,0,1-.24.24h-1.1a.244.244,0,0,1-.24-.24V35.313h-7.277V40.42a.244.244,0,0,1-.24.24h-1.1a.244.244,0,0,1-.24-.24V29.21a.244.244,0,0,1,.24-.24h1.1a.244.244,0,0,1,.24.24v4.762h7.277V29.21a.244.244,0,0,1,.24-.24h1.1a.244.244,0,0,1,.24.24" transform="translate(-233.489 -19.288)" fill="#fff"/>
|
||||
<path id="Path_1576" data-name="Path 1576" d="M414.109,34.471a5.96,5.96,0,1,1-5.96-6.017,5.879,5.879,0,0,1,5.96,6.017m-1.653,0c0-2.769-1.806-4.676-4.307-4.676s-4.307,1.907-4.307,4.676,1.806,4.676,4.307,4.676,4.307-1.907,4.307-4.676" transform="translate(-267.773 -18.944)" fill="#fff"/>
|
||||
<path id="Path_1577" data-name="Path 1577" d="M466.416,39.558v.862a.226.226,0,0,1-.24.24h-7.794a.244.244,0,0,1-.24-.24V29.21a.244.244,0,0,1,.24-.24h1.1a.244.244,0,0,1,.239.24V39.318h6.458a.226.226,0,0,1,.24.24" transform="translate(-305.025 -19.288)" fill="#fff"/>
|
||||
<path id="Path_1578" data-name="Path 1578" d="M510.615,39.558v.862a.226.226,0,0,1-.24.24h-8.417a.244.244,0,0,1-.24-.24V29.21a.244.244,0,0,1,.24-.24h8.226a.226.226,0,0,1,.24.24v.862a.226.226,0,0,1-.24.24h-6.889v3.66h6.123a.225.225,0,0,1,.239.235v.867a.226.226,0,0,1-.239.239h-6.123v4.005h7.081a.226.226,0,0,1,.24.24" transform="translate(-334.038 -19.288)" fill="#fff"/>
|
||||
<path id="Path_1579" data-name="Path 1579" d="M15.39,30.781a15.39,15.39,0,1,1,15.39-15.39,15.408,15.408,0,0,1-15.39,15.39m0-30.1A14.708,14.708,0,1,0,30.1,15.39,14.724,14.724,0,0,0,15.39.683" transform="translate(0 0)" fill="#fff"/>
|
||||
<path id="Path_1580" data-name="Path 1580" d="M22.272,32.5A13.829,13.829,0,1,1,36.1,18.672,13.844,13.844,0,0,1,22.272,32.5m0-27.065A13.237,13.237,0,1,0,35.509,18.672,13.251,13.251,0,0,0,22.272,5.436" transform="translate(-5.622 -3.225)" fill="#fff"/>
|
||||
<path id="Path_1581" data-name="Path 1581" d="M29.154,34.22A12.266,12.266,0,1,1,41.42,21.954,12.28,12.28,0,0,1,29.154,34.22m0-24.032A11.766,11.766,0,1,0,40.92,21.954,11.779,11.779,0,0,0,29.154,10.188" transform="translate(-11.244 -6.449)" fill="#fff"/>
|
||||
<path id="Path_1582" data-name="Path 1582" d="M36.035,35.94a10.7,10.7,0,1,1,10.7-10.7,10.717,10.717,0,0,1-10.7,10.7m0-21A10.295,10.295,0,1,0,46.33,25.235,10.307,10.307,0,0,0,36.035,14.941" transform="translate(-16.865 -9.675)" fill="#fff"/>
|
||||
<path id="Path_1583" data-name="Path 1583" d="M42.916,37.66a9.142,9.142,0,1,1,9.142-9.142,9.153,9.153,0,0,1-9.142,9.142m0-17.966a8.824,8.824,0,1,0,8.824,8.824,8.833,8.833,0,0,0-8.824-8.824" transform="translate(-22.486 -12.9)" fill="#fff"/>
|
||||
<path id="Path_1584" data-name="Path 1584" d="M49.8,39.379a7.58,7.58,0,1,1,7.58-7.58,7.589,7.589,0,0,1-7.58,7.58m0-14.933A7.353,7.353,0,1,0,57.151,31.8,7.361,7.361,0,0,0,49.8,24.446" transform="translate(-28.108 -16.124)" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
|
@ -5,6 +5,7 @@ import ReactDOM from "react-dom";
|
|||
import App from "./App";
|
||||
import ErrorBoundary from "./components/ErrorBoundary";
|
||||
import { EthereumProviderProvider } from "./contexts/EthereumProviderContext";
|
||||
import { TerraWalletProvider } from "./contexts/TerraWalletContext";
|
||||
import { theme } from "./muiTheme";
|
||||
|
||||
ReactDOM.render(
|
||||
|
@ -12,9 +13,11 @@ ReactDOM.render(
|
|||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline>
|
||||
<EthereumProviderProvider>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<App />
|
||||
</SnackbarProvider>
|
||||
<TerraWalletProvider>
|
||||
<SnackbarProvider maxSnack={3}>
|
||||
<App />
|
||||
</SnackbarProvider>
|
||||
</TerraWalletProvider>
|
||||
</EthereumProviderProvider>
|
||||
</CssBaseline>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*.js
|
|
@ -1,41 +1,132 @@
|
|||
import { ethers } from "ethers";
|
||||
import { UniEvmToken } from "./uniswap-core";
|
||||
import { QuickswapRouter } from "./quickswap";
|
||||
import { SingleAmmSwapRouter as UniswapV3Router } from "./uniswap-v3";
|
||||
|
||||
import { QuickswapRouter as MaticRouter } from "./quickswap";
|
||||
import { UniswapV3Router as EthRouter } from "./uniswap-v3";
|
||||
import { TerraUstTransfer as UstRouter } from "./terra-ust-transfer";
|
||||
import { HurricaneswapRouter as AvaxRouter } from "./hurricaneswap";
|
||||
import { PancakeswapRouter as BnbRouter } from "./pancakeswap";
|
||||
import {
|
||||
ETH_NETWORK_CHAIN_ID,
|
||||
POLYGON_NETWORK_CHAIN_ID,
|
||||
ETH_TOKEN_INFO,
|
||||
MATIC_TOKEN_INFO,
|
||||
AVAX_TOKEN_INFO,
|
||||
BNB_TOKEN_INFO,
|
||||
UST_TOKEN_INFO,
|
||||
} from "../utils/consts";
|
||||
import { addFixedAmounts, subtractFixedAmounts } from "../utils/math";
|
||||
import { UstLocation } from "./generic";
|
||||
import {
|
||||
ExactInParameters,
|
||||
ExactOutParameters,
|
||||
makeExactInParameters,
|
||||
makeExactOutParameters,
|
||||
} from "./uniswap-core";
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_POLYGON,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_BSC,
|
||||
CHAIN_ID_TERRA,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
export { PROTOCOL as PROTOCOL_UNISWAP_V2 } from "./uniswap-v2";
|
||||
export { PROTOCOL as PROTOCOL_UNISWAP_V3 } from "./uniswap-v3";
|
||||
export { PROTOCOL as PROTOCOL_TERRA_UST_TRANSFER } from "./terra-ust-transfer";
|
||||
|
||||
export const TERRA_UST = UST_TOKEN_INFO.address;
|
||||
|
||||
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);
|
||||
export function makeEvmProviderFromAddress(tokenAddress: string) {
|
||||
switch (tokenAddress) {
|
||||
case ETH_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 POLYGON_NETWORK_CHAIN_ID: {
|
||||
return new QuickswapRouter(provider);
|
||||
case MATIC_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);
|
||||
}
|
||||
case AVAX_TOKEN_INFO.address: {
|
||||
const url = process.env.REACT_APP_FUJI_PROVIDER;
|
||||
if (!url) {
|
||||
throw new Error("Could not find REACT_APP_FUJI_PROVIDER");
|
||||
}
|
||||
return new ethers.providers.StaticJsonRpcProvider(url);
|
||||
}
|
||||
case BNB_TOKEN_INFO.address: {
|
||||
const url = process.env.REACT_APP_BSC_PROVIDER;
|
||||
if (!url) {
|
||||
throw new Error("Could not find REACT_APP_BSC_PROVIDER");
|
||||
}
|
||||
return new ethers.providers.StaticJsonRpcProvider(url);
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized chain id");
|
||||
throw Error("unrecognized evm token address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getUstAddress(id: number): string {
|
||||
switch (id) {
|
||||
case ETH_NETWORK_CHAIN_ID: {
|
||||
return "0x36Ed51Afc79619b299b238898E72ce482600568a";
|
||||
export function getChainIdFromAddress(tokenAddress: string) {
|
||||
switch (tokenAddress) {
|
||||
case ETH_TOKEN_INFO.address: {
|
||||
return CHAIN_ID_ETH;
|
||||
}
|
||||
case POLYGON_NETWORK_CHAIN_ID: {
|
||||
return "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c";
|
||||
case MATIC_TOKEN_INFO.address: {
|
||||
return CHAIN_ID_POLYGON;
|
||||
}
|
||||
case AVAX_TOKEN_INFO.address: {
|
||||
return CHAIN_ID_AVAX;
|
||||
}
|
||||
case BNB_TOKEN_INFO.address: {
|
||||
return CHAIN_ID_BSC;
|
||||
}
|
||||
case UST_TOKEN_INFO.address: {
|
||||
return CHAIN_ID_TERRA;
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized evm token address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function makeRouter(tokenAddress: string, loc: UstLocation) {
|
||||
switch (tokenAddress) {
|
||||
case ETH_TOKEN_INFO.address: {
|
||||
const provider = makeEvmProviderFromAddress(tokenAddress);
|
||||
const router = new EthRouter(provider);
|
||||
await router.initialize(loc);
|
||||
return router;
|
||||
}
|
||||
case MATIC_TOKEN_INFO.address: {
|
||||
const provider = makeEvmProviderFromAddress(tokenAddress);
|
||||
const router = new MaticRouter(provider);
|
||||
await router.initialize(loc);
|
||||
return router;
|
||||
}
|
||||
case AVAX_TOKEN_INFO.address: {
|
||||
const provider = makeEvmProviderFromAddress(tokenAddress);
|
||||
const router = new AvaxRouter(provider);
|
||||
await router.initialize(loc);
|
||||
return router;
|
||||
}
|
||||
case BNB_TOKEN_INFO.address: {
|
||||
const provider = makeEvmProviderFromAddress(tokenAddress);
|
||||
const router = new BnbRouter(provider);
|
||||
await router.initialize(loc);
|
||||
return router;
|
||||
}
|
||||
case UST_TOKEN_INFO.address: {
|
||||
return new UstRouter();
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized chain id");
|
||||
|
@ -51,123 +142,101 @@ function splitSlippageInHalf(totalSlippage: string): string {
|
|||
.toString();
|
||||
}
|
||||
|
||||
interface RelayerFee {
|
||||
amount: ethers.BigNumber;
|
||||
export interface RelayerFee {
|
||||
amount: string;
|
||||
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;
|
||||
amountIn: string;
|
||||
ustAmountIn: string;
|
||||
minAmountOut: string;
|
||||
src: ExactInParameters | undefined;
|
||||
dst: ExactInParameters | undefined;
|
||||
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;
|
||||
amountOut: string;
|
||||
ustAmountIn: string;
|
||||
maxAmountIn: string;
|
||||
src: ExactOutParameters | undefined;
|
||||
dst: ExactOutParameters | undefined;
|
||||
relayerFee: RelayerFee;
|
||||
}
|
||||
|
||||
export class UniswapToUniswapQuoter {
|
||||
// providers
|
||||
srcProvider: ethers.providers.Provider;
|
||||
dstProvider: ethers.providers.Provider;
|
||||
|
||||
// networks
|
||||
srcNetwork: ethers.providers.Network;
|
||||
dstNetwork: ethers.providers.Network;
|
||||
// tokens
|
||||
tokenInAddress: string;
|
||||
tokenOutAddress: string;
|
||||
|
||||
// routers
|
||||
srcRouter: UniswapV3Router | QuickswapRouter;
|
||||
dstRouter: UniswapV3Router | QuickswapRouter;
|
||||
srcRouter: UstRouter | EthRouter | MaticRouter | AvaxRouter | BnbRouter;
|
||||
dstRouter: UstRouter | EthRouter | MaticRouter | AvaxRouter | BnbRouter;
|
||||
|
||||
// tokens
|
||||
srcTokenIn: UniEvmToken;
|
||||
srcTokenOut: UniEvmToken;
|
||||
dstTokenIn: UniEvmToken;
|
||||
dstTokenOut: UniEvmToken;
|
||||
async initialize(tokenInAddress: string, tokenOutAddress: string) {
|
||||
if (tokenInAddress !== this.tokenInAddress) {
|
||||
this.tokenInAddress = tokenInAddress;
|
||||
this.srcRouter = await makeRouter(tokenInAddress, UstLocation.Out);
|
||||
}
|
||||
|
||||
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];
|
||||
if (tokenOutAddress !== this.tokenOutAddress) {
|
||||
this.tokenOutAddress = tokenOutAddress;
|
||||
this.dstRouter = await makeRouter(tokenOutAddress, UstLocation.In);
|
||||
}
|
||||
}
|
||||
|
||||
async computeAndVerifySrcPoolAddress(): Promise<string> {
|
||||
return this.srcRouter.computeAndVerifyPoolAddress(
|
||||
this.srcTokenIn,
|
||||
this.srcTokenOut
|
||||
);
|
||||
return this.srcRouter.computeAndVerifyPoolAddress();
|
||||
}
|
||||
|
||||
async computeAndVerifyDstPoolAddress(): Promise<string> {
|
||||
return this.dstRouter.computeAndVerifyPoolAddress(
|
||||
this.dstTokenIn,
|
||||
this.dstTokenOut
|
||||
);
|
||||
return this.dstRouter.computeAndVerifyPoolAddress();
|
||||
}
|
||||
|
||||
computeSwapSlippage(slippage: string): string {
|
||||
if (this.isSrcUst() || this.isDstUst()) {
|
||||
return slippage;
|
||||
}
|
||||
|
||||
return splitSlippageInHalf(slippage);
|
||||
}
|
||||
|
||||
getRelayerFee(amount: string): RelayerFee {
|
||||
if (this.isSrcUst()) {
|
||||
return {
|
||||
amount: this.srcRouter.computeUnitAmountOut(amount),
|
||||
tokenAddress: TERRA_UST, // TODO: make sure this is the right address for bridge transfer?
|
||||
};
|
||||
}
|
||||
|
||||
const relayerFee: RelayerFee = {
|
||||
amount: this.srcRouter.computeUnitAmountOut(amount),
|
||||
tokenAddress: this.srcRouter.getTokenOutAddress(),
|
||||
};
|
||||
return relayerFee;
|
||||
}
|
||||
|
||||
makeSrcExactInParameters(
|
||||
amountIn: string,
|
||||
minAmountOut: string
|
||||
): ExactInParameters | undefined {
|
||||
if (this.isSrcUst()) {
|
||||
return undefined;
|
||||
}
|
||||
// @ts-ignore
|
||||
return makeExactInParameters(this.srcRouter, amountIn, minAmountOut);
|
||||
}
|
||||
|
||||
makeDstExactInParameters(
|
||||
amountIn: string,
|
||||
minAmountOut: string
|
||||
): ExactInParameters | undefined {
|
||||
if (this.isDstUst()) {
|
||||
return undefined;
|
||||
}
|
||||
// @ts-ignore
|
||||
return makeExactInParameters(this.dstRouter, amountIn, minAmountOut);
|
||||
}
|
||||
|
||||
async computeExactInParameters(
|
||||
|
@ -175,71 +244,69 @@ export class UniswapToUniswapQuoter {
|
|||
slippage: string,
|
||||
relayerFeeUst: string
|
||||
): Promise<ExactInCrossParameters> {
|
||||
const singleSlippage = splitSlippageInHalf(slippage);
|
||||
const singleSlippage = this.computeSwapSlippage(slippage);
|
||||
|
||||
// src quote
|
||||
const srcRouter = this.srcRouter;
|
||||
const srcTokenIn = this.srcTokenIn;
|
||||
const srcTokenOut = this.srcTokenOut;
|
||||
const srcMinAmountOut = await srcRouter.fetchQuoteAmountOut(
|
||||
srcTokenIn,
|
||||
srcTokenOut,
|
||||
const srcMinAmountOut = await srcRouter.fetchExactInQuote(
|
||||
amountIn,
|
||||
singleSlippage
|
||||
);
|
||||
|
||||
// dst quote
|
||||
const dstRouter = this.dstRouter;
|
||||
const dstAmountIn = this.srcTokenOut.formatAmount(srcMinAmountOut);
|
||||
const dstAmountIn = srcMinAmountOut; //srcRouter.formatAmountOut(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(
|
||||
const dstAmountInAfterFee = subtractFixedAmounts(
|
||||
dstAmountIn,
|
||||
relayerFeeUst
|
||||
relayerFeeUst,
|
||||
dstRouter.getTokenInDecimals()
|
||||
);
|
||||
|
||||
const dstMinAmountOut = await dstRouter.fetchQuoteAmountOut(
|
||||
dstTokenIn,
|
||||
dstTokenOut,
|
||||
const dstMinAmountOut = await dstRouter.fetchExactInQuote(
|
||||
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()],
|
||||
};
|
||||
|
||||
// organize parameters
|
||||
const params: ExactInCrossParameters = {
|
||||
src: srcParameters,
|
||||
dst: dstParameters,
|
||||
relayerFee: {
|
||||
amount: dstTokenIn.computeUnitAmount(relayerFeeUst),
|
||||
tokenAddress: this.dstTokenIn.getAddress(),
|
||||
},
|
||||
amountIn: amountIn,
|
||||
ustAmountIn: dstAmountInAfterFee,
|
||||
minAmountOut: dstMinAmountOut,
|
||||
src: this.makeSrcExactInParameters(amountIn, srcMinAmountOut),
|
||||
dst: this.makeDstExactInParameters(dstAmountInAfterFee, dstMinAmountOut),
|
||||
relayerFee: this.getRelayerFee(relayerFeeUst),
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
makeSrcExactOutParameters(
|
||||
amountOut: string,
|
||||
maxAmountIn: string
|
||||
): ExactOutParameters | undefined {
|
||||
if (this.isSrcUst()) {
|
||||
return undefined;
|
||||
}
|
||||
// @ts-ignore
|
||||
return makeExactOutParameters(this.srcRouter, amountOut, maxAmountIn);
|
||||
}
|
||||
|
||||
makeDstExactOutParameters(
|
||||
amountOut: string,
|
||||
maxAmountIn: string
|
||||
): ExactOutParameters | undefined {
|
||||
if (this.isDstUst()) {
|
||||
return undefined;
|
||||
}
|
||||
// @ts-ignore
|
||||
return makeExactOutParameters(this.dstRouter, amountOut, maxAmountIn);
|
||||
}
|
||||
|
||||
async computeExactOutParameters(
|
||||
amountOut: string,
|
||||
slippage: string,
|
||||
|
@ -249,69 +316,86 @@ export class UniswapToUniswapQuoter {
|
|||
|
||||
// dst quote first
|
||||
const dstRouter = this.dstRouter;
|
||||
const dstTokenIn = this.dstTokenIn;
|
||||
const dstTokenOut = this.dstTokenOut;
|
||||
const dstMaxAmountIn = await dstRouter.fetchQuoteAmountIn(
|
||||
dstTokenIn,
|
||||
dstTokenOut,
|
||||
const dstMaxAmountIn = await dstRouter.fetchExactOutQuote(
|
||||
amountOut,
|
||||
singleSlippage
|
||||
);
|
||||
|
||||
// src quote
|
||||
const srcRouter = this.srcRouter;
|
||||
const srcAmountOut = this.dstTokenIn.formatAmount(dstMaxAmountIn);
|
||||
const srcAmountOut = 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(
|
||||
const srcAmountOutBeforeFee = addFixedAmounts(
|
||||
srcAmountOut,
|
||||
relayerFeeUst
|
||||
relayerFeeUst,
|
||||
srcRouter.getTokenOutDecimals()
|
||||
);
|
||||
|
||||
const srcMaxAmountIn = await srcRouter.fetchQuoteAmountIn(
|
||||
srcTokenIn,
|
||||
srcTokenOut,
|
||||
const srcMaxAmountIn = await srcRouter.fetchExactOutQuote(
|
||||
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()],
|
||||
};
|
||||
|
||||
// organize parameters
|
||||
const params: ExactOutCrossParameters = {
|
||||
src: srcParameters,
|
||||
dst: dstParameters,
|
||||
relayerFee: {
|
||||
amount: dstTokenIn.computeUnitAmount(relayerFeeUst),
|
||||
tokenAddress: this.dstTokenIn.getAddress(),
|
||||
},
|
||||
amountOut: amountOut,
|
||||
ustAmountIn: dstMaxAmountIn,
|
||||
maxAmountIn: srcMaxAmountIn,
|
||||
src: this.makeSrcExactOutParameters(
|
||||
srcAmountOutBeforeFee,
|
||||
srcMaxAmountIn
|
||||
),
|
||||
dst: this.makeDstExactOutParameters(amountOut, dstMaxAmountIn),
|
||||
relayerFee: this.getRelayerFee(relayerFeeUst),
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
setDeadlines(deadline: string): void {
|
||||
this.srcRouter.setDeadline(deadline);
|
||||
this.dstRouter.setDeadline(deadline);
|
||||
if (!this.isSrcUst()) {
|
||||
// @ts-ignore
|
||||
this.srcRouter.setDeadline(deadline);
|
||||
}
|
||||
if (!this.isDstUst()) {
|
||||
// @ts-ignore
|
||||
this.dstRouter.setDeadline(deadline);
|
||||
}
|
||||
}
|
||||
|
||||
isSrcUst(): boolean {
|
||||
return this.tokenInAddress === TERRA_UST;
|
||||
}
|
||||
|
||||
isDstUst(): boolean {
|
||||
return this.tokenOutAddress === TERRA_UST;
|
||||
}
|
||||
|
||||
getSrcEvmProvider(): ethers.providers.Provider | undefined {
|
||||
if (this.isSrcUst()) {
|
||||
return undefined;
|
||||
}
|
||||
// @ts-ignore
|
||||
return this.srcRouter.getProvider();
|
||||
}
|
||||
|
||||
getDstEvmProvider(): ethers.providers.Provider | undefined {
|
||||
if (this.isDstUst()) {
|
||||
return undefined;
|
||||
}
|
||||
// @ts-ignore
|
||||
return this.dstRouter.getProvider();
|
||||
}
|
||||
|
||||
getSrcChainId(): ChainId {
|
||||
return getChainIdFromAddress(this.tokenInAddress);
|
||||
}
|
||||
|
||||
getDstChainId(): ChainId {
|
||||
return getChainIdFromAddress(this.tokenOutAddress);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,38 @@
|
|||
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 enum UstLocation {
|
||||
In = 1,
|
||||
Out,
|
||||
}
|
||||
|
||||
export abstract class RouterCore {
|
||||
abstract computeAndVerifyPoolAddress(): Promise<string>;
|
||||
|
||||
abstract computePoolAddress(): string;
|
||||
|
||||
//abstract computeUnitAmountIn(amount: string): string;
|
||||
|
||||
abstract computeUnitAmountOut(amount: string): string;
|
||||
|
||||
abstract fetchExactInQuote(
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<string>;
|
||||
|
||||
abstract fetchExactOutQuote(
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<string>;
|
||||
|
||||
abstract formatAmountIn(amount: string): string;
|
||||
|
||||
abstract formatAmountOut(amount: string): string;
|
||||
|
||||
abstract getProtocol(): string;
|
||||
|
||||
abstract getTokenInDecimals(): number;
|
||||
|
||||
abstract getTokenOutDecimals(): number;
|
||||
|
||||
abstract getTokenOutAddress(): string;
|
||||
}
|
||||
|
||||
export abstract class GenericToken {
|
||||
|
@ -9,16 +40,3 @@ export abstract class GenericToken {
|
|||
|
||||
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,26 @@
|
|||
import { ethers } from "ethers";
|
||||
|
||||
import { AVAX_TOKEN_INFO } from "../utils/consts";
|
||||
import { UstLocation } from "./generic";
|
||||
import { UniswapV2Router } from "./uniswap-v2";
|
||||
|
||||
export { PROTOCOL } from "./uniswap-v2";
|
||||
|
||||
const HURRICANESWAP_FACTORY_ADDRESS = "";
|
||||
|
||||
export class HurricaneswapRouter extends UniswapV2Router {
|
||||
constructor(provider: ethers.providers.Provider) {
|
||||
super(provider);
|
||||
super.setFactoryAddress(HURRICANESWAP_FACTORY_ADDRESS);
|
||||
}
|
||||
|
||||
async initialize(ustLocation: UstLocation): Promise<void> {
|
||||
await super.initializeTokens(AVAX_TOKEN_INFO, ustLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
computePoolAddress(): string {
|
||||
// cannot find factory address on testnet
|
||||
return "0xD8087870E8869e45154189d434DF61C19e77ae30";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { ethers } from "ethers";
|
||||
|
||||
import { BNB_TOKEN_INFO } from "../utils/consts";
|
||||
import { UstLocation } from "./generic";
|
||||
import { UniswapV2Router } from "./uniswap-v2";
|
||||
|
||||
export { PROTOCOL } from "./uniswap-v2";
|
||||
|
||||
const PANCAKESWAP_FACTORY_ADDRESS = "";
|
||||
|
||||
export class PancakeswapRouter extends UniswapV2Router {
|
||||
constructor(provider: ethers.providers.Provider) {
|
||||
super(provider);
|
||||
super.setFactoryAddress(PANCAKESWAP_FACTORY_ADDRESS);
|
||||
}
|
||||
|
||||
async initialize(ustLocation: UstLocation): Promise<void> {
|
||||
await super.initializeTokens(BNB_TOKEN_INFO, ustLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
computePoolAddress(): string {
|
||||
// cannot find factory address on testnet
|
||||
return "0x8682096d4A2a2f3cd63147D05e4BAB47634e2AD1";
|
||||
}
|
||||
}
|
|
@ -1,12 +1,21 @@
|
|||
import { ethers } from "ethers";
|
||||
import { QUICKSWAP_FACTORY_ADDRESS } from "../utils/consts";
|
||||
import { SingleAmmSwapRouter } from "./uniswap-v2";
|
||||
|
||||
import { MATIC_TOKEN_INFO } from "../utils/consts";
|
||||
import { UstLocation } from "./generic";
|
||||
import { UniswapV2Router } from "./uniswap-v2";
|
||||
|
||||
export { PROTOCOL } from "./uniswap-v2";
|
||||
|
||||
export class QuickswapRouter extends SingleAmmSwapRouter {
|
||||
const QUICKSWAP_FACTORY_ADDRESS = "0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32";
|
||||
|
||||
export class QuickswapRouter extends UniswapV2Router {
|
||||
constructor(provider: ethers.providers.Provider) {
|
||||
super(provider);
|
||||
super.setFactoryAddress(QUICKSWAP_FACTORY_ADDRESS);
|
||||
}
|
||||
|
||||
async initialize(ustLocation: UstLocation): Promise<void> {
|
||||
await super.initializeTokens(MATIC_TOKEN_INFO, ustLocation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import { Dec, Int } from "@terra-money/terra.js";
|
||||
|
||||
import { UST_TOKEN_INFO } from "../utils/consts";
|
||||
import { RouterCore } from "./generic";
|
||||
|
||||
export const PROTOCOL = "TerraUstTransfer";
|
||||
|
||||
const UST_DECIMALS = 6;
|
||||
|
||||
const UST_AMOUNT_MULTIPLIER = "1000000";
|
||||
|
||||
export class TerraUstTransfer extends RouterCore {
|
||||
computePoolAddress(): string {
|
||||
return UST_TOKEN_INFO.address;
|
||||
}
|
||||
|
||||
computeAndVerifyPoolAddress(): Promise<string> {
|
||||
return new Promise<string>((resolve) => {
|
||||
return resolve(this.computePoolAddress());
|
||||
});
|
||||
}
|
||||
|
||||
formatAmountIn(amount: string): string {
|
||||
const formatted = new Dec(amount).div(UST_AMOUNT_MULTIPLIER);
|
||||
return formatted.toString();
|
||||
}
|
||||
|
||||
formatAmountOut(amount: string): string {
|
||||
return this.formatAmountIn(amount);
|
||||
}
|
||||
|
||||
computeUnitAmountIn(amount: string): string {
|
||||
const unitified = new Dec(amount).mul(UST_AMOUNT_MULTIPLIER);
|
||||
return new Int(unitified.toString()).toString();
|
||||
}
|
||||
|
||||
computeUnitAmountOut(amount: string): string {
|
||||
return this.computeUnitAmountIn(amount);
|
||||
}
|
||||
|
||||
getProtocol(): string {
|
||||
return PROTOCOL;
|
||||
}
|
||||
|
||||
async fetchExactInQuote(amountIn: string, slippage: string): Promise<string> {
|
||||
return amountIn;
|
||||
}
|
||||
|
||||
async fetchExactOutQuote(
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<string> {
|
||||
return amountOut;
|
||||
}
|
||||
|
||||
getTokenInDecimals(): number {
|
||||
return UST_DECIMALS;
|
||||
}
|
||||
|
||||
getTokenOutDecimals(): number {
|
||||
return UST_DECIMALS;
|
||||
}
|
||||
|
||||
getTokenOutAddress(): string {
|
||||
return this.computePoolAddress();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
//@ts-nocheck
|
||||
import { ethers } from "ethers";
|
||||
import { CurrencyAmount, Token } from "@uniswap/sdk-core";
|
||||
|
||||
import { EvmToken } from "./evm";
|
||||
import { RouterCore, UstLocation } from "./generic";
|
||||
import { TokenInfo } from "../utils/consts";
|
||||
|
||||
export function computeTradeDeadline(deadline: string): ethers.BigNumber {
|
||||
return ethers.BigNumber.from(Math.floor(Date.now() / 1000)).add(deadline);
|
||||
|
@ -78,46 +81,112 @@ export async function makeUniEvmToken(
|
|||
return new UniEvmToken(chainId, erc20);
|
||||
}
|
||||
|
||||
export abstract class UniswapRouterCore {
|
||||
function stringToBigNumber(value: string): ethers.BigNumber {
|
||||
return ethers.BigNumber.from(value);
|
||||
}
|
||||
|
||||
export interface ExactInParameters {
|
||||
protocol: string;
|
||||
amountIn: ethers.BigNumber;
|
||||
minAmountOut: ethers.BigNumber;
|
||||
deadline: ethers.BigNumber;
|
||||
poolFee: string;
|
||||
path: [string, string];
|
||||
}
|
||||
|
||||
export interface ExactOutParameters {
|
||||
protocol: string;
|
||||
amountOut: ethers.BigNumber;
|
||||
maxAmountIn: ethers.BigNumber;
|
||||
deadline: ethers.BigNumber;
|
||||
poolFee: string;
|
||||
path: [string, string];
|
||||
}
|
||||
|
||||
export function makeExactInParameters(
|
||||
router: UniswapRouterCore,
|
||||
amountIn: string,
|
||||
minAmountOut: string
|
||||
): ExactInParameters {
|
||||
const params: ExactInParameters = {
|
||||
protocol: router.getProtocol(),
|
||||
amountIn: router.tokenIn.computeUnitAmount(amountIn),
|
||||
minAmountOut: router.tokenOut.computeUnitAmount(minAmountOut),
|
||||
poolFee: router.getPoolFee(),
|
||||
deadline: router.getTradeDeadline(),
|
||||
path: [router.tokenIn.getAddress(), router.tokenOut.getAddress()],
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
export function makeExactOutParameters(
|
||||
router: UniswapRouterCore,
|
||||
amountOut: string,
|
||||
maxAmountIn: string
|
||||
): ExactOutParameters {
|
||||
const params: ExactOutParameters = {
|
||||
protocol: router.getProtocol(),
|
||||
amountOut: router.tokenOut.computeUnitAmount(amountOut),
|
||||
maxAmountIn: router.tokenIn.computeUnitAmount(maxAmountIn),
|
||||
poolFee: router.getPoolFee(),
|
||||
deadline: router.getTradeDeadline(),
|
||||
path: [router.tokenIn.getAddress(), router.tokenOut.getAddress()],
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
export abstract class UniswapRouterCore extends RouterCore {
|
||||
provider: ethers.providers.Provider;
|
||||
network: ethers.providers.Network;
|
||||
|
||||
// wormhole
|
||||
chainId: number;
|
||||
|
||||
// tokens
|
||||
tokenIn: UniEvmToken;
|
||||
tokenOut: UniEvmToken;
|
||||
|
||||
// params
|
||||
deadline: string = "";
|
||||
|
||||
constructor(provider: ethers.providers.Provider) {
|
||||
super();
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public async makeToken(tokenAddress: string): Promise<UniEvmToken> {
|
||||
const network = await this.provider.getNetwork();
|
||||
return makeUniEvmToken(this.provider, network.chainId, tokenAddress);
|
||||
public getProvider(): ethers.providers.Provider {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
abstract computePoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): string;
|
||||
public async initializeTokens(
|
||||
tokenInfo: TokenInfo,
|
||||
ustLocation: UstLocation
|
||||
): Promise<void> {
|
||||
this.network = await this.provider.getNetwork();
|
||||
|
||||
abstract computeAndVerifyPoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): Promise<string>;
|
||||
const network = this.network;
|
||||
|
||||
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;
|
||||
if (ustLocation === UstLocation.Out) {
|
||||
[this.tokenIn, this.tokenOut] = await Promise.all([
|
||||
makeUniEvmToken(this.provider, network.chainId, tokenInfo.address),
|
||||
makeUniEvmToken(
|
||||
this.provider,
|
||||
network.chainId,
|
||||
tokenInfo.ustPairedAddress
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
[this.tokenIn, this.tokenOut] = await Promise.all([
|
||||
makeUniEvmToken(
|
||||
this.provider,
|
||||
network.chainId,
|
||||
tokenInfo.ustPairedAddress
|
||||
),
|
||||
makeUniEvmToken(this.provider, network.chainId, tokenInfo.address),
|
||||
]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public getPoolFee(): string {
|
||||
return "";
|
||||
|
@ -130,4 +199,36 @@ export abstract class UniswapRouterCore {
|
|||
public getTradeDeadline(): ethers.BigNumber {
|
||||
return computeTradeDeadline(this.deadline);
|
||||
}
|
||||
|
||||
/*
|
||||
public computeUnitAmountIn(amount: string): string {
|
||||
return this.tokenIn.computeUnitAmount(amount).toString();
|
||||
}
|
||||
*/
|
||||
|
||||
public computeUnitAmountOut(amount: string): string {
|
||||
return this.tokenOut.computeUnitAmount(amount).toString();
|
||||
}
|
||||
|
||||
public formatAmountIn(amount: string): string {
|
||||
return this.tokenIn.formatAmount(stringToBigNumber(amount));
|
||||
}
|
||||
|
||||
public formatAmountOut(amount: string): string {
|
||||
return this.tokenOut.formatAmount(stringToBigNumber(amount));
|
||||
}
|
||||
|
||||
public getTokenInDecimals(): number {
|
||||
return this.tokenIn.getDecimals();
|
||||
}
|
||||
|
||||
public getTokenOutDecimals(): number {
|
||||
return this.tokenOut.getDecimals();
|
||||
}
|
||||
|
||||
public getTokenOutAddress(): string {
|
||||
return this.tokenOut.getAddress();
|
||||
}
|
||||
|
||||
abstract getProtocol(): string;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,19 @@ 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";
|
||||
import { UniswapRouterCore } from "./uniswap-core";
|
||||
|
||||
export const PROTOCOL = "UniswapV2";
|
||||
|
||||
export class SingleAmmSwapRouter extends UniswapRouterCore {
|
||||
// uniswap v3 (ethereum)
|
||||
//export const UNISWAP_V3_FACTORY_ADDRESS = '0x1F98431c8aD98523631AE4a59f267346ea31F984';
|
||||
//export const UNISWAP_V3_ROUTER_ADDRESS = '0xE592427A0AEce92De3Edee1F18E0157C05861564';
|
||||
|
||||
// quickswap (polygon)
|
||||
export const QUICKSWAP_V2_ROUTER_ADDRESS =
|
||||
"0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff";
|
||||
|
||||
export class UniswapV2Router extends UniswapRouterCore {
|
||||
factoryAddress: string;
|
||||
pairContract: ethers.Contract;
|
||||
pair: Pair;
|
||||
|
@ -17,23 +25,20 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
return;
|
||||
}
|
||||
|
||||
computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string {
|
||||
computePoolAddress(): string {
|
||||
if (this.factoryAddress === undefined) {
|
||||
throw Error("factoryAddress is undefined. use setFactoryAddress");
|
||||
}
|
||||
|
||||
return computePairAddress({
|
||||
factoryAddress: this.factoryAddress,
|
||||
tokenA: tokenIn.getUniToken(),
|
||||
tokenB: tokenOut.getUniToken(),
|
||||
tokenA: this.tokenIn.getUniToken(),
|
||||
tokenB: this.tokenOut.getUniToken(),
|
||||
});
|
||||
}
|
||||
|
||||
async computeAndVerifyPoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): Promise<string> {
|
||||
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
async computeAndVerifyPoolAddress(): Promise<string> {
|
||||
const pairAddress = this.computePoolAddress();
|
||||
|
||||
// verify by attempting to call factory()
|
||||
const poolContract = new ethers.Contract(
|
||||
|
@ -46,8 +51,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
return pairAddress;
|
||||
}
|
||||
|
||||
async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise<Pair> {
|
||||
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
async createPool(): Promise<Pair> {
|
||||
const pairAddress = this.computePoolAddress();
|
||||
|
||||
const pairContract = new ethers.Contract(
|
||||
pairAddress,
|
||||
|
@ -63,6 +68,9 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
const reserve0 = reserves._reserve0.toString();
|
||||
const reserve1 = reserves._reserve1.toString();
|
||||
|
||||
const tokenIn = this.tokenIn;
|
||||
const tokenOut = this.tokenOut;
|
||||
|
||||
if (token0.toLowerCase() === tokenIn.getAddress().toLowerCase()) {
|
||||
return new Pair(
|
||||
CurrencyAmount.fromRawAmount(tokenIn.getUniToken(), reserve0),
|
||||
|
@ -76,15 +84,13 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
);
|
||||
}
|
||||
|
||||
async fetchQuoteAmountOut(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountIn: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
async fetchExactInQuote(amountIn: string, slippage: string): Promise<string> {
|
||||
// create pool
|
||||
const pair = await this.createPool(tokenIn, tokenOut);
|
||||
const pair = await this.createPool();
|
||||
|
||||
// let's get that quote
|
||||
const tokenIn = this.tokenIn;
|
||||
const tokenOut = this.tokenOut;
|
||||
|
||||
const route = new Route(
|
||||
[pair],
|
||||
|
@ -108,18 +114,24 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
.mulUnsafe(slippageMultiplier)
|
||||
.round(decimals);
|
||||
|
||||
return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString());
|
||||
/*
|
||||
return tokenOut
|
||||
.computeUnitAmount(minAmountOutWithSlippage.toString())
|
||||
.toString();
|
||||
*/
|
||||
return minAmountOutWithSlippage.toString();
|
||||
}
|
||||
|
||||
async fetchQuoteAmountIn(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
async fetchExactOutQuote(
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
): Promise<string> {
|
||||
// create pool
|
||||
const pair = await this.createPool(tokenIn, tokenOut);
|
||||
const pair = await this.createPool();
|
||||
|
||||
// let's get that quote
|
||||
const tokenIn = this.tokenIn;
|
||||
const tokenOut = this.tokenOut;
|
||||
|
||||
const route = new Route(
|
||||
[pair],
|
||||
|
@ -142,7 +154,12 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
.divUnsafe(slippageDivisor)
|
||||
.round(decimals);
|
||||
|
||||
return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString());
|
||||
/*
|
||||
return tokenIn
|
||||
.computeUnitAmount(maxAmountInWithSlippage.toString())
|
||||
.toString();
|
||||
*/
|
||||
return maxAmountInWithSlippage.toString();
|
||||
}
|
||||
|
||||
getProtocol(): string {
|
||||
|
|
|
@ -13,12 +13,15 @@ import {
|
|||
Trade,
|
||||
} from "@uniswap/v3-sdk";
|
||||
|
||||
import { UniEvmToken, UniswapRouterCore } from "./uniswap-core";
|
||||
import { UNISWAP_V3_FACTORY_ADDRESS } from "../utils/consts";
|
||||
import { UniswapRouterCore } from "./uniswap-core";
|
||||
import { ETH_TOKEN_INFO } from "../utils/consts";
|
||||
import { UstLocation } from "./generic";
|
||||
|
||||
export const PROTOCOL = "UniswapV3";
|
||||
|
||||
export class SingleAmmSwapRouter extends UniswapRouterCore {
|
||||
const UNISWAP_V3_FACTORY_ADDRESS = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
|
||||
|
||||
export class UniswapV3Router extends UniswapRouterCore {
|
||||
poolContract: ethers.Contract;
|
||||
pool: Pool;
|
||||
poolFee: FeeAmount;
|
||||
|
@ -30,24 +33,26 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
this.poolFee = FeeAmount.MEDIUM;
|
||||
}
|
||||
|
||||
async initialize(ustLocation: UstLocation): Promise<void> {
|
||||
await this.initializeTokens(ETH_TOKEN_INFO, ustLocation);
|
||||
return;
|
||||
}
|
||||
|
||||
getPoolFee(): string {
|
||||
return this.poolFee.toString();
|
||||
}
|
||||
|
||||
computePoolAddress(tokenIn: UniEvmToken, tokenOut: UniEvmToken): string {
|
||||
computePoolAddress(): string {
|
||||
return computePoolAddress({
|
||||
factoryAddress: UNISWAP_V3_FACTORY_ADDRESS,
|
||||
fee: this.poolFee,
|
||||
tokenA: tokenIn.getUniToken(),
|
||||
tokenB: tokenOut.getUniToken(),
|
||||
tokenA: this.tokenIn.getUniToken(),
|
||||
tokenB: this.tokenOut.getUniToken(),
|
||||
});
|
||||
}
|
||||
|
||||
async computeAndVerifyPoolAddress(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken
|
||||
): Promise<string> {
|
||||
const pairAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
async computeAndVerifyPoolAddress(): Promise<string> {
|
||||
const pairAddress = this.computePoolAddress();
|
||||
|
||||
// verify by attempting to call factory()
|
||||
const poolContract = new ethers.Contract(
|
||||
|
@ -60,8 +65,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
return pairAddress;
|
||||
}
|
||||
|
||||
async createPool(tokenIn: UniEvmToken, tokenOut: UniEvmToken): Promise<Pool> {
|
||||
const poolAddress = this.computePoolAddress(tokenIn, tokenOut);
|
||||
async createPool(): Promise<Pool> {
|
||||
const poolAddress = this.computePoolAddress();
|
||||
|
||||
const poolContract = new ethers.Contract(
|
||||
poolAddress,
|
||||
|
@ -103,8 +108,8 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
];
|
||||
|
||||
return new Pool(
|
||||
tokenIn.getUniToken(),
|
||||
tokenOut.getUniToken(),
|
||||
this.tokenIn.getUniToken(),
|
||||
this.tokenOut.getUniToken(),
|
||||
this.poolFee,
|
||||
sqrtPriceX96.toString(), //note the description discrepancy - sqrtPriceX96 and sqrtRatioX96 are interchangable values
|
||||
liquidity,
|
||||
|
@ -114,13 +119,15 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
}
|
||||
|
||||
async computeTradeExactIn(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amount: string
|
||||
): Promise<Trade<Token, Token, TradeType.EXACT_INPUT>> {
|
||||
// create pool
|
||||
const pool = await this.createPool(tokenIn, tokenOut);
|
||||
const pool = await this.createPool();
|
||||
|
||||
// let's get that quote
|
||||
const tokenIn = this.tokenIn;
|
||||
const tokenOut = this.tokenOut;
|
||||
|
||||
const amountIn = tokenIn.computeUnitAmount(amount);
|
||||
|
||||
const route = new Route(
|
||||
|
@ -136,13 +143,15 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
}
|
||||
|
||||
async computeTradeExactOut(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amount: string
|
||||
): Promise<Trade<Token, Token, TradeType.EXACT_OUTPUT>> {
|
||||
// create pool
|
||||
const pool = await this.createPool(tokenIn, tokenOut);
|
||||
const pool = await this.createPool();
|
||||
|
||||
// let's get that quote
|
||||
const tokenIn = this.tokenIn;
|
||||
const tokenOut = this.tokenOut;
|
||||
|
||||
const amountOut = tokenOut.computeUnitAmount(amount);
|
||||
|
||||
const route = new Route(
|
||||
|
@ -160,15 +169,11 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
);
|
||||
}
|
||||
|
||||
async fetchQuoteAmountOut(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
amountIn: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
async fetchExactInQuote(amountIn: string, slippage: string): Promise<string> {
|
||||
// get the quote
|
||||
const trade = await this.computeTradeExactIn(tokenIn, tokenOut, amountIn);
|
||||
const trade = await this.computeTradeExactIn(amountIn);
|
||||
|
||||
const tokenOut = this.tokenOut;
|
||||
const decimals = tokenOut.getDecimals();
|
||||
|
||||
// calculate output amount with slippage
|
||||
|
@ -183,18 +188,22 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
.mulUnsafe(slippageMultiplier)
|
||||
.round(decimals);
|
||||
|
||||
return tokenOut.computeUnitAmount(minAmountOutWithSlippage.toString());
|
||||
/*
|
||||
return tokenOut
|
||||
.computeUnitAmount(minAmountOutWithSlippage.toString())
|
||||
.toString();
|
||||
*/
|
||||
return minAmountOutWithSlippage.toString();
|
||||
}
|
||||
|
||||
async fetchQuoteAmountIn(
|
||||
tokenIn: UniEvmToken,
|
||||
tokenOut: UniEvmToken,
|
||||
async fetchExactOutQuote(
|
||||
amountOut: string,
|
||||
slippage: string
|
||||
): Promise<ethers.BigNumber> {
|
||||
): Promise<string> {
|
||||
// get the quote
|
||||
const trade = await this.computeTradeExactOut(tokenIn, tokenOut, amountOut);
|
||||
const trade = await this.computeTradeExactOut(amountOut);
|
||||
|
||||
const tokenIn = this.tokenIn;
|
||||
const decimals = tokenIn.getDecimals();
|
||||
|
||||
// calculate output amount with slippage
|
||||
|
@ -209,7 +218,12 @@ export class SingleAmmSwapRouter extends UniswapRouterCore {
|
|||
.divUnsafe(slippageDivisor)
|
||||
.round(decimals);
|
||||
|
||||
return tokenIn.computeUnitAmount(maxAmountInWithSlippage.toString());
|
||||
/*
|
||||
return tokenIn
|
||||
.computeUnitAmount(maxAmountInWithSlippage.toString())
|
||||
.toString();
|
||||
*/
|
||||
return maxAmountInWithSlippage.toString();
|
||||
}
|
||||
|
||||
getProtocol(): string {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*.js
|
|
@ -0,0 +1,111 @@
|
|||
import { ethers } from "ethers";
|
||||
import { TransactionReceipt } from "@ethersproject/abstract-provider";
|
||||
|
||||
import {
|
||||
EVM_ETH_NETWORK_CHAIN_ID,
|
||||
EVM_POLYGON_NETWORK_CHAIN_ID,
|
||||
EVM_AVAX_NETWORK_CHAIN_ID,
|
||||
//EVM_BSC_NETWORK_CHAIN_ID,
|
||||
} from "../utils/consts";
|
||||
|
||||
export const CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559 = {
|
||||
gasLimit: "694200",
|
||||
//maxFeePerGas: "250000000000",
|
||||
maxFeePerGas: "100420690000",
|
||||
maxPriorityFeePerGas: "1690000000",
|
||||
};
|
||||
|
||||
export const CROSSCHAINSWAP_GAS_PARAMETERS_EVM = {
|
||||
gasLimit: "694200",
|
||||
//gasPrice: "250000000000",
|
||||
gasPrice: "20420690000",
|
||||
};
|
||||
|
||||
export const EVM_EIP1559_CHAIN_IDS = [
|
||||
EVM_ETH_NETWORK_CHAIN_ID,
|
||||
EVM_POLYGON_NETWORK_CHAIN_ID,
|
||||
EVM_AVAX_NETWORK_CHAIN_ID,
|
||||
];
|
||||
|
||||
export async function getEvmGasParametersForContract(
|
||||
contract: ethers.Contract
|
||||
): Promise<any> {
|
||||
const chainId = await getChainIdFromContract(contract);
|
||||
|
||||
if (EVM_EIP1559_CHAIN_IDS.indexOf(chainId) >= 0) {
|
||||
return CROSSCHAINSWAP_GAS_PARAMETERS_EIP1559;
|
||||
}
|
||||
|
||||
return CROSSCHAINSWAP_GAS_PARAMETERS_EVM;
|
||||
}
|
||||
|
||||
async function getChainIdFromContract(
|
||||
contract: ethers.Contract
|
||||
): Promise<number> {
|
||||
const network = await contract.provider.getNetwork();
|
||||
return network.chainId;
|
||||
}
|
||||
|
||||
// exact in
|
||||
//
|
||||
export async function evmSwapExactInFromVaaNative(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const gasParams = await getEvmGasParametersForContract(
|
||||
swapContractWithSigner
|
||||
);
|
||||
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactNativeIn(
|
||||
signedVaa,
|
||||
gasParams
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
export async function evmSwapExactInFromVaaToken(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const gasParams = await getEvmGasParametersForContract(
|
||||
swapContractWithSigner
|
||||
);
|
||||
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactIn(
|
||||
signedVaa,
|
||||
gasParams
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
// exact out
|
||||
//
|
||||
export async function evmSwapExactOutFromVaaNative(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const gasParams = await getEvmGasParametersForContract(
|
||||
swapContractWithSigner
|
||||
);
|
||||
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactNativeOut(
|
||||
signedVaa,
|
||||
gasParams
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
export async function evmSwapExactOutFromVaaToken(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const gasParams = await getEvmGasParametersForContract(
|
||||
swapContractWithSigner
|
||||
);
|
||||
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactOut(
|
||||
signedVaa,
|
||||
gasParams
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
//@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,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_POLYGON,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_BSC,
|
||||
CHAIN_ID_TERRA,
|
||||
getEmitterAddressEth,
|
||||
hexToUint8Array,
|
||||
nativeToHexString,
|
||||
|
@ -11,7 +15,6 @@ import {
|
|||
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,
|
||||
|
@ -21,27 +24,48 @@ import {
|
|||
UniswapToUniswapQuoter,
|
||||
} from "../route/cross-quote";
|
||||
import {
|
||||
TOKEN_BRIDGE_ADDRESS_ETHEREUM,
|
||||
TOKEN_BRIDGE_ADDRESS_POLYGON,
|
||||
TOKEN_BRIDGE_ADDRESS_TERRA,
|
||||
TOKEN_BRIDGE_ADDRESS_AVALANCHE,
|
||||
TOKEN_BRIDGE_ADDRESS_BSC,
|
||||
CORE_BRIDGE_ADDRESS_ETHEREUM,
|
||||
CORE_BRIDGE_ADDRESS_POLYGON,
|
||||
TOKEN_BRIDGE_ADDRESS_ETHEREUM,
|
||||
CORE_BRIDGE_ADDRESS_TERRA,
|
||||
CORE_BRIDGE_ADDRESS_AVALANCHE,
|
||||
CORE_BRIDGE_ADDRESS_BSC,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
POLYGON_NETWORK_CHAIN_ID,
|
||||
ETH_NETWORK_CHAIN_ID,
|
||||
WETH_TOKEN_INFO,
|
||||
WMATIC_TOKEN_INFO,
|
||||
//ETH_NETWORK_CHAIN_ID,
|
||||
//POLYGON_NETWORK_CHAIN_ID,
|
||||
//TERRA_NETWORK_CHAIN_ID,
|
||||
UST_TOKEN_INFO,
|
||||
} from "../utils/consts";
|
||||
import {
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS,
|
||||
swapExactInFromVaaNative,
|
||||
swapExactInFromVaaToken,
|
||||
swapExactOutFromVaaNative,
|
||||
swapExactOutFromVaaToken,
|
||||
} from "./util";
|
||||
evmSwapExactInFromVaaNative,
|
||||
evmSwapExactInFromVaaToken,
|
||||
evmSwapExactOutFromVaaNative,
|
||||
evmSwapExactOutFromVaaToken,
|
||||
getEvmGasParametersForContract,
|
||||
} from "./helpers";
|
||||
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";
|
||||
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_AVALANCHE } from "../addresses/fuji";
|
||||
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_BSC } from "../addresses/bsc";
|
||||
import { makeErc20Contract } from "../route/evm";
|
||||
|
||||
// placeholders
|
||||
const CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA =
|
||||
"terra163shc8unyqrndgcldaj2q9kgnqs82v0kgkhynf";
|
||||
|
||||
function makeNullSwapPath(): any[] {
|
||||
const zeroBuffer = Buffer.alloc(20);
|
||||
const nullAddress = "0x" + zeroBuffer.toString("hex");
|
||||
return [nullAddress, nullAddress];
|
||||
}
|
||||
|
||||
const NULL_SWAP_PATH = makeNullSwapPath();
|
||||
|
||||
interface SwapContractParameters {
|
||||
address: string;
|
||||
|
@ -63,7 +87,7 @@ const EXECUTION_PARAMETERS_ETHEREUM: ExecutionParameters = {
|
|||
address: CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM,
|
||||
},
|
||||
wormhole: {
|
||||
chainId: WORMHOLE_CHAIN_ID_ETHEREUM,
|
||||
chainId: CHAIN_ID_ETH,
|
||||
coreBridgeAddress: CORE_BRIDGE_ADDRESS_ETHEREUM,
|
||||
tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_ETHEREUM,
|
||||
},
|
||||
|
@ -74,34 +98,77 @@ const EXECUTION_PARAMETERS_POLYGON: ExecutionParameters = {
|
|||
address: CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON,
|
||||
},
|
||||
wormhole: {
|
||||
chainId: WORMHOLE_CHAIN_ID_POLYGON,
|
||||
chainId: CHAIN_ID_POLYGON,
|
||||
coreBridgeAddress: CORE_BRIDGE_ADDRESS_POLYGON,
|
||||
tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_POLYGON,
|
||||
},
|
||||
};
|
||||
|
||||
function makeExecutionParameters(id: number): ExecutionParameters {
|
||||
switch (id) {
|
||||
case ETH_NETWORK_CHAIN_ID: {
|
||||
const EXECUTION_PARAMETERS_AVALANCHE: ExecutionParameters = {
|
||||
crossChainSwap: {
|
||||
address: CROSSCHAINSWAP_CONTRACT_ADDRESS_AVALANCHE,
|
||||
},
|
||||
wormhole: {
|
||||
chainId: CHAIN_ID_AVAX,
|
||||
coreBridgeAddress: CORE_BRIDGE_ADDRESS_AVALANCHE,
|
||||
tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_AVALANCHE,
|
||||
},
|
||||
};
|
||||
|
||||
const EXECUTION_PARAMETERS_BSC: ExecutionParameters = {
|
||||
crossChainSwap: {
|
||||
address: CROSSCHAINSWAP_CONTRACT_ADDRESS_BSC,
|
||||
},
|
||||
wormhole: {
|
||||
chainId: CHAIN_ID_BSC,
|
||||
coreBridgeAddress: CORE_BRIDGE_ADDRESS_BSC,
|
||||
tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_BSC,
|
||||
},
|
||||
};
|
||||
|
||||
const EXECUTION_PARAMETERS_TERRA: ExecutionParameters = {
|
||||
crossChainSwap: {
|
||||
address: CROSSCHAINSWAP_CONTRACT_ADDRESS_TERRA,
|
||||
},
|
||||
wormhole: {
|
||||
chainId: CHAIN_ID_TERRA,
|
||||
coreBridgeAddress: CORE_BRIDGE_ADDRESS_TERRA,
|
||||
tokenBridgeAddress: TOKEN_BRIDGE_ADDRESS_TERRA,
|
||||
},
|
||||
};
|
||||
|
||||
function makeExecutionParameters(chainId: ChainId): ExecutionParameters {
|
||||
switch (chainId) {
|
||||
case CHAIN_ID_ETH: {
|
||||
return EXECUTION_PARAMETERS_ETHEREUM;
|
||||
}
|
||||
case POLYGON_NETWORK_CHAIN_ID: {
|
||||
case CHAIN_ID_POLYGON: {
|
||||
return EXECUTION_PARAMETERS_POLYGON;
|
||||
}
|
||||
case CHAIN_ID_AVAX: {
|
||||
return EXECUTION_PARAMETERS_AVALANCHE;
|
||||
}
|
||||
case CHAIN_ID_BSC: {
|
||||
return EXECUTION_PARAMETERS_BSC;
|
||||
}
|
||||
case CHAIN_ID_TERRA: {
|
||||
return EXECUTION_PARAMETERS_TERRA;
|
||||
}
|
||||
default: {
|
||||
throw Error("unrecognized chain id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function approveContractTokenSpend(
|
||||
async function evmApproveContractTokenSpend(
|
||||
provider: ethers.providers.Provider,
|
||||
signer: ethers.Signer,
|
||||
tokenContract: ethers.Contract,
|
||||
tokenAddress: string, //ethers.Contract,
|
||||
swapContractAddress: string,
|
||||
amount: ethers.BigNumber
|
||||
): Promise<TransactionReceipt> {
|
||||
// build transaction for token spending
|
||||
const tokenContract = await makeErc20Contract(provider, tokenAddress);
|
||||
const unsignedTx = await tokenContract.populateTransaction.approve(
|
||||
swapContractAddress,
|
||||
amount
|
||||
|
@ -140,7 +207,7 @@ function makeCrossChainSwapV2Contract(
|
|||
return new ethers.Contract(contractAddress, SWAP_CONTRACT_V2_ABI, provider);
|
||||
}
|
||||
|
||||
function makeCrossChainSwapContract(
|
||||
function makeCrossChainSwapEvmContract(
|
||||
provider: ethers.providers.Provider,
|
||||
protocol: string,
|
||||
contractAddress: string
|
||||
|
@ -163,19 +230,62 @@ function addressToBytes32(
|
|||
return hexToUint8Array(hexString);
|
||||
}
|
||||
|
||||
async function approveAndSwapExactIn(
|
||||
function evmMakeExactInSwapParameters(
|
||||
amountIn: ethers.BigNumber,
|
||||
recipientAddress: string,
|
||||
dstWormholeChainId: ChainId,
|
||||
quoteParams: ExactInCrossParameters
|
||||
): any[] {
|
||||
const src = quoteParams.src;
|
||||
const dst = quoteParams.dst;
|
||||
|
||||
if (dst === undefined) {
|
||||
return [
|
||||
amountIn,
|
||||
src.minAmountOut,
|
||||
0,
|
||||
addressToBytes32(recipientAddress, dstWormholeChainId),
|
||||
src.deadline,
|
||||
src.poolFee || 0,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
amountIn,
|
||||
src.minAmountOut,
|
||||
dst.minAmountOut,
|
||||
addressToBytes32(recipientAddress, dstWormholeChainId),
|
||||
src.deadline,
|
||||
dst.poolFee || src.poolFee || 0,
|
||||
];
|
||||
}
|
||||
|
||||
function makePathArray(
|
||||
quoteParams: ExactInCrossParameters | ExactOutCrossParameters
|
||||
): any[] {
|
||||
if (quoteParams.src === undefined) {
|
||||
return NULL_SWAP_PATH.concat(quoteParams.dst.path);
|
||||
} else if (quoteParams.dst === undefined) {
|
||||
return quoteParams.src.path.concat(NULL_SWAP_PATH);
|
||||
} else {
|
||||
return quoteParams.src.path.concat(quoteParams.dst.path);
|
||||
}
|
||||
}
|
||||
|
||||
async function evmApproveAndSwapExactIn(
|
||||
srcProvider: ethers.providers.Provider,
|
||||
srcWallet: ethers.Signer,
|
||||
srcTokenIn: UniEvmToken,
|
||||
tokenInAddress: string,
|
||||
quoteParams: ExactInCrossParameters,
|
||||
srcExecutionParams: ExecutionParameters,
|
||||
dstExecutionParams: ExecutionParameters,
|
||||
isNative: boolean
|
||||
isNative: boolean,
|
||||
recipientAddress: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const swapContractParams = srcExecutionParams.crossChainSwap;
|
||||
|
||||
const protocol = quoteParams.src.protocol;
|
||||
const swapContract = makeCrossChainSwapContract(
|
||||
const swapContract = makeCrossChainSwapEvmContract(
|
||||
srcProvider,
|
||||
protocol,
|
||||
swapContractParams.address
|
||||
|
@ -184,35 +294,27 @@ async function approveAndSwapExactIn(
|
|||
|
||||
// approve and swap this amount
|
||||
const amountIn = quoteParams.src.amountIn;
|
||||
|
||||
const address = await srcWallet.getAddress();
|
||||
|
||||
const swapParams = [
|
||||
amountIn,
|
||||
quoteParams.src.minAmountOut,
|
||||
quoteParams.dst.minAmountOut,
|
||||
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 swapParams = evmMakeExactInSwapParameters(
|
||||
amountIn,
|
||||
recipientAddress,
|
||||
dstWormholeChainId,
|
||||
quoteParams
|
||||
);
|
||||
|
||||
const pathArray = makePathArray(quoteParams);
|
||||
|
||||
const dstContractAddress = addressToBytes32(
|
||||
dstExecutionParams.crossChainSwap.address,
|
||||
dstWormholeChainId
|
||||
);
|
||||
const bridgeNonce = 69;
|
||||
|
||||
const gasParams = getEvmGasParametersForContract(swapContract);
|
||||
// do the swap
|
||||
if (isNative) {
|
||||
const gasPlusValue = {
|
||||
value: amountIn,
|
||||
gasLimit: CROSSCHAINSWAP_GAS_PARAMETERS.gasLimit,
|
||||
maxFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxFeePerGas,
|
||||
maxPriorityFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxPriorityFeePerGas,
|
||||
};
|
||||
const transactionParams = { value: amountIn, ...gasParams };
|
||||
|
||||
console.info("swapExactNativeInAndTransfer");
|
||||
const tx = await contractWithSigner.swapExactNativeInAndTransfer(
|
||||
|
@ -222,15 +324,15 @@ async function approveAndSwapExactIn(
|
|||
dstWormholeChainId,
|
||||
dstContractAddress,
|
||||
bridgeNonce,
|
||||
gasPlusValue
|
||||
transactionParams
|
||||
);
|
||||
return tx.wait();
|
||||
} else {
|
||||
console.info("approving contract to spend token in");
|
||||
await approveContractTokenSpend(
|
||||
await evmApproveContractTokenSpend(
|
||||
srcProvider,
|
||||
srcWallet,
|
||||
srcTokenIn.getContract(),
|
||||
tokenInAddress,
|
||||
swapContract.address,
|
||||
amountIn
|
||||
);
|
||||
|
@ -243,25 +345,27 @@ async function approveAndSwapExactIn(
|
|||
dstWormholeChainId,
|
||||
dstContractAddress,
|
||||
bridgeNonce,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS
|
||||
gasParams
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
}
|
||||
|
||||
async function approveAndSwapExactOut(
|
||||
// TODO: fix to resemble ExactIn
|
||||
async function evmApproveAndSwapExactOut(
|
||||
srcProvider: ethers.providers.Provider,
|
||||
srcWallet: ethers.Signer,
|
||||
srcTokenIn: UniEvmToken,
|
||||
tokenInAddress: string,
|
||||
quoteParams: ExactOutCrossParameters,
|
||||
srcExecutionParams: ExecutionParameters,
|
||||
dstExecutionParams: ExecutionParameters,
|
||||
isNative: boolean
|
||||
isNative: boolean,
|
||||
recipientAddress: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const swapContractParams = srcExecutionParams.crossChainSwap;
|
||||
|
||||
const protocol = quoteParams.src.protocol;
|
||||
const swapContract = makeCrossChainSwapContract(
|
||||
const protocol = quoteParams.src?.protocol;
|
||||
const swapContract = makeCrossChainSwapEvmContract(
|
||||
srcProvider,
|
||||
protocol,
|
||||
swapContractParams.address
|
||||
|
@ -269,36 +373,30 @@ async function approveAndSwapExactOut(
|
|||
const contractWithSigner = swapContract.connect(srcWallet);
|
||||
|
||||
// approve and swap this amount
|
||||
const amountOut = quoteParams.src.amountOut;
|
||||
const maxAmountIn = quoteParams.src.maxAmountIn;
|
||||
|
||||
const address = await srcWallet.getAddress();
|
||||
const amountOut = quoteParams.src?.amountOut;
|
||||
const maxAmountIn = quoteParams.src?.maxAmountIn;
|
||||
const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
|
||||
|
||||
const swapParams = [
|
||||
amountOut,
|
||||
maxAmountIn,
|
||||
quoteParams.dst.amountOut,
|
||||
address,
|
||||
addressToBytes32(recipientAddress, dstWormholeChainId),
|
||||
quoteParams.src.deadline,
|
||||
quoteParams.dst.poolFee || quoteParams.src.poolFee,
|
||||
quoteParams.dst.poolFee || quoteParams.src.poolFee || 0,
|
||||
];
|
||||
const pathArray = quoteParams.src.path.concat(quoteParams.dst.path);
|
||||
const pathArray = makePathArray(quoteParams);
|
||||
|
||||
const dstWormholeChainId = dstExecutionParams.wormhole.chainId;
|
||||
const dstContractAddress = addressToBytes32(
|
||||
dstExecutionParams.crossChainSwap.address,
|
||||
dstWormholeChainId
|
||||
);
|
||||
const bridgeNonce = 69;
|
||||
|
||||
const gasParams = getEvmGasParametersForContract(swapContract);
|
||||
// do the swap
|
||||
if (isNative) {
|
||||
const gasPlusValue = {
|
||||
value: maxAmountIn,
|
||||
gasLimit: CROSSCHAINSWAP_GAS_PARAMETERS.gasLimit,
|
||||
maxFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxFeePerGas,
|
||||
maxPriorityFeePerGas: CROSSCHAINSWAP_GAS_PARAMETERS.maxPriorityFeePerGas,
|
||||
};
|
||||
const gasPlusValue = { value: maxAmountIn, ...gasParams };
|
||||
|
||||
console.info("swapExactNativeOutAndTransfer");
|
||||
const tx = await contractWithSigner.swapExactNativeOutAndTransfer(
|
||||
|
@ -313,10 +411,10 @@ async function approveAndSwapExactOut(
|
|||
return tx.wait();
|
||||
} else {
|
||||
console.info("approving contract to spend token in");
|
||||
await approveContractTokenSpend(
|
||||
await evmApproveContractTokenSpend(
|
||||
srcProvider,
|
||||
srcWallet,
|
||||
srcTokenIn.getContract(),
|
||||
tokenInAddress,
|
||||
swapContract.address,
|
||||
maxAmountIn
|
||||
);
|
||||
|
@ -329,7 +427,7 @@ async function approveAndSwapExactOut(
|
|||
dstWormholeChainId,
|
||||
dstContractAddress,
|
||||
bridgeNonce,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS
|
||||
gasParams
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
@ -345,7 +443,7 @@ async function swapExactInFromVaa(
|
|||
): Promise<TransactionReceipt> {
|
||||
const swapContractParams = dstExecutionParams.crossChainSwap;
|
||||
|
||||
const swapContract = makeCrossChainSwapContract(
|
||||
const swapContract = makeCrossChainSwapEvmContract(
|
||||
dstProvider,
|
||||
dstProtocol,
|
||||
swapContractParams.address
|
||||
|
@ -353,11 +451,11 @@ async function swapExactInFromVaa(
|
|||
const contractWithSigner = swapContract.connect(dstWallet);
|
||||
|
||||
if (isNative) {
|
||||
console.info("swapExactInFromVaaNative");
|
||||
return swapExactInFromVaaNative(contractWithSigner, signedVaa);
|
||||
console.info("evmSwapExactInFromVaaNative");
|
||||
return evmSwapExactInFromVaaNative(contractWithSigner, signedVaa);
|
||||
} else {
|
||||
console.info("swapExactInFromVaaToken");
|
||||
return swapExactInFromVaaToken(contractWithSigner, signedVaa);
|
||||
console.info("evmSwapExactInFromVaaToken");
|
||||
return evmSwapExactInFromVaaToken(contractWithSigner, signedVaa);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,7 +469,7 @@ async function swapExactOutFromVaa(
|
|||
): Promise<TransactionReceipt> {
|
||||
const swapContractParams = dstExecutionParams.crossChainSwap;
|
||||
|
||||
const swapContract = makeCrossChainSwapContract(
|
||||
const swapContract = makeCrossChainSwapEvmContract(
|
||||
dstProvider,
|
||||
dstProtocol,
|
||||
swapContractParams.address
|
||||
|
@ -379,46 +477,42 @@ async function swapExactOutFromVaa(
|
|||
const contractWithSigner = swapContract.connect(dstWallet);
|
||||
|
||||
if (isNative) {
|
||||
console.info("swapExactOutFromVaaNative");
|
||||
return swapExactOutFromVaaNative(contractWithSigner, signedVaa);
|
||||
console.info("evmSwapExactOutFromVaaNative");
|
||||
return evmSwapExactOutFromVaaNative(contractWithSigner, signedVaa);
|
||||
} else {
|
||||
console.info("swapExactOutFromVaaToken");
|
||||
return swapExactOutFromVaaToken(contractWithSigner, signedVaa);
|
||||
console.info("evmSwapExactOutFromVaaToken");
|
||||
return evmSwapExactOutFromVaaToken(contractWithSigner, signedVaa);
|
||||
}
|
||||
}
|
||||
|
||||
interface CrossChainSwapTokens {
|
||||
srcIn: UniEvmToken;
|
||||
srcOut: UniEvmToken;
|
||||
dstIn: UniEvmToken;
|
||||
dstOut: UniEvmToken;
|
||||
}
|
||||
|
||||
interface VaaSearchParams {
|
||||
sequence: string;
|
||||
emitterAddress: string;
|
||||
}
|
||||
|
||||
export function makeProvider(tokenAddress: string) {
|
||||
export function makeEvmProvider(tokenAddress: string) {
|
||||
let url;
|
||||
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: {
|
||||
case ETH_TOKEN_INFO.address:
|
||||
url = process.env.REACT_APP_GOERLI_PROVIDER;
|
||||
if (!url) throw new Error("REACT_APP_GOERLI_PROVIDER not set");
|
||||
break;
|
||||
case MATIC_TOKEN_INFO.address:
|
||||
url = process.env.REACT_APP_MUMBAI_PROVIDER;
|
||||
if (!url) throw new Error("REACT_APP_MUMBAI_PROVIDER not set");
|
||||
break;
|
||||
case AVAX_TOKEN_INFO.address:
|
||||
url = process.env.REACT_APP_FUJI_PROVIDER;
|
||||
if (!url) throw new Error("REACT_APP_FUJI_PROVIDER not set");
|
||||
break;
|
||||
case BSC_TOKEN_INFO.address:
|
||||
url = process.env.REACT_APP_BSC_PROVIDER;
|
||||
if (!url) throw new Error("REACT_APP_BSC_PROVIDER not set");
|
||||
break;
|
||||
default:
|
||||
throw Error("unrecognized token address");
|
||||
}
|
||||
}
|
||||
return new ethers.providers.StaticJsonRpcProvider(url);
|
||||
}
|
||||
|
||||
export class UniswapToUniswapExecutor {
|
||||
|
@ -427,7 +521,6 @@ export class UniswapToUniswapExecutor {
|
|||
cachedExactInParams: ExactInCrossParameters;
|
||||
cachedExactOutParams: ExactOutCrossParameters;
|
||||
quoteType: QuoteType;
|
||||
tokens: CrossChainSwapTokens;
|
||||
|
||||
// swapping
|
||||
isNative: boolean;
|
||||
|
@ -440,8 +533,16 @@ export class UniswapToUniswapExecutor {
|
|||
transportFactory: grpc.TransportFactory;
|
||||
vaaSearchParams: VaaSearchParams;
|
||||
vaaBytes: Uint8Array;
|
||||
srcReceipt: TransactionReceipt;
|
||||
dstReceipt: TransactionReceipt;
|
||||
|
||||
// receipts
|
||||
srcEvmReceipt: TransactionReceipt;
|
||||
dstEvmReceipt: TransactionReceipt;
|
||||
srcTerraReceipt: any;
|
||||
dstTerraReceipt: any;
|
||||
|
||||
constructor() {
|
||||
this.quoter = new UniswapToUniswapQuoter();
|
||||
}
|
||||
|
||||
async initialize(
|
||||
tokenInAddress: string,
|
||||
|
@ -450,20 +551,14 @@ export class UniswapToUniswapExecutor {
|
|||
): Promise<void> {
|
||||
this.isNative = isNative;
|
||||
|
||||
const srcProvider = makeProvider(tokenInAddress);
|
||||
const dstProvider = makeProvider(tokenOutAddress);
|
||||
|
||||
this.quoter = new UniswapToUniswapQuoter(srcProvider, dstProvider);
|
||||
await this.quoter.initialize();
|
||||
|
||||
await this.makeTokens(tokenInAddress, tokenOutAddress);
|
||||
await this.quoter.initialize(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.quoter.getSrcChainId()
|
||||
);
|
||||
this.dstExecutionParams = makeExecutionParameters(
|
||||
this.quoter.dstNetwork.chainId
|
||||
this.quoter.getDstChainId()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -483,6 +578,7 @@ export class UniswapToUniswapExecutor {
|
|||
this.quoter.setDeadlines(deadline);
|
||||
}
|
||||
|
||||
/*
|
||||
async makeTokens(
|
||||
tokenInAddress: string,
|
||||
tokenOutAddress: string
|
||||
|
@ -507,7 +603,7 @@ export class UniswapToUniswapExecutor {
|
|||
getTokens(): CrossChainSwapTokens {
|
||||
return this.tokens;
|
||||
}
|
||||
|
||||
*/
|
||||
async computeAndVerifySrcPoolAddress(): Promise<string> {
|
||||
return this.quoter.computeAndVerifySrcPoolAddress();
|
||||
}
|
||||
|
@ -546,59 +642,97 @@ export class UniswapToUniswapExecutor {
|
|||
return this.cachedExactOutParams;
|
||||
}
|
||||
|
||||
getSrcProvider(): ethers.providers.Provider {
|
||||
return this.quoter.srcProvider;
|
||||
getSrcEvmProvider(): ethers.providers.Provider {
|
||||
return this.quoter.getSrcEvmProvider();
|
||||
}
|
||||
|
||||
getDstProvider(): ethers.providers.Provider {
|
||||
return this.quoter.dstProvider;
|
||||
getDstEvmProvider(): ethers.providers.Provider {
|
||||
return this.quoter.getDstEvmProvider();
|
||||
}
|
||||
|
||||
async approveAndSwapExactIn(
|
||||
wallet: ethers.Signer
|
||||
getTokenInAddress(): string {
|
||||
return this.quoter.tokenInAddress;
|
||||
}
|
||||
|
||||
getTokenOutAddress(): string {
|
||||
return this.quoter.tokenOutAddress;
|
||||
}
|
||||
|
||||
async evmApproveAndSwapExactIn(
|
||||
srcWallet: ethers.Signer,
|
||||
recipientAddress: string
|
||||
): Promise<TransactionReceipt> {
|
||||
return approveAndSwapExactIn(
|
||||
this.getSrcProvider(),
|
||||
wallet,
|
||||
this.tokens.srcIn,
|
||||
return evmApproveAndSwapExactIn(
|
||||
this.getSrcEvmProvider(),
|
||||
srcWallet,
|
||||
this.getTokenInAddress(),
|
||||
this.cachedExactInParams,
|
||||
this.srcExecutionParams,
|
||||
this.dstExecutionParams,
|
||||
this.isNative
|
||||
this.isNative,
|
||||
recipientAddress
|
||||
);
|
||||
}
|
||||
|
||||
async approveAndSwapExactOut(
|
||||
wallet: ethers.Signer
|
||||
async evmApproveAndSwapExactOut(
|
||||
srcWallet: ethers.Signer,
|
||||
recipientAddress: string
|
||||
): Promise<TransactionReceipt> {
|
||||
return approveAndSwapExactOut(
|
||||
this.getSrcProvider(),
|
||||
wallet,
|
||||
this.tokens.srcIn,
|
||||
return evmApproveAndSwapExactOut(
|
||||
this.getSrcEvmProvider(),
|
||||
srcWallet,
|
||||
this.getTokenInAddress(),
|
||||
this.cachedExactOutParams,
|
||||
this.srcExecutionParams,
|
||||
this.dstExecutionParams,
|
||||
this.isNative
|
||||
this.isNative,
|
||||
recipientAddress
|
||||
);
|
||||
}
|
||||
|
||||
async approveAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> {
|
||||
srcIsUst(): boolean {
|
||||
return (
|
||||
this.quoter.tokenInAddress === UST_TOKEN_INFO.address &&
|
||||
this.cachedExactInParams.src === undefined
|
||||
);
|
||||
}
|
||||
|
||||
async evmApproveAndSwap(
|
||||
wallet: ethers.Signer,
|
||||
recipientAddress: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const quoteType = this.quoteType;
|
||||
|
||||
if (quoteType === QuoteType.ExactIn) {
|
||||
this.srcReceipt = await this.approveAndSwapExactIn(wallet);
|
||||
this.srcEvmReceipt = await this.evmApproveAndSwapExactIn(
|
||||
wallet,
|
||||
recipientAddress
|
||||
);
|
||||
} else if (quoteType === QuoteType.ExactOut) {
|
||||
this.srcReceipt = await this.approveAndSwapExactOut(wallet);
|
||||
this.srcEvmReceipt = await this.evmApproveAndSwapExactOut(
|
||||
wallet,
|
||||
recipientAddress
|
||||
);
|
||||
} else {
|
||||
throw Error("no quote found");
|
||||
}
|
||||
|
||||
this.fetchAndSetEmitterAndSequence();
|
||||
return this.srcReceipt;
|
||||
this.fetchAndSetEvmEmitterAndSequence();
|
||||
return this.srcEvmReceipt;
|
||||
}
|
||||
|
||||
fetchAndSetEmitterAndSequence(): void {
|
||||
const receipt = this.srcReceipt;
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
fetchAndSetTerraEmitterAndSequence(): void {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
fetchAndSetEvmEmitterAndSequence(): void {
|
||||
const receipt = this.srcEvmReceipt;
|
||||
if (receipt === undefined) {
|
||||
throw Error("no swap receipt found");
|
||||
}
|
||||
|
@ -623,11 +757,15 @@ export class UniswapToUniswapExecutor {
|
|||
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
|
||||
vaaSearchParams.sequence,
|
||||
{
|
||||
transport: this.transportFactory,
|
||||
}
|
||||
);
|
||||
// grab vaaBytes
|
||||
this.vaaBytes = vaaResponse.vaaBytes;
|
||||
|
@ -636,22 +774,27 @@ export class UniswapToUniswapExecutor {
|
|||
async fetchVaaAndSwap(wallet: ethers.Signer): Promise<TransactionReceipt> {
|
||||
await this.fetchSignedVaaFromSwap();
|
||||
|
||||
// check if Terra transaction
|
||||
// TODO: change return as something else (not evm TransactionReceipt)
|
||||
|
||||
const quoteType = this.quoteType;
|
||||
|
||||
if (quoteType === QuoteType.ExactIn) {
|
||||
this.dstReceipt = await this.swapExactInFromVaa(wallet);
|
||||
this.dstEvmReceipt = await this.evmSwapExactInFromVaa(wallet);
|
||||
} else if (quoteType === QuoteType.ExactOut) {
|
||||
this.dstReceipt = await this.swapExactOutFromVaa(wallet);
|
||||
this.dstEvmReceipt = await this.evmSwapExactOutFromVaa(wallet);
|
||||
} else {
|
||||
throw Error("no quote found");
|
||||
}
|
||||
|
||||
return this.dstReceipt;
|
||||
return this.dstEvmReceipt;
|
||||
}
|
||||
|
||||
async swapExactInFromVaa(wallet: ethers.Signer): Promise<TransactionReceipt> {
|
||||
async evmSwapExactInFromVaa(
|
||||
wallet: ethers.Signer
|
||||
): Promise<TransactionReceipt> {
|
||||
return swapExactInFromVaa(
|
||||
this.getDstProvider(),
|
||||
this.getDstEvmProvider(),
|
||||
wallet,
|
||||
this.dstExecutionParams,
|
||||
this.cachedExactInParams.dst.protocol,
|
||||
|
@ -660,11 +803,11 @@ export class UniswapToUniswapExecutor {
|
|||
);
|
||||
}
|
||||
|
||||
async swapExactOutFromVaa(
|
||||
async evmSwapExactOutFromVaa(
|
||||
wallet: ethers.Signer
|
||||
): Promise<TransactionReceipt> {
|
||||
return swapExactOutFromVaa(
|
||||
this.getDstProvider(),
|
||||
this.getDstEvmProvider(),
|
||||
wallet,
|
||||
this.dstExecutionParams,
|
||||
this.cachedExactOutParams.dst.protocol,
|
||||
|
@ -673,6 +816,10 @@ export class UniswapToUniswapExecutor {
|
|||
);
|
||||
}
|
||||
|
||||
setTransport(transportFactory: grpc.TransportFactory) {
|
||||
this.transportFactory = transportFactory;
|
||||
}
|
||||
|
||||
//getSwapResult(
|
||||
// walletAddress: string,
|
||||
// onSwapResult: (result: boolean) => void
|
||||
|
@ -680,7 +827,7 @@ export class UniswapToUniswapExecutor {
|
|||
// console.log(this.cachedExactInParams.dst.protocol);
|
||||
// console.log(this.dstExecutionParams.crossChainSwap.address);
|
||||
// const contract = makeCrossChainSwapContract(
|
||||
// this.getDstProvider(),
|
||||
// this.getDstEvmProvider(),
|
||||
// this.quoteType === QuoteType.ExactIn
|
||||
// ? this.cachedExactInParams.dst.protocol
|
||||
// : this.cachedExactOutParams.dst.protocol,
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
import { ethers } from "ethers";
|
||||
import { TransactionReceipt } from "@ethersproject/abstract-provider";
|
||||
|
||||
export const CROSSCHAINSWAP_GAS_PARAMETERS = {
|
||||
gasLimit: "550000",
|
||||
maxFeePerGas: "250000000000",
|
||||
maxPriorityFeePerGas: "1690000000",
|
||||
};
|
||||
|
||||
// exact in
|
||||
//
|
||||
export async function swapExactInFromVaaNative(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactNativeIn(
|
||||
signedVaa,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
export async function swapExactInFromVaaToken(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactIn(
|
||||
signedVaa,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
// exact out (TODO: add to util)
|
||||
//
|
||||
export async function swapExactOutFromVaaNative(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactNativeOut(
|
||||
signedVaa,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
export async function swapExactOutFromVaaToken(
|
||||
swapContractWithSigner: ethers.Contract,
|
||||
signedVaa: Uint8Array
|
||||
): Promise<TransactionReceipt> {
|
||||
const tx = await swapContractWithSigner.recvAndSwapExactOut(
|
||||
signedVaa,
|
||||
CROSSCHAINSWAP_GAS_PARAMETERS
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
*.js
|
|
@ -2,72 +2,117 @@ import {
|
|||
ChainId,
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_POLYGON,
|
||||
CHAIN_ID_TERRA,
|
||||
CHAIN_ID_AVAX,
|
||||
CHAIN_ID_BSC,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import ethIcon from "../icons/eth.svg";
|
||||
import polygonIcon from "../icons/polygon.svg";
|
||||
|
||||
export const EVM_POLYGON_NETWORK_CHAIN_ID = 80001;
|
||||
export const EVM_ETH_NETWORK_CHAIN_ID = 5;
|
||||
export const EVM_AVAX_NETWORK_CHAIN_ID = 43113;
|
||||
export const EVM_BSC_NETWORK_CHAIN_ID = 97;
|
||||
|
||||
export interface TokenInfo {
|
||||
name: string;
|
||||
address: string;
|
||||
chainId: ChainId;
|
||||
logo: string;
|
||||
isNative: boolean;
|
||||
evmChainId: number | undefined;
|
||||
maxAmount: number;
|
||||
ustPairedAddress: string | undefined;
|
||||
}
|
||||
|
||||
export const MATIC_TOKEN_INFO: TokenInfo = {
|
||||
name: "MATIC",
|
||||
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889", // used to compute quote
|
||||
chainId: CHAIN_ID_POLYGON,
|
||||
logo: polygonIcon,
|
||||
isNative: true,
|
||||
maxAmount: 0.1,
|
||||
};
|
||||
|
||||
export const WMATIC_TOKEN_INFO: TokenInfo = {
|
||||
name: "WMATIC",
|
||||
address: "0x9c3c9283d3e44854697cd22d3faa240cfb032889",
|
||||
chainId: CHAIN_ID_POLYGON,
|
||||
logo: polygonIcon,
|
||||
isNative: false,
|
||||
evmChainId: EVM_POLYGON_NETWORK_CHAIN_ID,
|
||||
//logo: polygonIcon,
|
||||
maxAmount: 0.1,
|
||||
ustPairedAddress: "0xe3a1c77e952b57b5883f6c906fc706fcc7d4392c",
|
||||
};
|
||||
|
||||
export const ETH_TOKEN_INFO: TokenInfo = {
|
||||
name: "ETH",
|
||||
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", // used to compute quote
|
||||
chainId: CHAIN_ID_ETH,
|
||||
logo: ethIcon,
|
||||
isNative: true,
|
||||
maxAmount: 0.01,
|
||||
};
|
||||
|
||||
export const WETH_TOKEN_INFO: TokenInfo = {
|
||||
name: "WETH",
|
||||
address: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6",
|
||||
chainId: CHAIN_ID_ETH,
|
||||
logo: ethIcon,
|
||||
isNative: false,
|
||||
evmChainId: EVM_ETH_NETWORK_CHAIN_ID,
|
||||
//logo: ethIcon,
|
||||
maxAmount: 0.01,
|
||||
ustPairedAddress: "0x36Ed51Afc79619b299b238898E72ce482600568a",
|
||||
};
|
||||
|
||||
export const AVAX_TOKEN_INFO: TokenInfo = {
|
||||
name: "AVAX",
|
||||
address: "0x1d308089a2d1ced3f1ce36b1fcaf815b07217be3",
|
||||
chainId: CHAIN_ID_AVAX,
|
||||
evmChainId: EVM_AVAX_NETWORK_CHAIN_ID,
|
||||
//logo: avaxIcon,
|
||||
maxAmount: 0.01,
|
||||
ustPairedAddress: "0xe09ed38e5cd1014444846f62376ac88c5232cde9",
|
||||
};
|
||||
|
||||
export const BNB_TOKEN_INFO: TokenInfo = {
|
||||
name: "BNB",
|
||||
address: "0xae13d989dac2f0debff460ac112a837c89baa7cd",
|
||||
chainId: CHAIN_ID_BSC,
|
||||
evmChainId: EVM_BSC_NETWORK_CHAIN_ID,
|
||||
//logo: bscIcon,
|
||||
maxAmount: 0.01,
|
||||
ustPairedAddress: "0x7b8eae1e85c8b189ee653d3f78733f4f788bb2c1",
|
||||
};
|
||||
|
||||
export const UST_TOKEN_INFO: TokenInfo = {
|
||||
name: "UST",
|
||||
address: "uusd",
|
||||
chainId: CHAIN_ID_TERRA,
|
||||
evmChainId: undefined,
|
||||
//logo: terraIcon,
|
||||
maxAmount: 10.0,
|
||||
ustPairedAddress: undefined,
|
||||
};
|
||||
|
||||
export const TOKEN_INFOS = [
|
||||
MATIC_TOKEN_INFO,
|
||||
WMATIC_TOKEN_INFO,
|
||||
ETH_TOKEN_INFO,
|
||||
WETH_TOKEN_INFO,
|
||||
AVAX_TOKEN_INFO,
|
||||
BNB_TOKEN_INFO,
|
||||
// TODO: support swaps from/to terra
|
||||
// UST_TOKEN_INFO,
|
||||
];
|
||||
|
||||
export const ETH_NETWORK_CHAIN_ID = 5;
|
||||
export const getSupportedSwaps = (tokenInfo: TokenInfo) => {
|
||||
return TOKEN_INFOS.filter((x) => x !== tokenInfo);
|
||||
};
|
||||
|
||||
export const POLYGON_NETWORK_CHAIN_ID = 80001;
|
||||
export const getEvmChainId = (chainId: ChainId): number | undefined => {
|
||||
switch (chainId) {
|
||||
case CHAIN_ID_ETH:
|
||||
return EVM_ETH_NETWORK_CHAIN_ID;
|
||||
case CHAIN_ID_POLYGON:
|
||||
return EVM_POLYGON_NETWORK_CHAIN_ID;
|
||||
case CHAIN_ID_AVAX:
|
||||
return EVM_AVAX_NETWORK_CHAIN_ID;
|
||||
case CHAIN_ID_BSC:
|
||||
return EVM_BSC_NETWORK_CHAIN_ID;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const getEvmChainId = (chainId: ChainId) =>
|
||||
chainId === CHAIN_ID_ETH
|
||||
? ETH_NETWORK_CHAIN_ID
|
||||
: chainId === CHAIN_ID_POLYGON
|
||||
? POLYGON_NETWORK_CHAIN_ID
|
||||
: undefined;
|
||||
export const getChainName = (chainId: ChainId) => {
|
||||
switch (chainId) {
|
||||
case CHAIN_ID_ETH:
|
||||
return "Ethereum";
|
||||
case CHAIN_ID_POLYGON:
|
||||
return "Polygon";
|
||||
case CHAIN_ID_AVAX:
|
||||
return "Avalanche";
|
||||
case CHAIN_ID_BSC:
|
||||
return "BSC";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export const RELAYER_FEE_UST = "0.25";
|
||||
|
||||
|
@ -75,22 +120,37 @@ export const WORMHOLE_RPC_HOSTS = [
|
|||
"https://wormhole-v2-testnet-api.certus.one",
|
||||
];
|
||||
|
||||
// core bridge
|
||||
export const CORE_BRIDGE_ADDRESS_ETHEREUM =
|
||||
"0x706abc4E45D419950511e474C7B9Ed348A4a716c";
|
||||
|
||||
export const CORE_BRIDGE_ADDRESS_POLYGON =
|
||||
"0x0CBE91CF822c73C2315FB05100C2F714765d5c20";
|
||||
|
||||
export const CORE_BRIDGE_ADDRESS_AVALANCHE =
|
||||
"0x7bbcE28e64B3F8b84d876Ab298393c38ad7aac4C";
|
||||
|
||||
export const CORE_BRIDGE_ADDRESS_BSC =
|
||||
"0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D";
|
||||
|
||||
export const CORE_BRIDGE_ADDRESS_TERRA =
|
||||
"terra1pd65m0q9tl3v8znnz5f5ltsfegyzah7g42cx5v";
|
||||
|
||||
// token bridge
|
||||
export const TOKEN_BRIDGE_ADDRESS_ETHEREUM =
|
||||
"0xF890982f9310df57d00f659cf4fd87e65adEd8d7";
|
||||
|
||||
export const TOKEN_BRIDGE_ADDRESS_POLYGON =
|
||||
"0x377D55a7928c046E18eEbb61977e714d2a76472a";
|
||||
|
||||
export const QUICKSWAP_FACTORY_ADDRESS =
|
||||
"0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32";
|
||||
export const TOKEN_BRIDGE_ADDRESS_BSC =
|
||||
"0x9dcF9D205C9De35334D646BeE44b2D2859712A09";
|
||||
|
||||
export const UNISWAP_V3_FACTORY_ADDRESS =
|
||||
"0x1F98431c8aD98523631AE4a59f267346ea31F984";
|
||||
export const TOKEN_BRIDGE_ADDRESS_AVALANCHE =
|
||||
"0x61E44E506Ca5659E6c0bba9b678586fA2d729756";
|
||||
|
||||
export const TOKEN_BRIDGE_ADDRESS_TERRA =
|
||||
"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a";
|
||||
|
||||
// gas
|
||||
export const APPROVAL_GAS_LIMIT = "100000";
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { FixedNumber } from "ethers";
|
||||
|
||||
export function addFixedAmounts(
|
||||
left: string,
|
||||
right: string,
|
||||
decimals: number
|
||||
): string {
|
||||
const sum = FixedNumber.from(left).addUnsafe(FixedNumber.from(right));
|
||||
return sum.round(decimals).toString();
|
||||
}
|
||||
|
||||
export function subtractFixedAmounts(
|
||||
left: string,
|
||||
right: string,
|
||||
decimals: number
|
||||
): string {
|
||||
const diff = FixedNumber.from(left).subUnsafe(FixedNumber.from(right));
|
||||
return diff.round(decimals).toString();
|
||||
}
|
|
@ -7,7 +7,12 @@ import {
|
|||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { ChainId, getSignedVAAWithRetry } from "@certusone/wormhole-sdk";
|
||||
import {
|
||||
ChainId,
|
||||
CHAIN_ID_TERRA,
|
||||
getSignedVAAWithRetry,
|
||||
isEVMChain,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import ButtonWithLoader from "../components/ButtonWithLoader";
|
||||
import EthereumSignerKey from "../components/EthereumSignerKey";
|
||||
|
@ -16,11 +21,11 @@ import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
|||
import {
|
||||
ETH_TOKEN_INFO,
|
||||
getEvmChainId,
|
||||
getSupportedSwaps,
|
||||
MATIC_TOKEN_INFO,
|
||||
RELAYER_FEE_UST,
|
||||
TokenInfo,
|
||||
TOKEN_INFOS,
|
||||
WETH_TOKEN_INFO,
|
||||
WMATIC_TOKEN_INFO,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "../utils/consts";
|
||||
import { COLORS } from "../muiTheme";
|
||||
|
@ -37,6 +42,8 @@ import CircleLoader from "../components/CircleLoader";
|
|||
import { ArrowForward, CheckCircleOutlineRounded } from "@material-ui/icons";
|
||||
import SwapProgress from "../components/SwapProgress";
|
||||
import Footer from "../components/Footer";
|
||||
import TerraWalletKey from "../components/TerraWalletKey";
|
||||
import useIsWalletReady from "../hooks/useIsWalletReady";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
bg: {
|
||||
|
@ -135,7 +142,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
const switchProviderNetwork = async (
|
||||
const switchEvmProviderNetwork = async (
|
||||
provider: Web3Provider,
|
||||
chainId: ChainId
|
||||
) => {
|
||||
|
@ -152,6 +159,78 @@ const switchProviderNetwork = async (
|
|||
}
|
||||
};
|
||||
|
||||
const ConnectedWalletAddress = ({
|
||||
chainId,
|
||||
prefix,
|
||||
}: {
|
||||
chainId: ChainId;
|
||||
prefix: string;
|
||||
}) => {
|
||||
const { walletAddress } = useIsWalletReady(chainId, false);
|
||||
if (walletAddress) {
|
||||
const is0x = walletAddress.startsWith("0x");
|
||||
return (
|
||||
<Typography variant="subtitle2">
|
||||
{prefix} {walletAddress?.substring(0, is0x ? 6 : 3)}...
|
||||
{walletAddress?.substring(walletAddress.length - (is0x ? 4 : 3))}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const SwapButton = ({
|
||||
source,
|
||||
target,
|
||||
disabled,
|
||||
showLoader,
|
||||
onClick,
|
||||
}: {
|
||||
source: TokenInfo;
|
||||
target: TokenInfo;
|
||||
disabled: boolean;
|
||||
showLoader: boolean;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
const { isReady: isSourceWalletReady } = useIsWalletReady(
|
||||
source.chainId,
|
||||
!disabled
|
||||
);
|
||||
const { isReady: isTargetWalletReady } = useIsWalletReady(
|
||||
target.chainId,
|
||||
!isEVMChain(source.chainId)
|
||||
);
|
||||
|
||||
if (!isSourceWalletReady) {
|
||||
return isEVMChain(source.chainId) ? (
|
||||
<EthereumSignerKey />
|
||||
) : source.chainId === CHAIN_ID_TERRA ? (
|
||||
<TerraWalletKey />
|
||||
) : null;
|
||||
}
|
||||
|
||||
if (
|
||||
!isTargetWalletReady &&
|
||||
(!isEVMChain(source.chainId) || !isEVMChain(target.chainId))
|
||||
) {
|
||||
return isEVMChain(target.chainId) ? (
|
||||
<EthereumSignerKey />
|
||||
) : source.chainId === CHAIN_ID_TERRA ? (
|
||||
<TerraWalletKey />
|
||||
) : null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonWithLoader
|
||||
disabled={disabled}
|
||||
showLoader={showLoader}
|
||||
onClick={onClick}
|
||||
>
|
||||
Swap
|
||||
</ButtonWithLoader>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Home() {
|
||||
const classes = useStyles();
|
||||
const [sourceTokenInfo, setSourceTokenInfo] = useState(MATIC_TOKEN_INFO);
|
||||
|
@ -167,10 +246,10 @@ export default function Home() {
|
|||
const [isSwapping, setIsSwapping] = useState(false);
|
||||
const [isComputingQuote, setIsComputingQuote] = useState(false);
|
||||
const [hasQuote, setHasQuote] = useState(false);
|
||||
const { provider, signer } = useEthereumProvider();
|
||||
const { provider, signer, signerAddress, disconnect } = useEthereumProvider();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [isFirstSwapComplete, setIsFirstSwapComplete] = useState(false);
|
||||
const [isSecondSwapComplete, setIsSecondSwapComplete] = useState(false);
|
||||
const [isSourceSwapComplete, setIsSourceSwapComplete] = useState(false);
|
||||
const [isTargetSwapComplete, setIsTargetSwapComplete] = useState(false);
|
||||
const [sourceTxBlockNumber, setSourceTxBlockNumber] = useState<
|
||||
number | undefined
|
||||
>(undefined);
|
||||
|
@ -193,7 +272,7 @@ export default function Home() {
|
|||
await executor.initialize(
|
||||
sourceTokenInfo.address,
|
||||
targetTokenInfo.address,
|
||||
sourceTokenInfo.isNative
|
||||
true
|
||||
);
|
||||
await executor.computeAndVerifySrcPoolAddress().catch((e) => {
|
||||
throw new Error("failed to verify source pool address");
|
||||
|
@ -206,16 +285,8 @@ export default function Home() {
|
|||
executor.setRelayerFee(RELAYER_FEE_UST);
|
||||
const quote = await executor.computeQuoteExactIn(amountIn);
|
||||
setExecutor(executor);
|
||||
setAmountOut(
|
||||
parseFloat(
|
||||
executor.tokens.dstOut.formatAmount(quote.dst.minAmountOut)
|
||||
).toFixed(8)
|
||||
);
|
||||
setAmountInUST(
|
||||
parseFloat(
|
||||
executor.tokens.dstIn.formatAmount(quote.dst.amountIn)
|
||||
).toFixed(2)
|
||||
);
|
||||
setAmountOut(parseFloat(quote.minAmountOut).toFixed(8));
|
||||
setAmountInUST(parseFloat(quote.ustAmountIn).toFixed(2));
|
||||
setHasQuote(true);
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -260,52 +331,65 @@ export default function Home() {
|
|||
setDeadline(deadline);
|
||||
}, []);
|
||||
|
||||
const handleSourceChange = useCallback((event) => {
|
||||
// NOTE: only native-to-native or wrapped-to-wrapped swaps are currently supported
|
||||
if (event.target.value === WMATIC_TOKEN_INFO.name) {
|
||||
setSourceTokenInfo(WMATIC_TOKEN_INFO);
|
||||
setTargetTokenInfo(WETH_TOKEN_INFO);
|
||||
} else if (event.target.value === WETH_TOKEN_INFO.name) {
|
||||
setSourceTokenInfo(WETH_TOKEN_INFO);
|
||||
setTargetTokenInfo(WMATIC_TOKEN_INFO);
|
||||
} else if (event.target.value === ETH_TOKEN_INFO.name) {
|
||||
setSourceTokenInfo(ETH_TOKEN_INFO);
|
||||
setTargetTokenInfo(MATIC_TOKEN_INFO);
|
||||
} else {
|
||||
setSourceTokenInfo(MATIC_TOKEN_INFO);
|
||||
setTargetTokenInfo(ETH_TOKEN_INFO);
|
||||
const handleSourceChange = useCallback(
|
||||
(event) => {
|
||||
const tokenInfo = TOKEN_INFOS.find((x) => x.name === event.target.value);
|
||||
if (tokenInfo) {
|
||||
const supportedSwaps = getSupportedSwaps(tokenInfo);
|
||||
if (supportedSwaps) {
|
||||
setSourceTokenInfo(tokenInfo);
|
||||
if (!supportedSwaps.find((x) => x.name === targetTokenInfo.name)) {
|
||||
setTargetTokenInfo(supportedSwaps[0]);
|
||||
}
|
||||
setAmountIn("");
|
||||
setAmountOut("");
|
||||
}
|
||||
}
|
||||
},
|
||||
[targetTokenInfo]
|
||||
);
|
||||
|
||||
const handleTargetChange = useCallback((event) => {
|
||||
const tokenInfo = TOKEN_INFOS.find((x) => x.name === event.target.value);
|
||||
if (tokenInfo) {
|
||||
setTargetTokenInfo(tokenInfo);
|
||||
setAmountOut("");
|
||||
}
|
||||
setAmountIn("");
|
||||
setAmountOut("");
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setIsSwapping(false);
|
||||
setHasQuote(false);
|
||||
setIsFirstSwapComplete(false);
|
||||
setIsSecondSwapComplete(false);
|
||||
setIsSourceSwapComplete(false);
|
||||
setHasSignedVAA(false);
|
||||
setIsTargetSwapComplete(false);
|
||||
setAmountIn("");
|
||||
setAmountOut("");
|
||||
setSourceTxBlockNumber(undefined);
|
||||
setRelayerTimeoutString("");
|
||||
}, []);
|
||||
disconnect();
|
||||
}, [disconnect]);
|
||||
|
||||
const handleSwapClick = useCallback(async () => {
|
||||
if (provider && signer && executor) {
|
||||
if (provider && signer && signerAddress && executor) {
|
||||
try {
|
||||
setIsSwapping(true);
|
||||
setIsFirstSwapComplete(false);
|
||||
setIsSourceSwapComplete(false);
|
||||
setHasSignedVAA(false);
|
||||
setIsSecondSwapComplete(false);
|
||||
setIsTargetSwapComplete(false);
|
||||
setRelayerTimeoutString("");
|
||||
await switchProviderNetwork(provider, sourceTokenInfo.chainId);
|
||||
await switchEvmProviderNetwork(provider, sourceTokenInfo.chainId);
|
||||
console.log(signerAddress);
|
||||
|
||||
const sourceReceipt = await executor.approveAndSwap(signer);
|
||||
const sourceReceipt = await executor.evmApproveAndSwap(
|
||||
signer,
|
||||
signerAddress
|
||||
);
|
||||
console.info(
|
||||
"firstSwapTransactionHash:",
|
||||
sourceReceipt.transactionHash
|
||||
);
|
||||
setIsFirstSwapComplete(true);
|
||||
setIsSourceSwapComplete(true);
|
||||
setSourceTxBlockNumber(sourceReceipt.blockNumber);
|
||||
|
||||
// Wait for the guardian network to reach consensus and emit the signedVAA
|
||||
|
@ -319,7 +403,9 @@ export default function Home() {
|
|||
// Check if the signedVAA has redeemed by the relayer
|
||||
const isCompleted = await getIsTransferCompletedEvmWithRetry(
|
||||
executor.dstExecutionParams.wormhole.tokenBridgeAddress,
|
||||
executor.quoter.dstProvider,
|
||||
// TODO: fix typescript error
|
||||
// @ts-ignore
|
||||
executor.quoter.getDstEvmProvider(),
|
||||
vaaBytes,
|
||||
// retry for two minutes
|
||||
3000,
|
||||
|
@ -330,14 +416,14 @@ export default function Home() {
|
|||
setRelayerTimeoutString(
|
||||
"Timed out waiting for relayer to complete swap. You'll need to complete it yourself."
|
||||
);
|
||||
await switchProviderNetwork(provider, targetTokenInfo.chainId);
|
||||
await switchEvmProviderNetwork(provider, targetTokenInfo.chainId);
|
||||
const targetReceipt = await executor.fetchVaaAndSwap(signer);
|
||||
console.info(
|
||||
"secondSwapTransactionHash:",
|
||||
targetReceipt.transactionHash
|
||||
);
|
||||
}
|
||||
setIsSecondSwapComplete(true);
|
||||
setIsTargetSwapComplete(true);
|
||||
} catch (e: any) {
|
||||
reset();
|
||||
console.error(e);
|
||||
|
@ -349,6 +435,7 @@ export default function Home() {
|
|||
}, [
|
||||
provider,
|
||||
signer,
|
||||
signerAddress,
|
||||
executor,
|
||||
enqueueSnackbar,
|
||||
sourceTokenInfo,
|
||||
|
@ -357,19 +444,18 @@ export default function Home() {
|
|||
]);
|
||||
|
||||
const readyToSwap = provider && signer && hasQuote;
|
||||
const disableSelect = isSwapping || isComputingQuote;
|
||||
|
||||
return (
|
||||
<div className={classes.bg}>
|
||||
<Container className={classes.centeredContainer} maxWidth="sm">
|
||||
<div className={classes.titleBar}></div>
|
||||
<Typography variant="h4" color="textSecondary">
|
||||
Wormhole NativeSwap Demo
|
||||
</Typography>
|
||||
<Typography variant="h4">Wormhole NativeSwap Demo</Typography>
|
||||
<div className={classes.spacer} />
|
||||
<Paper className={classes.mainPaper}>
|
||||
<Collapse in={!isFirstSwapComplete}>
|
||||
<Collapse in={!isSourceSwapComplete}>
|
||||
<Settings
|
||||
disabled={isSwapping || isComputingQuote}
|
||||
disabled={disableSelect}
|
||||
slippage={slippage}
|
||||
deadline={deadline}
|
||||
onSlippageChange={handleSlippageChange}
|
||||
|
@ -379,13 +465,13 @@ export default function Home() {
|
|||
tokens={TOKEN_INFOS}
|
||||
value={sourceTokenInfo.name}
|
||||
onChange={handleSourceChange}
|
||||
disabled={isSwapping || isComputingQuote}
|
||||
disabled={disableSelect}
|
||||
></TokenSelect>
|
||||
<Typography variant="subtitle1">Send</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
value={amountIn}
|
||||
disabled={isSwapping || isComputingQuote}
|
||||
disabled={disableSelect}
|
||||
InputProps={{ disableUnderline: true }}
|
||||
className={classes.numberField}
|
||||
onChange={handleAmountChange}
|
||||
|
@ -397,12 +483,16 @@ export default function Home() {
|
|||
color="error"
|
||||
>{`The max input amount is ${sourceTokenInfo.maxAmount} ${sourceTokenInfo.name}`}</Typography>
|
||||
) : null}
|
||||
<ConnectedWalletAddress
|
||||
chainId={sourceTokenInfo.chainId}
|
||||
prefix="From:"
|
||||
/>
|
||||
<div className={classes.spacer} />
|
||||
<TokenSelect
|
||||
tokens={TOKEN_INFOS}
|
||||
tokens={getSupportedSwaps(sourceTokenInfo)}
|
||||
value={targetTokenInfo.name}
|
||||
onChange={() => {}}
|
||||
disabled={true}
|
||||
onChange={handleTargetChange}
|
||||
disabled={disableSelect}
|
||||
></TokenSelect>
|
||||
<Typography variant="subtitle1">Receive (estimated)</Typography>
|
||||
<TextField
|
||||
|
@ -414,10 +504,21 @@ export default function Home() {
|
|||
inputProps={{ readOnly: true }}
|
||||
placeholder="0.0"
|
||||
></TextField>
|
||||
<ConnectedWalletAddress
|
||||
chainId={
|
||||
isEVMChain(sourceTokenInfo.chainId) &&
|
||||
isEVMChain(targetTokenInfo.chainId)
|
||||
? sourceTokenInfo.chainId
|
||||
: targetTokenInfo.chainId
|
||||
}
|
||||
prefix="To:"
|
||||
/>
|
||||
<div className={classes.spacer} />
|
||||
<Typography variant="subtitle2">{`Slippage tolerance: ${slippage}%`}</Typography>
|
||||
<Typography variant="subtitle2">{`Relayer fee: ${RELAYER_FEE_UST} UST`}</Typography>
|
||||
{!isSwapping && <EthereumSignerKey />}
|
||||
<ButtonWithLoader
|
||||
<SwapButton
|
||||
source={sourceTokenInfo}
|
||||
target={targetTokenInfo}
|
||||
disabled={
|
||||
!readyToSwap ||
|
||||
isSwapping ||
|
||||
|
@ -425,11 +526,9 @@ export default function Home() {
|
|||
}
|
||||
showLoader={isSwapping}
|
||||
onClick={handleSwapClick}
|
||||
>
|
||||
Swap
|
||||
</ButtonWithLoader>
|
||||
/>
|
||||
</Collapse>
|
||||
<Collapse in={isFirstSwapComplete && !isSecondSwapComplete}>
|
||||
<Collapse in={isSourceSwapComplete && !isTargetSwapComplete}>
|
||||
<div className={classes.loaderHolder}>
|
||||
<CircleLoader />
|
||||
<div className={classes.spacer} />
|
||||
|
@ -438,14 +537,14 @@ export default function Home() {
|
|||
</Typography>
|
||||
</div>
|
||||
</Collapse>
|
||||
<Collapse in={isSecondSwapComplete}>
|
||||
<Collapse in={isTargetSwapComplete}>
|
||||
<div className={classes.loaderHolder}>
|
||||
<CheckCircleOutlineRounded
|
||||
className={classes.successIcon}
|
||||
fontSize={"inherit"}
|
||||
/>
|
||||
<Typography>Swap completed!</Typography>
|
||||
<ButtonWithLoader onClick={() => reset()}>
|
||||
<ButtonWithLoader onClick={reset}>
|
||||
Swap more tokens!
|
||||
</ButtonWithLoader>
|
||||
</div>
|
||||
|
@ -460,33 +559,48 @@ export default function Home() {
|
|||
{`${amountOut} ${targetTokenInfo.name}`}
|
||||
</Typography>
|
||||
)}
|
||||
{isFirstSwapComplete &&
|
||||
!isSecondSwapComplete &&
|
||||
{isSourceSwapComplete &&
|
||||
!isTargetSwapComplete &&
|
||||
!relayerTimeoutString && (
|
||||
<SwapProgress
|
||||
chainId={sourceTokenInfo.chainId}
|
||||
txBlockNumber={sourceTxBlockNumber}
|
||||
step={!hasSignedVAA ? 1 : !isSecondSwapComplete ? 2 : 3}
|
||||
/>
|
||||
<>
|
||||
<SwapProgress
|
||||
chainId={sourceTokenInfo.chainId}
|
||||
txBlockNumber={sourceTxBlockNumber}
|
||||
hasSignedVAA={hasSignedVAA}
|
||||
isTargetSwapComplete={isTargetSwapComplete}
|
||||
/>
|
||||
<div className={classes.spacer} />
|
||||
</>
|
||||
)}
|
||||
{relayerTimeoutString && (
|
||||
<Typography variant="subtitle1">{relayerTimeoutString}</Typography>
|
||||
)}
|
||||
<div className={classes.spacer} />
|
||||
<Typography variant="subtitle2" color="error">
|
||||
WARNING: this is a Testnet release only
|
||||
WARNING: this is a testnet release only
|
||||
</Typography>
|
||||
</Paper>
|
||||
<div className={classes.spacer} />
|
||||
<Footer />
|
||||
<Link href="https://goerli-faucet.slock.it/" style={{ margin: "5px" }}>
|
||||
Goerli faucet
|
||||
Goerli Faucet
|
||||
</Link>
|
||||
<Link
|
||||
href="https://faucet.polygon.technology/"
|
||||
style={{ margin: "5px" }}
|
||||
>
|
||||
Mumbai faucet
|
||||
Mumbai Faucet
|
||||
</Link>
|
||||
<Link
|
||||
href="https://faucet.avax-test.network/"
|
||||
style={{ margin: "5px" }}
|
||||
>
|
||||
Fuji Faucet
|
||||
</Link>
|
||||
<Link
|
||||
href="https://testnet.binance.org/faucet-smart/"
|
||||
style={{ margin: "5px" }}
|
||||
>
|
||||
BSC Faucet
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/certusone/wormhole-nativeswap-example/"
|
||||
|
|
|
@ -1,18 +1,44 @@
|
|||
# TestNet
|
||||
SPY_SERVICE_HOST=localhost:7073
|
||||
SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"}]
|
||||
# You can omit the following to get signed VAAs from every emitter.
|
||||
#SPY_SERVICE_FILTERS=[{"chain_id":2,"emitter_address":"0x000000000000000000000000f890982f9310df57d00f659cf4fd87e65aded8d7"},{"chain_id":3,"emitter_address":"terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a"},{"chain_id":4,"emitter_address":"0x0000000000000000000000009dcF9D205C9De35334D646BeE44b2D2859712A09"},{"chain_id":5,"emitter_address":"0x000000000000000000000000377d55a7928c046e18eebb61977e714d2a76472a"},{"chain_id":6,"emitter_address":"0x0000000000000000000000007bbcE28e64B3F8b84d876Ab298393c38ad7aac4C"}]
|
||||
|
||||
ETH_PROVIDER=https://goerli.infura.io/v3/your_project_id
|
||||
POLYGON_PROVIDER=https://polygon-mumbai.infura.io/v3/your_project_id
|
||||
EVM_CHAINS=ETH,BSC,POLYGON,AVAX
|
||||
|
||||
ETH_PROVIDER=https://goerli.infura.io/v3/YOUR_PROJECT_ID
|
||||
ETH_TOKEN_BRIDGE_ADDRESS=0xF890982f9310df57d00f659cf4fd87e65adEd8d7
|
||||
POLYGON_TOKEN_BRIDGE_ADDRESS=0x377D55a7928c046E18eEbb61977e714d2a76472a
|
||||
ETH_CHAIN_ID=2
|
||||
ETH_ABI=V3
|
||||
|
||||
# If these are defined, use them instead of the ones defined in the code.
|
||||
ETH_CONTRACT_ADDRESS=0x61D26732B190bdc5771e2a2b3ADB295e1b5A88BF
|
||||
POLYGON_CONTRACT_ADDRESS=0xc5Ba16A974a0c0E7935285d99F496Ee65eDFB8BA
|
||||
BSC_PROVIDER="https://data-seed-prebsc-1-s1.binance.org:8545"
|
||||
BSC_TOKEN_BRIDGE_ADDRESS=0x9dcF9D205C9De35334D646BeE44b2D2859712A09
|
||||
BSC_CHAIN_ID=4
|
||||
BSC_ABI=V2
|
||||
|
||||
POLYGON_PROVIDER=https://polygon-mumbai.infura.io/v3/YOUR_PROJECT_ID
|
||||
POLYGON_TOKEN_BRIDGE_ADDRESS=0x377D55a7928c046E18eEbb61977e714d2a76472a
|
||||
POLYGON_CHAIN_ID=5
|
||||
POLYGON_ABI=V2
|
||||
|
||||
AVAX_PROVIDER="https://api.avax-test.network/ext/bc/C/rpc"
|
||||
AVAX_TOKEN_BRIDGE_ADDRESS=0x61E44E506Ca5659E6c0bba9b678586fA2d729756
|
||||
AVAX_CHAIN_ID=6
|
||||
AVAX_ABI=V2
|
||||
|
||||
WALLET_PRIVATE_KEY=your_key_here
|
||||
|
||||
TERRA_PROVIDER=https://bombay-lcd.terra.dev
|
||||
TERRA_CHAIN_ID=bombay-12
|
||||
TERRA_NAME=testnet
|
||||
TERRA_WALLET_PRIVATE_KEY=your_key_here
|
||||
TERRA_GAS_PRICE_URL=https://bombay-fcd.terra.dev/v1/txs/gas_prices
|
||||
TERRA_TOKEN_BRIDGE_ADDRESS=terra1pseddrv0yfsn76u4zxrjmtf45kdlmalswdv39a
|
||||
|
||||
#LOG_DIR=/var/logs
|
||||
LOG_LEVEL=debug
|
||||
|
||||
ETH_CONTRACT_ADDRESS=0x9e7Cae3a46ED297b0a05FCEeb41160fC5218E14f
|
||||
BSC_CONTRACT_ADDRESS=0x0DC183c2eFAA5e1749B85f13621F5cC6aCcDa786
|
||||
POLYGON_CONTRACT_ADDRESS=0x72F2F646dC979a9fA8aA685B8a47b7afe2fE0516
|
||||
TERRA_CONTRACT_ADDRESS=terra163shc8unyqrndgcldaj2q9kgnqs82v0kgkhynf
|
||||
AVAX_CONTRACT_ADDRESS=0x52D8A50AF35b0760335F29a4D6aaF0604B7D7484
|
||||
|
|
|
@ -14,24 +14,25 @@
|
|||
"@types/jest": "^27.0.2",
|
||||
"@types/long": "^4.0.1",
|
||||
"@types/node": "^16.6.1",
|
||||
"async-mutex": "^0.3.2",
|
||||
"axios": "^0.24.0",
|
||||
"condition-variable": "^1.0.0",
|
||||
"esm": "^3.2.25",
|
||||
"ethers": "^5.5.3",
|
||||
"jest": "^27.3.1",
|
||||
"prettier": "^2.3.2",
|
||||
"ts-jest": "^27.0.7",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^4.3.5",
|
||||
"winston": "^3.3.3"
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "^0.1.4",
|
||||
"@certusone/wormhole-spydk": "^0.0.1",
|
||||
"async-mutex": "^0.3.2",
|
||||
"condition-variable": "^1.0.0",
|
||||
"body-parser": "^1.19.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^10.0.0"
|
||||
"dotenv": "^10.0.0",
|
||||
"ethers": "^5.5.3",
|
||||
"@terra-money/terra.js": "^3.0.4",
|
||||
"winston": "^3.3.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,478 @@
|
|||
import {
|
||||
getIsTransferCompletedEth,
|
||||
hexToUint8Array,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import { ethers } from "ethers";
|
||||
|
||||
import { abi as SWAP_CONTRACT_V2_ABI } from "../../react/src/abi/contracts/CrossChainSwapV2.json";
|
||||
import { abi as SWAP_CONTRACT_V3_ABI } from "../../react/src/abi/contracts/CrossChainSwapV3.json";
|
||||
|
||||
import * as swap from "../../react/src/swapper/helpers";
|
||||
|
||||
import { logger, OurEnvironment, Type3Payload } from "./index";
|
||||
|
||||
export type EvmEnvironment = {
|
||||
name: string;
|
||||
chain_id: number;
|
||||
provider_url: string;
|
||||
contract_address: string;
|
||||
token_bridge_address: string;
|
||||
wallet_private_key: string;
|
||||
abi_version: string;
|
||||
};
|
||||
|
||||
type EvmContractData = {
|
||||
chain_id: number;
|
||||
name: string;
|
||||
contractAddress: string;
|
||||
tokenBridgeAddress: string;
|
||||
contract: ethers.Contract;
|
||||
provider: ethers.providers.StaticJsonRpcProvider;
|
||||
wallet: ethers.Wallet;
|
||||
contractWithSigner: ethers.Contract;
|
||||
};
|
||||
|
||||
let evmContractData = new Map<number, EvmContractData>();
|
||||
|
||||
export function loadEvmConfig(): EvmEnvironment[] {
|
||||
let evm_configs: EvmEnvironment[] = [];
|
||||
let evms = process.env.EVM_CHAINS.split(",");
|
||||
for (const evm of evms) {
|
||||
let key_chain_id: string = evm + "_CHAIN_ID";
|
||||
let val_chain_id: string = eval("process.env." + key_chain_id);
|
||||
if (!val_chain_id) {
|
||||
logger.error("Missing environment variable " + key_chain_id);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let key_provider: string = evm + "_PROVIDER";
|
||||
let val_provider: string = eval("process.env." + key_provider);
|
||||
if (!val_provider) {
|
||||
logger.error("Missing environment variable " + key_provider);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let key_contract_address: string = evm + "_CONTRACT_ADDRESS";
|
||||
let val_contract_address: string = eval(
|
||||
"process.env." + key_contract_address
|
||||
);
|
||||
if (!val_contract_address) {
|
||||
logger.error("Missing environment variable " + key_contract_address);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let key_token_bridge_address: string = evm + "_TOKEN_BRIDGE_ADDRESS";
|
||||
let val_token_bridge_address: string = eval(
|
||||
"process.env." + key_token_bridge_address
|
||||
);
|
||||
if (!val_token_bridge_address) {
|
||||
logger.error("Missing environment variable " + key_token_bridge_address);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let key_wallet_private_key: string = evm + "_WALLET_PRIVATE_KEY";
|
||||
let val_wallet_private_key: string = eval(
|
||||
"process.env." + key_wallet_private_key
|
||||
);
|
||||
if (!val_wallet_private_key)
|
||||
val_wallet_private_key = process.env.WALLET_PRIVATE_KEY;
|
||||
if (!val_wallet_private_key) {
|
||||
logger.error(
|
||||
"Missing environment variable " +
|
||||
key_wallet_private_key +
|
||||
" or WALLET_PRIVATE_KEY"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let key_abi_version: string = evm + "_ABI";
|
||||
let val_abi_version: string = eval("process.env." + key_abi_version);
|
||||
if (!val_abi_version) {
|
||||
logger.error("Missing environment variable " + key_abi_version);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (val_abi_version !== "V2" && val_abi_version !== "V3") {
|
||||
logger.error(
|
||||
"Invalid value of environment variable " +
|
||||
key_abi_version +
|
||||
", is [" +
|
||||
val_abi_version +
|
||||
"], must be either V2 or V3"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
evm_configs.push({
|
||||
name: evm,
|
||||
chain_id: parseInt(val_chain_id),
|
||||
provider_url: val_provider,
|
||||
contract_address: val_contract_address,
|
||||
token_bridge_address: val_token_bridge_address,
|
||||
wallet_private_key: val_wallet_private_key,
|
||||
abi_version: val_abi_version,
|
||||
});
|
||||
}
|
||||
|
||||
return evm_configs;
|
||||
}
|
||||
|
||||
export function makeEvmContractData(envs: EvmEnvironment[]) {
|
||||
if (!envs) return;
|
||||
for (const evm of envs) {
|
||||
evmContractData.set(evm.chain_id, makeContractDataForEvm(evm));
|
||||
}
|
||||
}
|
||||
|
||||
function makeContractDataForEvm(env: EvmEnvironment): EvmContractData {
|
||||
let contractAddress: string = env.contract_address.toLowerCase();
|
||||
if (contractAddress.search("0x") === 0) {
|
||||
contractAddress = contractAddress.substring(2);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"Connecting to " +
|
||||
env.name +
|
||||
": chain_id: " +
|
||||
env.chain_id +
|
||||
", contract address: [" +
|
||||
contractAddress +
|
||||
"], node: [" +
|
||||
env.provider_url +
|
||||
"], token bridge address: [" +
|
||||
env.token_bridge_address +
|
||||
"], abi version: [" +
|
||||
env.abi_version +
|
||||
"]"
|
||||
);
|
||||
|
||||
const provider = new ethers.providers.StaticJsonRpcProvider(env.provider_url);
|
||||
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
env.abi_version == "V2" ? SWAP_CONTRACT_V2_ABI : SWAP_CONTRACT_V3_ABI,
|
||||
provider
|
||||
);
|
||||
|
||||
const wallet = new ethers.Wallet(env.wallet_private_key, provider);
|
||||
const contractWithSigner = contract.connect(wallet);
|
||||
|
||||
return {
|
||||
chain_id: env.chain_id,
|
||||
name: env.name,
|
||||
contractAddress: contractAddress,
|
||||
tokenBridgeAddress: env.token_bridge_address,
|
||||
contract: contract,
|
||||
provider: provider,
|
||||
wallet: wallet,
|
||||
contractWithSigner: contractWithSigner,
|
||||
};
|
||||
}
|
||||
|
||||
export function isEvmContract(
|
||||
contractAddress: string,
|
||||
chain_id: number
|
||||
): boolean {
|
||||
let ecd = evmContractData.get(chain_id);
|
||||
return ecd && ecd.contractAddress === contractAddress;
|
||||
}
|
||||
|
||||
/*
|
||||
// GOERLI_PROVIDER = Ethereum
|
||||
// MUMBAI_PROVIDER = Polygon
|
||||
|
||||
if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM) {
|
||||
// Use one of the V3 swap methods.
|
||||
} else if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON) {
|
||||
// Use one of the V2 swap methods.
|
||||
} else {
|
||||
// Error
|
||||
}
|
||||
|
||||
if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 1) {
|
||||
// swapExactInFromVaaNative
|
||||
} else if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 2) {
|
||||
// swapExactInFromVaaToken
|
||||
} else if (
|
||||
t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 1) {
|
||||
// swapExactOutFromVaaNative
|
||||
} else if (t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 2) {
|
||||
// swapExactOutFromVaaToken
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// GOERLI_PROVIDER = Ethereum
|
||||
// MUMBAI_PROVIDER = Polygon
|
||||
|
||||
if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM) {
|
||||
// Use one of the V3 swap methods.
|
||||
} else if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON) {
|
||||
// Use one of the V2 swap methods.
|
||||
} else {
|
||||
// Error
|
||||
}
|
||||
|
||||
if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 1) {
|
||||
// swapExactInFromVaaNative
|
||||
} else if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 2) {
|
||||
// swapExactInFromVaaToken
|
||||
} else if (
|
||||
t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 1) {
|
||||
// swapExactOutFromVaaNative
|
||||
} else if (t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 2) {
|
||||
// swapExactOutFromVaaToken
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
*/
|
||||
|
||||
export async function relayVaaToEvm(vaaBytes: string, t3Payload: Type3Payload) {
|
||||
let ecd = evmContractData.get(t3Payload.targetChainId);
|
||||
if (!ecd) {
|
||||
logger.error(
|
||||
"relayVaaToEvm: chain id " + t3Payload.targetChainId + " does not exist!"
|
||||
);
|
||||
}
|
||||
|
||||
let exactIn: boolean = false;
|
||||
let error: boolean = false;
|
||||
if (t3Payload.swapFunctionType === 1) {
|
||||
exactIn = true;
|
||||
} else if (t3Payload.swapFunctionType !== 2) {
|
||||
error = true;
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
ecd.name +
|
||||
": unsupported swapFunctionType: [" +
|
||||
t3Payload.swapFunctionType +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
let native: boolean = false;
|
||||
if (t3Payload.swapCurrencyType === 1) {
|
||||
native = true;
|
||||
} else if (t3Payload.swapCurrencyType !== 2) {
|
||||
error = true;
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
ecd.name +
|
||||
": unsupported swapCurrencyType: [" +
|
||||
t3Payload.swapCurrencyType +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
if (error) return;
|
||||
|
||||
logger.debug(
|
||||
"relayVaaTo" +
|
||||
ecd.name +
|
||||
": chain_id: " +
|
||||
ecd.chain_id +
|
||||
", contractAddress: [" +
|
||||
t3Payload.contractAddress +
|
||||
"]"
|
||||
);
|
||||
|
||||
const signedVaaArray = hexToUint8Array(vaaBytes);
|
||||
await relayVaaToEvmChain(t3Payload, ecd, signedVaaArray, exactIn, native);
|
||||
}
|
||||
|
||||
async function relayVaaToEvmChain(
|
||||
t3Payload: Type3Payload,
|
||||
tcd: EvmContractData,
|
||||
signedVaaArray: Uint8Array,
|
||||
exactIn: boolean,
|
||||
native: boolean
|
||||
) {
|
||||
logger.debug(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": checking if already redeemed on " +
|
||||
tcd.name +
|
||||
" using tokenBridgeAddress [" +
|
||||
tcd.tokenBridgeAddress +
|
||||
"]"
|
||||
);
|
||||
|
||||
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: already transferred"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": submitting redeem request"
|
||||
);
|
||||
|
||||
try {
|
||||
let receipt: any = null;
|
||||
if (exactIn) {
|
||||
if (native) {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaNative()");
|
||||
receipt = await swap.evmSwapExactInFromVaaNative(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
} else {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactInFromVaaToken()");
|
||||
receipt = await swap.evmSwapExactInFromVaaToken(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (native) {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaNative()");
|
||||
receipt = await swap.evmSwapExactOutFromVaaNative(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
} else {
|
||||
logger.debug("relayVaaTo: calling evmSwapExactOutFromVaaToken()");
|
||||
receipt = await swap.evmSwapExactOutFromVaaToken(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: success, txHash: " +
|
||||
receipt.transactionHash
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: relay failed because the vaa has already been redeemed"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": transaction failed: %o",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
if (await isRedeemedOnEvm(tcd, signedVaaArray)) {
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": redeem confirmed"
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": completed: failed to confirm redeem!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function isRedeemedOnEvm(
|
||||
tcd: EvmContractData,
|
||||
signedVaaArray: Uint8Array
|
||||
): Promise<boolean> {
|
||||
let redeemed: boolean = false;
|
||||
try {
|
||||
redeemed = await getIsTransferCompletedEth(
|
||||
tcd.tokenBridgeAddress,
|
||||
tcd.provider,
|
||||
signedVaaArray
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": failed to check if transfer is already complete, will attempt the transfer, e: %o",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
return redeemed;
|
||||
}
|
|
@ -10,7 +10,6 @@ import {
|
|||
getEmitterAddressEth,
|
||||
getEmitterAddressSolana,
|
||||
getEmitterAddressTerra,
|
||||
getIsTransferCompletedEth,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import {
|
||||
|
@ -25,14 +24,23 @@ import {
|
|||
|
||||
import { ethers } from "ethers";
|
||||
|
||||
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM } from "../../react/src/addresses/goerli";
|
||||
import { SWAP_CONTRACT_ADDRESS as CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON } from "../../react/src/addresses/mumbai";
|
||||
import { abi as SWAP_CONTRACT_V2_ABI } from "../../react/src/abi/contracts/CrossChainSwapV2.json";
|
||||
import { abi as SWAP_CONTRACT_V3_ABI } from "../../react/src/abi/contracts/CrossChainSwapV3.json";
|
||||
import {
|
||||
EvmEnvironment,
|
||||
isEvmContract,
|
||||
loadEvmConfig,
|
||||
makeEvmContractData,
|
||||
relayVaaToEvm,
|
||||
} from "./evm";
|
||||
|
||||
import * as swap from "../../react/src/swapper/util";
|
||||
import {
|
||||
isTerraContract,
|
||||
loadTerraConfig,
|
||||
makeTerraContractData,
|
||||
relayVaaToTerra,
|
||||
TerraEnvironment,
|
||||
} from "./terra";
|
||||
|
||||
let logger: any;
|
||||
export let logger: any;
|
||||
|
||||
let configFile: string = ".env";
|
||||
if (process.env.SWAP_RELAY_CONFIG) {
|
||||
|
@ -44,29 +52,17 @@ require("dotenv").config({ path: configFile });
|
|||
|
||||
initLogger();
|
||||
|
||||
type OurEnvironment = {
|
||||
export type OurEnvironment = {
|
||||
spy_host: string;
|
||||
spy_filters: string;
|
||||
eth_provider_url: string;
|
||||
polygon_provider_url: string;
|
||||
wallet_private_key: string;
|
||||
eth_contract_address: string;
|
||||
polygon_contract_address: string;
|
||||
eth_token_bridge_address: string;
|
||||
polygon_token_bridge_address: string;
|
||||
|
||||
evm_configs: EvmEnvironment[];
|
||||
terra_config: TerraEnvironment;
|
||||
};
|
||||
|
||||
type TargetContractData = {
|
||||
name: string;
|
||||
contractAddress: string;
|
||||
tokenBridgeAddress: string;
|
||||
contract: ethers.Contract;
|
||||
provider: ethers.providers.StaticJsonRpcProvider;
|
||||
wallet: ethers.Wallet;
|
||||
contractWithSigner: ethers.Contract;
|
||||
};
|
||||
|
||||
type Type3Payload = {
|
||||
export type Type3Payload = {
|
||||
sourceChainId: number;
|
||||
targetChainId: number;
|
||||
contractAddress: string;
|
||||
relayerFee: ethers.BigNumber;
|
||||
swapFunctionType: number;
|
||||
|
@ -85,8 +81,6 @@ let success: boolean;
|
|||
let env: OurEnvironment;
|
||||
[success, env] = loadConfig();
|
||||
|
||||
let ethContractData: TargetContractData = null;
|
||||
let polygonContractData: TargetContractData = null;
|
||||
let seqMap = new Map<string, number>();
|
||||
|
||||
const mutex = new Mutex();
|
||||
|
@ -95,14 +89,14 @@ let pendingQueue = new Array<PendingEvent>();
|
|||
|
||||
if (success) {
|
||||
logger.info(
|
||||
"swap_relay starting up, will listen for signed VAAs from [" +
|
||||
"swap_relayer starting up, will listen for signed VAAs from [" +
|
||||
env.spy_host +
|
||||
"]"
|
||||
);
|
||||
|
||||
try {
|
||||
ethContractData = makeEthContractData();
|
||||
polygonContractData = makePolygonContractData();
|
||||
makeEvmContractData(env.evm_configs);
|
||||
makeTerraContractData(env.terra_config);
|
||||
} catch (e: any) {
|
||||
logger.error("failed to connect to target contracts: %o", e);
|
||||
success = false;
|
||||
|
@ -119,39 +113,23 @@ function loadConfig(): [boolean, OurEnvironment] {
|
|||
logger.error("Missing environment variable SPY_SERVICE_HOST");
|
||||
return [false, undefined];
|
||||
}
|
||||
if (!process.env.ETH_PROVIDER) {
|
||||
logger.error("Missing environment variable ETH_PROVIDER");
|
||||
return [false, undefined];
|
||||
}
|
||||
if (!process.env.POLYGON_PROVIDER) {
|
||||
logger.error("Missing environment variable POLYGON_PROVIDER");
|
||||
return [false, undefined];
|
||||
}
|
||||
if (!process.env.WALLET_PRIVATE_KEY) {
|
||||
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
|
||||
return [false, undefined];
|
||||
}
|
||||
if (!process.env.ETH_TOKEN_BRIDGE_ADDRESS) {
|
||||
logger.error("Missing environment variable ETH_TOKEN_BRIDGE_ADDRESS");
|
||||
return [false, undefined];
|
||||
}
|
||||
if (!process.env.POLYGON_TOKEN_BRIDGE_ADDRESS) {
|
||||
logger.error("Missing environment variable POLYGON_TOKEN_BRIDGE_ADDRESS");
|
||||
return [false, undefined];
|
||||
|
||||
let evm_configs: EvmEnvironment[] = null;
|
||||
if (process.env.EVM_CHAINS) {
|
||||
evm_configs = loadEvmConfig();
|
||||
if (!evm_configs) return [false, undefined];
|
||||
}
|
||||
|
||||
let terra_config = loadTerraConfig();
|
||||
if (!terra_config) return [false, undefined];
|
||||
|
||||
return [
|
||||
true,
|
||||
{
|
||||
spy_host: process.env.SPY_SERVICE_HOST,
|
||||
spy_filters: process.env.SPY_SERVICE_FILTERS,
|
||||
eth_provider_url: process.env.ETH_PROVIDER,
|
||||
polygon_provider_url: process.env.POLYGON_PROVIDER,
|
||||
wallet_private_key: process.env.WALLET_PRIVATE_KEY,
|
||||
eth_contract_address: process.env.ETH_CONTRACT_ADDRESS,
|
||||
polygon_contract_address: process.env.POLYGON_CONTRACT_ADDRESS,
|
||||
eth_token_bridge_address: process.env.ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
polygon_token_bridge_address: process.env.POLYGON_TOKEN_BRIDGE_ADDRESS,
|
||||
evm_configs: evm_configs,
|
||||
terra_config: terra_config,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -200,7 +178,7 @@ async function spy_listen() {
|
|||
processVaa(vaaBytes);
|
||||
});
|
||||
|
||||
logger.info("swap_relay waiting for transfer signed VAAs");
|
||||
logger.info("swap_relayer waiting for transfer signed VAAs");
|
||||
})();
|
||||
}
|
||||
|
||||
|
@ -221,10 +199,10 @@ async function encodeEmitterAddress(
|
|||
|
||||
async function processVaa(vaaBytes: string) {
|
||||
let receiveTime = new Date();
|
||||
logger.debug("processVaa");
|
||||
// logger.debug("processVaa");
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
const parsedVAA = parse_vaa(hexToUint8Array(vaaBytes));
|
||||
logger.debug("processVaa: parsedVAA: %o", parsedVAA);
|
||||
// logger.debug("processVaa: parsedVAA: %o", parsedVAA);
|
||||
|
||||
let emitter_address: string = uint8ArrayToHex(parsedVAA.emitter_address);
|
||||
|
||||
|
@ -245,11 +223,16 @@ async function processVaa(vaaBytes: string) {
|
|||
|
||||
seqMap.set(seqNumKey, parsedVAA.sequence);
|
||||
|
||||
let payload_type: number = parsedVAA.payload[0];
|
||||
let t3Payload: Type3Payload = null;
|
||||
try {
|
||||
t3Payload = decodeSignedVAAPayloadType3(parsedVAA, parsedVAA.emitter_chain);
|
||||
} catch (e) {
|
||||
logger.error("failed to parse type 3 vaa: %o", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let t3Payload = decodeSignedVAAPayloadType3(parsedVAA);
|
||||
if (t3Payload) {
|
||||
if (isOurContract(t3Payload.contractAddress)) {
|
||||
if (isOurContract(t3Payload.contractAddress, t3Payload.targetChainId)) {
|
||||
logger.info(
|
||||
"enqueuing type 3 vaa: emitter: [" +
|
||||
parsedVAA.emitter_chain +
|
||||
|
@ -257,7 +240,9 @@ async function processVaa(vaaBytes: string) {
|
|||
emitter_address +
|
||||
"], seqNum: " +
|
||||
parsedVAA.sequence +
|
||||
", contractAddress: [" +
|
||||
", target: [" +
|
||||
t3Payload.targetChainId +
|
||||
":" +
|
||||
t3Payload.contractAddress +
|
||||
"], relayerFee: [" +
|
||||
t3Payload.relayerFee +
|
||||
|
@ -277,7 +262,9 @@ async function processVaa(vaaBytes: string) {
|
|||
emitter_address +
|
||||
"], seqNum: " +
|
||||
parsedVAA.sequence +
|
||||
", contractAddress: [" +
|
||||
", target: [" +
|
||||
t3Payload.targetChainId +
|
||||
":" +
|
||||
t3Payload.contractAddress +
|
||||
"], relayerFee: [" +
|
||||
t3Payload.relayerFee +
|
||||
|
@ -288,39 +275,81 @@ async function processVaa(vaaBytes: string) {
|
|||
"]"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
"dropping vaa: emitter: [" +
|
||||
parsedVAA.emitter_chain +
|
||||
":" +
|
||||
emitter_address +
|
||||
"], seqNum: " +
|
||||
parsedVAA.sequence +
|
||||
" payloadType: " +
|
||||
payload_type
|
||||
);
|
||||
// } else {
|
||||
// logger.debug(
|
||||
// "dropping vaa: emitter: [" +
|
||||
// parsedVAA.emitter_chain +
|
||||
// ":" +
|
||||
// emitter_address +
|
||||
// "], seqNum: " +
|
||||
// parsedVAA.sequence +
|
||||
// " payloadType: " +
|
||||
// parsedVAA.payload[0]
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
function decodeSignedVAAPayloadType3(parsedVAA: any): Type3Payload {
|
||||
function decodeSignedVAAPayloadType3(
|
||||
parsedVAA: any,
|
||||
sourceChainId: number
|
||||
): Type3Payload {
|
||||
const payload = Buffer.from(new Uint8Array(parsedVAA.payload));
|
||||
const version = payload.readUInt8(0);
|
||||
if (payload[0] !== 3) return undefined;
|
||||
|
||||
if (version !== 3) {
|
||||
logger.info("decodeSignedVAAPayloadType3: length: " + payload.length);
|
||||
if (payload.length < 101) {
|
||||
logger.error(
|
||||
"decodeSignedVAAPayloadType3: dropping type 3 vaa because the payload is too short to determine the target chain id, length: " +
|
||||
payload.length
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const targetChainId = payload.readUInt16BE(99);
|
||||
logger.info("decodeSignedVAAPayloadType3: target ChainId: " + targetChainId);
|
||||
|
||||
let contractAddress: string = "";
|
||||
let swapFunctionType: number = 0;
|
||||
let swapCurrencyType: number = 0;
|
||||
|
||||
if (targetChainId === 3) {
|
||||
logger.info(
|
||||
"decodeSignedVAAPayloadType3: terraContractAddr: [" +
|
||||
payload.slice(67, 67 + 32).toString("hex") +
|
||||
"]"
|
||||
);
|
||||
|
||||
contractAddress = payload.slice(67, 67 + 32).toString("hex");
|
||||
} else {
|
||||
if (payload.length < 262) {
|
||||
logger.error(
|
||||
"decodeSignedVAAPayloadType3: dropping type 3 vaa because the payload is too short to extract the contract fields, length: " +
|
||||
payload.length +
|
||||
", target chain id: " +
|
||||
targetChainId
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
contractAddress = payload.slice(79, 79 + 20).toString("hex");
|
||||
swapFunctionType = payload.readUInt8(272);
|
||||
swapCurrencyType = payload.readUInt8(273);
|
||||
}
|
||||
|
||||
return {
|
||||
contractAddress: payload.slice(79, 79 + 20).toString("hex"),
|
||||
sourceChainId: sourceChainId,
|
||||
targetChainId: targetChainId,
|
||||
contractAddress: contractAddress,
|
||||
relayerFee: ethers.BigNumber.from(payload.slice(101, 101 + 32)),
|
||||
swapFunctionType: payload.readUInt8(260),
|
||||
swapCurrencyType: payload.readUInt8(261),
|
||||
swapFunctionType: swapFunctionType,
|
||||
swapCurrencyType: swapCurrencyType,
|
||||
};
|
||||
}
|
||||
|
||||
function isOurContract(contractAddress: string): boolean {
|
||||
function isOurContract(contractAddress: string, chainId: number): boolean {
|
||||
return (
|
||||
contractAddress === ethContractData.contractAddress ||
|
||||
contractAddress === polygonContractData.contractAddress
|
||||
isEvmContract(contractAddress, chainId) ||
|
||||
isTerraContract(contractAddress, chainId)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -408,379 +437,13 @@ async function callBack(err: any, result: any) {
|
|||
// logger.debug("leaving callback.");
|
||||
}
|
||||
|
||||
// Ethereum (Goerli) set up
|
||||
function makeEthContractData(): TargetContractData {
|
||||
let overridden: boolean = false;
|
||||
let contractAddress: string = CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM;
|
||||
if (env.eth_contract_address) {
|
||||
contractAddress = env.eth_contract_address;
|
||||
overridden = true;
|
||||
}
|
||||
|
||||
contractAddress = contractAddress.toLowerCase();
|
||||
if (contractAddress.search("0x") == 0) {
|
||||
contractAddress = contractAddress.substring(2);
|
||||
}
|
||||
|
||||
if (overridden) {
|
||||
logger.info(
|
||||
"Connecting to Ethereum: overriding contract address: [" +
|
||||
contractAddress +
|
||||
"], node: [" +
|
||||
env.eth_provider_url +
|
||||
"], token bridge address: [" +
|
||||
env.eth_token_bridge_address +
|
||||
"]"
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
"Connecting to Ethereum: contract address: [" +
|
||||
contractAddress +
|
||||
"], node: [" +
|
||||
env.eth_provider_url +
|
||||
"], token bridge address: [" +
|
||||
env.eth_token_bridge_address +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
const provider = new ethers.providers.StaticJsonRpcProvider(
|
||||
env.eth_provider_url
|
||||
);
|
||||
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
SWAP_CONTRACT_V3_ABI,
|
||||
provider
|
||||
);
|
||||
|
||||
const wallet = new ethers.Wallet(env.wallet_private_key, provider);
|
||||
const contractWithSigner = contract.connect(wallet);
|
||||
|
||||
return {
|
||||
name: "Ethereum",
|
||||
contractAddress: contractAddress,
|
||||
tokenBridgeAddress: env.eth_token_bridge_address,
|
||||
contract: contract,
|
||||
provider: provider,
|
||||
wallet: wallet,
|
||||
contractWithSigner: contractWithSigner,
|
||||
};
|
||||
}
|
||||
|
||||
// Polygon (Mumbai) set up
|
||||
function makePolygonContractData(): TargetContractData {
|
||||
let overridden: boolean = false;
|
||||
let contractAddress: string = CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON;
|
||||
if (env.polygon_contract_address) {
|
||||
contractAddress = env.polygon_contract_address;
|
||||
overridden = true;
|
||||
}
|
||||
|
||||
contractAddress = contractAddress.toLowerCase();
|
||||
if (contractAddress.search("0x") == 0) {
|
||||
contractAddress = contractAddress.substring(2);
|
||||
}
|
||||
|
||||
if (overridden) {
|
||||
logger.info(
|
||||
"Connecting to Polygon: overriding contract address: [" +
|
||||
contractAddress +
|
||||
"], node: [" +
|
||||
env.polygon_provider_url +
|
||||
"], token bridge address: [" +
|
||||
env.polygon_token_bridge_address +
|
||||
"]"
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
"Connecting to Polygon: contract address: [" +
|
||||
contractAddress +
|
||||
"], node: [" +
|
||||
env.polygon_provider_url +
|
||||
"], token bridge address: [" +
|
||||
env.polygon_token_bridge_address +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
const provider = new ethers.providers.StaticJsonRpcProvider(
|
||||
env.polygon_provider_url
|
||||
);
|
||||
|
||||
const contract = new ethers.Contract(
|
||||
contractAddress,
|
||||
SWAP_CONTRACT_V2_ABI,
|
||||
provider
|
||||
);
|
||||
|
||||
const wallet = new ethers.Wallet(env.wallet_private_key, provider);
|
||||
const contractWithSigner = contract.connect(wallet);
|
||||
|
||||
return {
|
||||
name: "Polygon",
|
||||
contractAddress: contractAddress,
|
||||
tokenBridgeAddress: env.polygon_token_bridge_address,
|
||||
contract: contract,
|
||||
provider: provider,
|
||||
wallet: wallet,
|
||||
contractWithSigner: contractWithSigner,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
// GOERLI_PROVIDER = Ethereum
|
||||
// MUMBAI_PROVIDER = Polygon
|
||||
|
||||
if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_ETHEREUM) {
|
||||
// Use one of the V3 swap methods.
|
||||
} else if (t3Payload.contractAddress === CROSSCHAINSWAP_CONTRACT_ADDRESS_POLYGON) {
|
||||
// Use one of the V2 swap methods.
|
||||
} else {
|
||||
// Error
|
||||
}
|
||||
|
||||
if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 1) {
|
||||
// swapExactInFromVaaNative
|
||||
} else if (t3Payload.swapFunctionType === 1 && t3Payload.swapCurrencyType === 2) {
|
||||
// swapExactInFromVaaToken
|
||||
} else if (
|
||||
t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 1) {
|
||||
// swapExactOutFromVaaNative
|
||||
} else if (t3Payload.swapFunctionType === 2 && t3Payload.swapCurrencyType === 2) {
|
||||
// swapExactOutFromVaaToken
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
*/
|
||||
|
||||
async function relayVaa(vaaBytes: string, t3Payload: Type3Payload) {
|
||||
const signedVaaArray = hexToUint8Array(vaaBytes);
|
||||
|
||||
let exactIn: boolean = false;
|
||||
if (t3Payload.swapFunctionType === 1) {
|
||||
exactIn = true;
|
||||
} else if (t3Payload.swapFunctionType !== 2) {
|
||||
logger.error(
|
||||
"relayVaa: unsupported swapFunctionType: [" +
|
||||
t3Payload.swapFunctionType +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
let native: boolean = false;
|
||||
if (t3Payload.swapCurrencyType === 1) {
|
||||
native = true;
|
||||
} else if (t3Payload.swapCurrencyType !== 2) {
|
||||
logger.error(
|
||||
"relayVaa: unsupported swapCurrencyType: [" +
|
||||
t3Payload.swapCurrencyType +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
"relayVaa: contractAddress: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], ethContract: [" +
|
||||
ethContractData.contractAddress +
|
||||
"], polygonContract[" +
|
||||
polygonContractData.contractAddress +
|
||||
"]"
|
||||
);
|
||||
|
||||
if (t3Payload.contractAddress === ethContractData.contractAddress) {
|
||||
await relayVaaToChain(
|
||||
t3Payload,
|
||||
ethContractData,
|
||||
signedVaaArray,
|
||||
exactIn,
|
||||
native
|
||||
);
|
||||
} else if (
|
||||
t3Payload.contractAddress === polygonContractData.contractAddress
|
||||
) {
|
||||
await relayVaaToChain(
|
||||
t3Payload,
|
||||
polygonContractData,
|
||||
signedVaaArray,
|
||||
exactIn,
|
||||
native
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
"relayVaa: unexpected contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], this should not happen!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function relayVaaToChain(
|
||||
t3Payload: Type3Payload,
|
||||
tcd: TargetContractData,
|
||||
signedVaaArray: Uint8Array,
|
||||
exactIn: boolean,
|
||||
native: boolean
|
||||
) {
|
||||
logger.debug(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": checking if already redeemed on " +
|
||||
tcd.name +
|
||||
" using tokenBridgeAddress [" +
|
||||
tcd.tokenBridgeAddress +
|
||||
"]"
|
||||
);
|
||||
|
||||
if (await isRedeemed(t3Payload, tcd, signedVaaArray)) {
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": already transferred"
|
||||
);
|
||||
|
||||
if (t3Payload.targetChainId === 3) {
|
||||
await relayVaaToTerra(t3Payload, vaaBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": submitting redeem request"
|
||||
);
|
||||
|
||||
try {
|
||||
let receipt: any = null;
|
||||
if (exactIn) {
|
||||
if (native) {
|
||||
receipt = await swap.swapExactInFromVaaNative(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
} else {
|
||||
receipt = await swap.swapExactInFromVaaToken(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (native) {
|
||||
receipt = await swap.swapExactOutFromVaaNative(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
} else {
|
||||
receipt = await swap.swapExactOutFromVaaToken(
|
||||
tcd.contractWithSigner,
|
||||
signedVaaArray
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": success, txHash: " +
|
||||
receipt.transactionHash
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (await isRedeemed(t3Payload, tcd, signedVaaArray)) {
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": relay failed because the vaa has already been redeemed"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": transaction failed: %o",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
if (await isRedeemed(t3Payload, tcd, signedVaaArray)) {
|
||||
logger.info(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": redeem succeeded"
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"], exactIn: " +
|
||||
exactIn +
|
||||
", native: " +
|
||||
native +
|
||||
": redeem failed!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function isRedeemed(
|
||||
t3Payload: Type3Payload,
|
||||
tcd: TargetContractData,
|
||||
signedVaaArray: Uint8Array
|
||||
): Promise<boolean> {
|
||||
let redeemed: boolean = false;
|
||||
try {
|
||||
redeemed = await getIsTransferCompletedEth(
|
||||
tcd.tokenBridgeAddress,
|
||||
tcd.provider,
|
||||
signedVaaArray
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"relayVaaTo" +
|
||||
tcd.name +
|
||||
": failed to check if transfer is already complete, will attempt the transfer, e: %o",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
return redeemed;
|
||||
await relayVaaToEvm(vaaBytes, t3Payload);
|
||||
}
|
||||
|
||||
///////////////////////////////// Start of logger stuff ///////////////////////////////////////////
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
import {
|
||||
CHAIN_ID_TERRA,
|
||||
getIsTransferCompletedTerra,
|
||||
redeemOnTerra,
|
||||
uint8ArrayToHex,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import axios from "axios";
|
||||
import { bech32 } from "bech32";
|
||||
import { zeroPad } from "ethers/lib/utils";
|
||||
import { fromUint8Array } from "js-base64";
|
||||
|
||||
import * as Terra from "@terra-money/terra.js";
|
||||
|
||||
import { logger, OurEnvironment, Type3Payload } from "./index";
|
||||
|
||||
export type TerraEnvironment = {
|
||||
terra_enabled: boolean;
|
||||
terra_provider_url: string;
|
||||
terra_chain_id: string;
|
||||
terra_name: string;
|
||||
terra_contract_address: string;
|
||||
terra_token_bridge_address: string;
|
||||
terra_wallet_private_key: string;
|
||||
terra_gas_price_url: string;
|
||||
};
|
||||
|
||||
type TerraContractData = {
|
||||
name: string;
|
||||
contractAddress: string;
|
||||
encodedContractAddress: string;
|
||||
tokenBridgeAddress: string;
|
||||
lcdConfig: Terra.LCDClientConfig;
|
||||
lcdClient: Terra.LCDClient;
|
||||
wallet: Terra.Wallet;
|
||||
gasPriceUrl: string;
|
||||
};
|
||||
|
||||
let terraContractData: TerraContractData = null;
|
||||
|
||||
export function loadTerraConfig(): TerraEnvironment {
|
||||
let terra_enabled: boolean = false;
|
||||
if (process.env.TERRA_PROVIDER) {
|
||||
terra_enabled = true;
|
||||
|
||||
if (!process.env.TERRA_CHAIN_ID) {
|
||||
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!process.env.TERRA_NAME) {
|
||||
logger.error("Missing environment variable WALLET_PRIVATE_KEY");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!process.env.TERRA_WALLET_PRIVATE_KEY) {
|
||||
logger.error("Missing environment variable TERRA_WALLET_PRIVATE_KEY");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!process.env.TERRA_GAS_PRICE_URL) {
|
||||
logger.error("Missing environment variable TERRA_GAS_PRICE_URL");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!process.env.TERRA_CONTRACT_ADDRESS) {
|
||||
logger.error("Missing environment variable TERRA_CONTRACT_ADDRESS");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!process.env.TERRA_TOKEN_BRIDGE_ADDRESS) {
|
||||
logger.error("Missing environment variable TERRA_TOKEN_BRIDGE_ADDRESS");
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
terra_enabled: terra_enabled,
|
||||
terra_provider_url: process.env.TERRA_PROVIDER,
|
||||
terra_chain_id: process.env.TERRA_CHAIN_ID,
|
||||
terra_name: process.env.TERRA_NAME,
|
||||
terra_contract_address: process.env.TERRA_CONTRACT_ADDRESS,
|
||||
terra_token_bridge_address: process.env.TERRA_TOKEN_BRIDGE_ADDRESS,
|
||||
terra_wallet_private_key: process.env.TERRA_WALLET_PRIVATE_KEY,
|
||||
terra_gas_price_url: process.env.TERRA_GAS_PRICE_URL,
|
||||
};
|
||||
}
|
||||
|
||||
export function makeTerraContractData(env: TerraEnvironment) {
|
||||
if (!env.terra_enabled) return;
|
||||
let contractAddress: string = env.terra_contract_address.toLowerCase();
|
||||
if (contractAddress.search("0x") == 0) {
|
||||
contractAddress = contractAddress.substring(2);
|
||||
}
|
||||
|
||||
let encodedContractAddress: string = Buffer.from(
|
||||
zeroPad(bech32.fromWords(bech32.decode(contractAddress).words), 32)
|
||||
).toString("hex");
|
||||
|
||||
logger.info(
|
||||
"Connecting to Terra: contract address: [" +
|
||||
contractAddress +
|
||||
"], encoded contract address: [" +
|
||||
encodedContractAddress +
|
||||
"], node: [" +
|
||||
env.terra_provider_url +
|
||||
"], token bridge address: [" +
|
||||
env.terra_token_bridge_address +
|
||||
"], terra chain id: [" +
|
||||
env.terra_chain_id +
|
||||
"], terra name: [" +
|
||||
env.terra_name +
|
||||
"]"
|
||||
);
|
||||
|
||||
const lcdConfig = {
|
||||
URL: env.terra_provider_url,
|
||||
chainID: env.terra_chain_id,
|
||||
name: env.terra_name,
|
||||
};
|
||||
|
||||
const lcdClient = new Terra.LCDClient(lcdConfig);
|
||||
|
||||
const mk = new Terra.MnemonicKey({
|
||||
mnemonic: env.terra_wallet_private_key,
|
||||
});
|
||||
|
||||
const wallet = lcdClient.wallet(mk);
|
||||
|
||||
terraContractData = {
|
||||
name: "Terra",
|
||||
contractAddress: contractAddress,
|
||||
encodedContractAddress: encodedContractAddress,
|
||||
tokenBridgeAddress: env.terra_token_bridge_address,
|
||||
lcdConfig: lcdConfig,
|
||||
lcdClient: lcdClient,
|
||||
wallet: wallet,
|
||||
gasPriceUrl: env.terra_gas_price_url,
|
||||
};
|
||||
}
|
||||
|
||||
export function isTerraContract(
|
||||
contractAddress: string,
|
||||
chain_id: number
|
||||
): boolean {
|
||||
if (chain_id !== CHAIN_ID_TERRA) return false;
|
||||
if (!terraContractData) return false;
|
||||
|
||||
let retVal: boolean =
|
||||
terraContractData &&
|
||||
contractAddress === terraContractData.encodedContractAddress;
|
||||
logger.debug(
|
||||
"isTerraContract: comparing [" +
|
||||
contractAddress +
|
||||
"] to [" +
|
||||
terraContractData.encodedContractAddress +
|
||||
"]: " +
|
||||
retVal
|
||||
);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
export async function relayVaaToTerra(
|
||||
t3Payload: Type3Payload,
|
||||
vaaBytes: string
|
||||
) {
|
||||
if (!terraContractData) return;
|
||||
|
||||
// logger.debug(
|
||||
// "relayVaaToTerra: checking if already redeemed using tokenBridgeAddress [" +
|
||||
// terraContractData.tokenBridgeAddress +
|
||||
// "]"
|
||||
// );
|
||||
|
||||
// if (await isRedeemedOnTerra(terraContractData, vaaBytes)) {
|
||||
// logger.info(
|
||||
// "relayVaaToTerra: srcChain: " +
|
||||
// t3Payload.sourceChainId +
|
||||
// ", targetChain: " +
|
||||
// t3Payload.targetChainId +
|
||||
// ", contract: [" +
|
||||
// t3Payload.contractAddress +
|
||||
// "]: completed: already redeemed"
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
try {
|
||||
logger.debug(
|
||||
"relayVaaToTerra: creating message using contract address [" +
|
||||
terraContractData.contractAddress +
|
||||
"]"
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
"relayVaaToTerra: vaa as hex: [" +
|
||||
Buffer.from(vaaBytes, "hex").toString("hex") +
|
||||
"]"
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
"relayVaaToTerra: vaa as base64: [" +
|
||||
Buffer.from(vaaBytes, "hex").toString("base64") +
|
||||
"]"
|
||||
);
|
||||
const msg = new Terra.MsgExecuteContract(
|
||||
terraContractData.wallet.key.accAddress,
|
||||
terraContractData.contractAddress,
|
||||
{
|
||||
submit_vaa: {
|
||||
data: Buffer.from(vaaBytes, "hex").toString("base64"),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// logger.debug("relayVaaToTerra: getting gas prices");
|
||||
// const gasPrices = terraContractData.lcdClient.config.gasPrices;
|
||||
|
||||
// logger.debug("relayVaaToTerra: estimating fees");
|
||||
// const feeEstimate = await terraContractData.lcdClient.tx.estimateFee(terraContractData.wallet.key.accAddress, [msg], {
|
||||
// feeDenoms: ["uluna"],
|
||||
// gasPrices,
|
||||
// });
|
||||
|
||||
logger.debug("relayVaaToTerra: creating transaction");
|
||||
const tx = await terraContractData.wallet.createAndSignTx({
|
||||
msgs: [msg],
|
||||
memo: "swap relayer",
|
||||
feeDenoms: ["uluna"],
|
||||
// gasPrices,
|
||||
// fee: feeEstimate,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
"relayVaaToTerra: contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"]: submitting redeem request"
|
||||
);
|
||||
|
||||
const receipt = await terraContractData.lcdClient.tx.broadcast(tx);
|
||||
|
||||
logger.info(
|
||||
"relayVaaToTerra: srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"]: completed: success: %o",
|
||||
receipt
|
||||
);
|
||||
|
||||
// logger.info(
|
||||
// "relayVaaToTerra: contract: [" +
|
||||
// t3Payload.contractAddress +
|
||||
// "]: success, txHash: " +
|
||||
// receipt.transactionHash
|
||||
// );
|
||||
} catch (e: any) {
|
||||
// if (await isRedeemedOnTerra(terraContractData, vaaBytes)) {
|
||||
// logger.info(
|
||||
// "relayVaaToTerra: srcChain: " +
|
||||
// t3Payload.sourceChainId +
|
||||
// ", targetChain: " +
|
||||
// t3Payload.targetChainId +
|
||||
// ", contract: [" +
|
||||
// t3Payload.contractAddress +
|
||||
// "]: completed: relay failed because the vaa has already been redeemed"
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
logger.error(
|
||||
"relayVaaToTerra: srcChain: " +
|
||||
t3Payload.sourceChainId +
|
||||
", targetChain: " +
|
||||
t3Payload.targetChainId +
|
||||
", contract: [" +
|
||||
t3Payload.contractAddress +
|
||||
"]: completed: transaction failed: %o",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// if (await isRedeemedOnTerra(terraContractData, vaaBytes)) {
|
||||
// logger.info(
|
||||
// "relayVaaToTerra: srcChain: " +
|
||||
// t3Payload.sourceChainId +
|
||||
// ", targetChain: " +
|
||||
// t3Payload.targetChainId +
|
||||
// ", contract: [" +
|
||||
// t3Payload.contractAddress +
|
||||
// "]: redeem confirmed"
|
||||
// );
|
||||
// } else {
|
||||
// logger.error(
|
||||
// "relayVaaToTerra: srcChain: " +
|
||||
// t3Payload.sourceChainId +
|
||||
// ", targetChain: " +
|
||||
// t3Payload.targetChainId +
|
||||
// ", contract: [" +
|
||||
// t3Payload.contractAddress +
|
||||
// "]: completed: failed to confirm redeem!"
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
async function isRedeemedOnTerra(
|
||||
terraContractData: TerraContractData,
|
||||
vaaBytes: string
|
||||
): Promise<boolean> {
|
||||
let msg: Terra.MsgExecuteContract = null;
|
||||
let sequenceNumber: number = 0;
|
||||
try {
|
||||
msg = new Terra.MsgExecuteContract(
|
||||
terraContractData.wallet.key.accAddress,
|
||||
terraContractData.tokenBridgeAddress,
|
||||
{
|
||||
submit_vaa: {
|
||||
data: Buffer.from(vaaBytes, "hex").toString("base64"),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
sequenceNumber = await terraContractData.wallet.sequence();
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"isRedeemedOnTerra: failed to create message or look up sequence number, e: %o",
|
||||
e
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await terraContractData.lcdClient.tx.estimateFee(
|
||||
[{ sequenceNumber: sequenceNumber }],
|
||||
{
|
||||
msgs: [msg],
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.response.data.error.includes("VaaAlreadyExecuted")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.error(
|
||||
"isRedeemedOnTerra: failed to check if transfer is already complete, e: %o",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
Loading…
Reference in New Issue