├── .env.local ├── .gitattributes ├── .github └── workflows │ ├── test.yaml │ └── test_dev.yaml ├── .gitignore ├── README.md ├── config.json ├── contracts ├── Meson.sol ├── MesonManager.sol ├── Pools │ ├── IMesonPoolsEvents.sol │ └── MesonPools.sol ├── Proxy2ToMeson.sol ├── ProxyToMeson.sol ├── Swap │ ├── IMesonSwapEvents.sol │ └── MesonSwap.sol ├── Token │ ├── PoDUpgradeable.sol │ └── UCTUpgradeable.sol ├── UpgradableMeson.sol ├── interfaces │ ├── IDepositWithBeneficiary.sol │ └── IERC20Minimal.sol ├── test │ ├── ForwardTokenContract.sol │ ├── MesonPoolsTest.sol │ ├── MesonStatesTest.sol │ ├── MesonSwapTest.sol │ └── MockToken.sol └── utils │ ├── MesonHelpers.sol │ ├── MesonStates.sol │ └── MesonTokens.sol ├── funding.json ├── hardhat.config.ts ├── package.json ├── packages ├── config │ ├── .babelrc │ ├── .gitignore │ ├── abis │ │ ├── ERC20Abi.d.ts │ │ ├── MesonAbi.d.ts │ │ ├── index.d.ts │ │ ├── index.js │ │ └── index.mjs │ ├── config │ │ ├── index.d.ts │ │ ├── index.js │ │ └── index.mjs │ ├── networks │ │ ├── Token.d.ts │ │ ├── category.json │ │ ├── config.d.ts │ │ ├── index.d.ts │ │ ├── index.mjs │ │ ├── mainnets.json │ │ ├── testnets.json │ │ └── v0 │ │ │ ├── mainnets.json │ │ │ └── testnets.json │ └── package.json ├── presets │ ├── .gitignore │ ├── package.json │ ├── scripts │ │ ├── chains.json │ │ ├── make-mainnets.js │ │ └── make-testnets.js │ ├── src │ │ ├── CustomFeeProvider.ts │ │ ├── MesonPresets.ts │ │ ├── index.ts │ │ └── providers.ts │ └── tsconfig.json └── sdk │ ├── .gitignore │ ├── package.json │ ├── src │ ├── MesonClient.ts │ ├── Rpc.ts │ ├── SignedSwap.ts │ ├── Swap.ts │ ├── SwapSigner.ts │ ├── SwapWithSigner.ts │ ├── adaptors │ │ ├── FailoverAdaptors.ts │ │ ├── aptos │ │ │ ├── AptosAdaptor.ts │ │ │ ├── AptosWallet.ts │ │ │ └── index.ts │ │ ├── bitcoin │ │ │ ├── BtcAdaptor.ts │ │ │ ├── BtcClient.ts │ │ │ ├── BtcWallet.ts │ │ │ └── index.ts │ │ ├── ckb │ │ │ ├── CkbAdaptor.ts │ │ │ ├── CkbWallet.ts │ │ │ └── index.ts │ │ ├── ethers │ │ │ ├── EthersAdaptor.ts │ │ │ ├── extendProvider.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── solana │ │ │ ├── SolanaAdaptor.ts │ │ │ ├── SolanaWallet.ts │ │ │ └── index.ts │ │ ├── starknet │ │ │ ├── StarkAdaptor.ts │ │ │ ├── StarkWallet.ts │ │ │ ├── abi │ │ │ │ ├── ERC20.json │ │ │ │ └── Meson.json │ │ │ ├── index.ts │ │ │ └── parse │ │ │ │ ├── index.ts │ │ │ │ ├── v0.json │ │ │ │ └── v1.json │ │ ├── sui │ │ │ ├── SuiAdaptor.ts │ │ │ ├── SuiWallet.ts │ │ │ └── index.ts │ │ ├── ton │ │ │ ├── TonAdaptor.ts │ │ │ ├── TonWallet.ts │ │ │ ├── index.ts │ │ │ ├── index_archive.ts │ │ │ └── types │ │ │ │ └── index.ts │ │ ├── tron │ │ │ ├── TronAdaptor.ts │ │ │ ├── TronContract.ts │ │ │ ├── TronWallet.ts │ │ │ └── index.ts │ │ ├── types.ts │ │ └── zksync │ │ │ ├── ZksyncAdaptor.ts │ │ │ └── index.ts │ ├── index.ts │ └── utils.ts │ ├── tests │ ├── MesonClient.spec.ts │ ├── SignedSwap.spec.ts │ ├── Swap.spec.ts │ ├── SwapSigner.spec.ts │ ├── SwapWithSigner.spec.ts │ └── shared │ │ └── index.ts │ └── tsconfig.json ├── scripts ├── config │ ├── MesonConfig.sol │ └── set-chain-config.js ├── copy-abis.js ├── data │ └── .gitignore ├── deploy-forward.js ├── deploy.js ├── estimate-gas.js ├── lib │ ├── CustomGasFeeProviderWrapper.js │ ├── deploy.js │ ├── getAdaptor.js │ ├── pool.js │ └── updatePresets.js ├── pool.js ├── uct.js └── upgrade.js ├── templates └── contract.hbs ├── test ├── MesonPools.spec.ts ├── MesonStates.spec.ts ├── MesonSwap.spec.ts └── shared │ ├── expect.ts │ ├── fixtures.ts │ ├── meson.ts │ └── wallet.ts ├── tsconfig.json └── yarn.lock /.env.local: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | LP_PRIVATE_KEY= 3 | PREMIUM_MANAGER= 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Main branch CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Use Node.js 16.x 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: 16.x 18 | - name: Install dependencies 19 | run: yarn install --immutable --immutable-cache --check-cache 20 | - name: Build packages 21 | run: yarn run build:packages 22 | - name: Check generated abis/types 23 | run: yarn build:types && git diff --exit-code 24 | - run: yarn test 25 | -------------------------------------------------------------------------------- /.github/workflows/test_dev.yaml: -------------------------------------------------------------------------------- 1 | name: Develop branch CI 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Use Node.js 16.x 15 | uses: actions/setup-node@v2 16 | with: 17 | node-version: 16.x 18 | - name: Install dependencies 19 | run: yarn install --immutable --immutable-cache --check-cache 20 | - name: Build packages 21 | run: yarn run build:packages 22 | - run: yarn test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | 4 | node_modules 5 | /.openzeppelin 6 | 7 | /cache 8 | /cache-zk 9 | /artifacts 10 | /artifacts-zk 11 | /deploys 12 | /docs 13 | 14 | .env 15 | 16 | /contracts/utils/MesonConfig.sol 17 | .npmrc 18 | .idea 19 | tsconfig.tsbuildinfo 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meson Smart Contract in Solidity 2 | 3 | This repository contains the Solidity implementation for the Meson protocol. 4 | See [Meson Docs](https://docs.meson.fi/protocol/background) for the design details of Meson. 5 | 6 | ## Local development 7 | 8 | This repo includes Meson's smart contracts in Solidity for deployment to multiple EVM-compatible blockchains. It also includes Meson JS SDKs in the `packages` folder for integrating into other Meson projects to interact with Meson contracts. 9 | 10 | Run `yarn` to install the project dependencies. Because the contracts of Meson need to be deployed on different chains, this project provides a script to switch current chain. Before the first time of compilation, run `yarn hardhat chain --testnet ropsten` to initialize contracts to Ropsten testnet. This command will copy the config file `MesonConfig.sol` to `contracts/utils` folder and set system invariants for the selected chain. 11 | 12 | You can also run `yarn hardhat chain --testnet [id]` or `yarn hardhat chain --mainnet [id]` to switch the project to other chains. See `packages/config/networks/testnets.json` and `packages/config/networks/mainnets.json` for available chains and their respective id's. 13 | 14 | ### Run tests 15 | 16 | Test cases are given in the `tests` folder. Before running the test, please run `yarn build:packages` to build Meson SDKs which are used by contract tests. Then, run `yarn test` to perform tests for Meson contracts. Notice that the test script will switch the current chain to Ethereum testnet because Meson contract tests should be run under this condition. 17 | 18 | The core Meson SDK `@mesonfi/sdk` also provides its own test cases. Go to folder `packages/sdk` and run `yarn test` to perform tests for it. SDK tests also requires Ethereum environment so make sure the current chain is switched to Ethereum. 19 | 20 | ### Generate docs 21 | 22 | Run `yarn docgen` to generate docs for Meson smart contracts, which are extracted from comments in the contract source codes. See generated docs under the `docs` folder. 23 | 24 | ### Estimate gas consumptions 25 | 26 | This project provides two scripts to estimate gas consumptions for crucial methos of Meson. Run `yarn hardhat estimate` to estimate the normal deployed Meson contracts, and run `yarn hardhat estimate --upgradable true` to estimate gas when Meson is deployed as an upgradable contract. 27 | 28 | ## Deployment 29 | 30 | Meson smart contracts can be deployed to multiple blockchains, either on their testsnets or mainnets. You can see many scripts of `yarn testnet:[id]` and `yarn deploy:[id]` with different values of id (which specifies the deploying chain) in `packages.json`. They are deployment scripts for Meson supported testnets and mainnets, respectively. These scripts will switch the current chain for the project, compile the smart contract, and run the actual script to deploy the upgradable version of Meson smart contract. 31 | 32 | The deployment process consists of the following steps 33 | 34 | 1. Copy the config file `MesonConfig.sol` to `contracts/utils` folder and set up system invariants based on the selected network, and whether it is a testnet or mainnet; 35 | 2. Build Meson smart contracts; 36 | 3. Read initialization parameters (supported tokens) from `@mesonfi/config`; 37 | 4. Deploy the upgradable version of Meson with signer given by environment variables; 38 | 5. Write the address of deployed contract back to `@mesonfi/config`. 39 | 40 | See the actual deploy scripts located in `scripts/deploy.js`. The connection config to different blockchains are given by the `@mesonfi/config` sdk in the `packages/config/networks/testnets.json` and `packages/config/networks/mainnets.json` files. 41 | 42 | ### Constructor parameters 43 | 44 | Meson use a whitelist for supported stablecoins (`address[] supportedTokens`), which are specified on first deployment as the constructor parameter. The deploy script will read tokens from `@mesonfi/config` and set them for each chain. 45 | 46 | ## Become a Liquidity Provider 47 | 48 | Any user who wishes to become Meson's liquidity provider needs to first register a pool index (by calling contract method `depositAndRegister`) and transfer supported stablecoins into the Meson contract. Related operations are provided in `scripts/pool.js`. Open the corresponding file and edit the parameters include token symbol, deposit amount, pool index, and run `yarn hardhat pool --testnet [id]` or `yarn hardhat pool --mainnet [id]` to execute the deposit operation. The `pool.js` files also provide withdraw scripts and please use them as needed. 49 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./contracts/Meson.sol", 3 | "deploy": "", 4 | "framework": "hardhat", 5 | "npmClient": "yarn", 6 | "compilers": { 7 | "solc": "0.8.16", 8 | "truffle": "", 9 | "evmVersion": "istanbul", 10 | "optimizer": { 11 | "enabled": true, 12 | "runs": 100 13 | } 14 | }, 15 | "linter": "solhint", 16 | "editor": { 17 | "fontFamily": "Hack", 18 | "fontSize": "13px", 19 | "ligatures": false 20 | } 21 | } -------------------------------------------------------------------------------- /contracts/Meson.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | import "./MesonManager.sol"; 5 | 6 | /// @title Meson 7 | /// @notice A plain non-upgradeable Meson 8 | contract Meson is MesonManager { 9 | constructor(address premiumManager) { 10 | _transferOwnership(_msgSender()); 11 | _transferPremiumManager(premiumManager); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /contracts/MesonManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | import "./Swap/MesonSwap.sol"; 5 | import "./Pools/MesonPools.sol"; 6 | 7 | /// @title MesonManager 8 | /// @notice The class to store data related to management permissions of Meson 9 | contract MesonManager is MesonSwap, MesonPools { 10 | /// @notice The admin of meson contract 11 | /// The owner has the permission to upgrade meson contract. In future versions, 12 | /// the management authority of meson contract will be decentralized. 13 | address internal _owner; 14 | 15 | /// @notice The manager to authorized fee waived swaps 16 | /// Only the premium manager can authorize the execution to release for fee waived swaps. 17 | /// This address is managed by Meson team. 18 | address internal _premiumManager; 19 | 20 | /// @dev This empty reserved space is put in place to allow future versions to 21 | /// add new variables without shifting down storage in the inheritance chain. 22 | /// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 23 | uint256[50] private __gap; 24 | 25 | event OwnerTransferred(address indexed prevOwner, address indexed newOwner); 26 | 27 | event PremiumManagerTransferred(address indexed prevPremiumManager, address indexed newPremiumManager); 28 | 29 | /// @notice The owner will also have the permission to add supported tokens 30 | function addSupportToken(address token, uint8 index) external onlyOwner { 31 | _addSupportToken(token, index); 32 | } 33 | 34 | /// @notice The owner will also have the permission to remove a supported tokens 35 | function removeSupportToken(uint8 index) external onlyOwner { 36 | _removeSupportToken(index); 37 | } 38 | 39 | /// @notice Add multiple tokens 40 | function addMultipleSupportedTokens(address[] memory tokens, uint8[] memory indexes) external onlyOwner { 41 | require(tokens.length == indexes.length, "Tokens and indexes should have the same length"); 42 | for (uint8 i = 0; i < tokens.length; i++) { 43 | _addSupportToken(tokens[i], indexes[i]); 44 | } 45 | } 46 | 47 | function transferOwnership(address newOwner) public onlyOwner { 48 | _transferOwnership(newOwner); 49 | } 50 | 51 | function transferPremiumManager(address newPremiumManager) public { 52 | require(_isPremiumManager(), "Caller is not the premium manager"); 53 | _transferPremiumManager(newPremiumManager); 54 | } 55 | 56 | function withdrawServiceFee(uint8 tokenIndex, uint256 amount, uint40 toPoolIndex) external onlyOwner { 57 | require(ownerOfPool[toPoolIndex] != address(0), "Pool index not registered"); 58 | _balanceOfPoolToken[_poolTokenIndexFrom(tokenIndex, 0)] -= amount; 59 | _balanceOfPoolToken[_poolTokenIndexFrom(tokenIndex, toPoolIndex)] += amount; 60 | } 61 | 62 | modifier onlyOwner() { 63 | require(_owner == _msgSender(), "Caller is not the owner"); 64 | _; 65 | } 66 | 67 | function _transferOwnership(address newOwner) internal { 68 | require(newOwner != address(0), "New owner cannot be zero address"); 69 | address prevOwner = _owner; 70 | _owner = newOwner; 71 | emit OwnerTransferred(prevOwner, newOwner); 72 | } 73 | 74 | function _isPremiumManager() internal view override(MesonSwap, MesonPools) returns (bool) { 75 | return _premiumManager == _msgSender(); 76 | } 77 | 78 | function _transferPremiumManager(address newPremiumManager) internal { 79 | require(newPremiumManager != address(0), "New premium manager be zero address"); 80 | address prevPremiumManager = _premiumManager; 81 | _premiumManager = newPremiumManager; 82 | emit PremiumManagerTransferred(prevPremiumManager, newPremiumManager); 83 | } 84 | 85 | function directSwap(uint256 encodedSwap, address recipient, bytes32 r, bytes32 yParityAndS) 86 | external matchProtocolVersion(encodedSwap) verifyEncodedSwap(encodedSwap) forTargetChain(encodedSwap) 87 | { 88 | require(recipient != address(0), "Recipient cannot be zero address"); 89 | 90 | bytes32 digest = keccak256(abi.encodePacked(encodedSwap, recipient)); 91 | _checkSignature(digest, r, yParityAndS, _premiumManager); 92 | 93 | uint256 amount = _amountFrom(encodedSwap); 94 | uint8 inTokenIndex = _inTokenIndexFrom(encodedSwap); 95 | uint256 releaseAmount = amount - _feeForLp(encodedSwap); 96 | 97 | _balanceOfPoolToken[_poolTokenIndexFrom(inTokenIndex, 1)] += amount; 98 | _balanceOfPoolToken[_poolTokenIndexForOutToken(encodedSwap, 1)] -= releaseAmount; 99 | 100 | _unsafeDepositToken(inTokenIndex, _msgSender(), amount); 101 | _release(encodedSwap, _outTokenIndexFrom(encodedSwap), _msgSender(), recipient, releaseAmount); 102 | 103 | emit SwapReleased(encodedSwap); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /contracts/Pools/IMesonPoolsEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | /// @title MesonPools Interface 5 | interface IMesonPoolsEvents { 6 | /// @notice Event when an LP pool is registered. 7 | /// Emit at the end of `depositAndRegister()` calls. 8 | /// @param poolIndex Pool index 9 | /// @param owner Pool owner 10 | event PoolRegistered(uint40 indexed poolIndex, address owner); 11 | 12 | /// @notice Event when fund was deposited to an LP pool. 13 | /// Emit at the end of `depositAndRegister()` and `deposit()` calls. 14 | /// @param poolTokenIndex Concatenation of pool index & token index 15 | /// @param amount The amount of tokens to be added to the pool 16 | event PoolDeposited(uint48 indexed poolTokenIndex, uint256 amount); 17 | 18 | /// @notice Event when fund was withdrawn from an LP pool. 19 | /// Emit at the end of `withdraw()` calls. 20 | /// @param poolTokenIndex Concatenation of pool index & token index 21 | /// @param amount The amount of tokens to be removed from the pool 22 | event PoolWithdrawn(uint48 indexed poolTokenIndex, uint256 amount); 23 | 24 | /// @notice Event when an authorized address was added for an LP pool. 25 | /// Emit at the end of `depositAndRegister()` calls. 26 | /// @param poolIndex Pool index 27 | /// @param addr Authorized address to be added 28 | event PoolAuthorizedAddrAdded(uint40 indexed poolIndex, address addr); 29 | 30 | /// @notice Event when an authorized address was removed for an LP pool. 31 | /// Emit at the end of `depositAndRegister()` calls. 32 | /// @param poolIndex Pool index 33 | /// @param addr Authorized address to be removed 34 | event PoolAuthorizedAddrRemoved(uint40 indexed poolIndex, address addr); 35 | 36 | /// @notice Event when the ownership of a pool was transferred. 37 | /// Emit at the end of `transferPoolOwner()` calls. 38 | /// @param poolIndex Pool index 39 | /// @param prevOwner Previous owner of the pool 40 | /// @param newOwner New owner of the pool 41 | event PoolOwnerTransferred(uint40 indexed poolIndex, address prevOwner, address newOwner); 42 | 43 | /// @notice Event when a swap was locked. 44 | /// Emit at the end of `lock()` and `lockSwap()` calls. 45 | /// @param encodedSwap Encoded swap 46 | event SwapLocked(uint256 indexed encodedSwap); 47 | 48 | /// @notice Event when a swap was unlocked. 49 | /// Emit at the end of `unlock()` calls. 50 | /// @param encodedSwap Encoded swap 51 | event SwapUnlocked(uint256 indexed encodedSwap); 52 | 53 | /// @notice Event when a swap was released. 54 | /// Emit at the end of `release()`, `directRelease()` calls. 55 | /// @param encodedSwap Encoded swap 56 | event SwapReleased(uint256 indexed encodedSwap); 57 | } 58 | -------------------------------------------------------------------------------- /contracts/Proxy2ToMeson.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 5 | import "./UpgradableMeson.sol"; 6 | 7 | // Some blockchains do not support deploying another contract in constructor 8 | contract Proxy2ToMeson is ERC1967Proxy { 9 | bytes4 private constant INITIALIZE_SELECTOR = bytes4(keccak256("initialize(address,address)")); 10 | 11 | constructor(address implAddress, address premiumManager) ERC1967Proxy(implAddress, _encodeData(msg.sender, premiumManager)) {} 12 | 13 | function _encodeData(address owner, address premiumManager) private pure returns (bytes memory) { 14 | return abi.encodeWithSelector(INITIALIZE_SELECTOR, owner, premiumManager); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/ProxyToMeson.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 5 | import "./UpgradableMeson.sol"; 6 | 7 | contract ProxyToMeson is ERC1967Proxy { 8 | bytes4 private constant INITIALIZE_SELECTOR = bytes4(keccak256("initialize(address,address)")); 9 | 10 | constructor(address premiumManager) ERC1967Proxy(_deployImpl(), _encodeData(msg.sender, premiumManager)) {} 11 | 12 | function _deployImpl() private returns (address) { 13 | UpgradableMeson _impl = new UpgradableMeson(); 14 | return address(_impl); 15 | } 16 | 17 | function _encodeData(address owner, address premiumManager) private pure returns (bytes memory) { 18 | return abi.encodeWithSelector(INITIALIZE_SELECTOR, owner, premiumManager); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/Swap/IMesonSwapEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | /// @title MesonSwapEvents Interface 5 | interface IMesonSwapEvents { 6 | /// @notice Event when a swap request was posted. 7 | /// Emit at the end of `postSwap()`, `postSwapFromInitiator()` and `postSwapFromContract()` calls. 8 | /// @param encodedSwap Encoded swap 9 | event SwapPosted(uint256 indexed encodedSwap); 10 | 11 | /// @notice Event when a swap request was bonded. 12 | /// Emit at the end of `bondSwap()` calls. 13 | /// @param encodedSwap Encoded swap 14 | event SwapBonded(uint256 indexed encodedSwap); 15 | 16 | /// @notice Event when a swap request was cancelled. 17 | /// Emit at the end of `cancelSwap()` calls. 18 | /// @param encodedSwap Encoded swap 19 | event SwapCancelled(uint256 indexed encodedSwap); 20 | 21 | /// @notice Event when a swap request was executed. 22 | /// Emit at the end of `executeSwap()`, `directExecuteSwap()` and `simpleExecuteSwap()` calls. 23 | /// @param encodedSwap Encoded swap 24 | event SwapExecuted(uint256 indexed encodedSwap); 25 | } 26 | -------------------------------------------------------------------------------- /contracts/Token/PoDUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 6 | 7 | /// @title PoDUpgradeable 8 | /// @notice The contract of POD, a token minted by Meson as the Proof of Deposit 9 | contract PoDUpgradeable is UUPSUpgradeable, ERC20Upgradeable { 10 | address private _owner; 11 | address private _minter; 12 | address private _mesonContract; 13 | 14 | mapping(address => uint256) private _lockedBalances; 15 | mapping(address => uint256) private _lockedSince; 16 | 17 | uint256 private _rewardRatePerSecTimes1e12; 18 | mapping(address => uint256) private _prevRewards; 19 | 20 | uint256 constant NO_REWARD_LOCK_PERIOD = 3 days; 21 | uint256 constant LESS_REWARD_LOCK_PERIOD = 7 days; 22 | 23 | function initialize(address minter, address mesonContract) public initializer { 24 | __ERC20_init("Proof of Deposit (meson.fi)", "PoD"); 25 | _owner = _msgSender(); 26 | _minter = minter; 27 | require(mesonContract != address(0), "Address of meson contract cannot be zero"); 28 | _mesonContract = mesonContract; 29 | } 30 | 31 | function decimals() public pure override returns (uint8) { 32 | return 6; 33 | } 34 | 35 | function mint(address account, uint256 amount) external onlyMinter { 36 | _mint(account, amount); 37 | } 38 | 39 | function _authorizeUpgrade(address newImplementation) internal override { 40 | require(_msgSender() == _owner, "Unauthorized"); 41 | } 42 | 43 | /// @notice Override the default ERC20 allowance method 44 | /// mesonContract will have max allowance so users don't need to execute approve 45 | function allowance(address owner, address spender) public view override returns (uint256) { 46 | if (spender == _mesonContract) { 47 | uint256 x = 0; 48 | unchecked { x--; } 49 | return x; 50 | } 51 | return ERC20Upgradeable.allowance(owner, spender); 52 | } 53 | 54 | function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { 55 | address msgSender = _msgSender(); 56 | if (msgSender == _mesonContract && ERC20Upgradeable.allowance(sender, msgSender) < amount) { 57 | uint256 x = 0; 58 | unchecked { x--; } 59 | _approve(sender, msgSender, x); 60 | } 61 | return ERC20Upgradeable.transferFrom(sender, recipient, amount); 62 | } 63 | 64 | function setRewardFactor(uint256 annualPercentageRateTimes1e4) external onlyOwner { 65 | _rewardRatePerSecTimes1e12 = annualPercentageRateTimes1e4 * 1e8 / 365 days; 66 | } 67 | 68 | function getCurrentAPR() public view returns (uint256) { 69 | return _rewardRatePerSecTimes1e12 * 365 days / 1e8; 70 | } 71 | 72 | function getLockedBalance(address account) public view returns (uint256) { 73 | return _lockedBalances[account]; 74 | } 75 | 76 | function _getRewards(address account) internal view returns (uint256 total, uint256 pending) { 77 | uint256 lockedBalance = _lockedBalances[account]; 78 | if (lockedBalance == 0) { 79 | return (_prevRewards[account], 0); 80 | } 81 | 82 | uint256 sinceLastLock = block.timestamp - _lockedSince[account]; 83 | uint256 rewardsForCurrentLock = sinceLastLock * lockedBalance * _rewardRatePerSecTimes1e12 / 1e12; 84 | if (sinceLastLock > LESS_REWARD_LOCK_PERIOD) { 85 | return (_prevRewards[account] + rewardsForCurrentLock, 0); 86 | } else if (sinceLastLock > NO_REWARD_LOCK_PERIOD) { 87 | return (_prevRewards[account] + rewardsForCurrentLock, 3 days * lockedBalance * _rewardRatePerSecTimes1e12 / 1e12); 88 | } else { 89 | return (_prevRewards[account] + rewardsForCurrentLock, rewardsForCurrentLock); 90 | } 91 | } 92 | 93 | function getTotalRewards(address account) public view returns (uint256) { 94 | (uint256 total, ) = _getRewards(account); 95 | return total; 96 | } 97 | 98 | function getClaimableRewards(address account) public view returns (uint256) { 99 | (uint256 total, uint256 pending) = _getRewards(account); 100 | return total - pending; 101 | } 102 | 103 | function lockPoD(uint256 amount) external { 104 | require(amount > 0, "amount must be greater than 0"); 105 | address account = _msgSender(); 106 | 107 | (uint256 total, uint256 pending) = _getRewards(account); 108 | _prevRewards[account] = total - pending; 109 | 110 | uint256 newLockedBalance = _lockedBalances[account] + amount; 111 | _lockedSince[account] = block.timestamp - pending * 1e12 / _rewardRatePerSecTimes1e12 / newLockedBalance; 112 | _lockedBalances[account] = newLockedBalance; 113 | 114 | _transfer(account, address(this), amount); 115 | } 116 | 117 | function unlockPoD(uint256 amount) external { 118 | require(amount > 0, "amount must be greater than 0"); 119 | address account = _msgSender(); 120 | 121 | _prevRewards[account] = getClaimableRewards(account); 122 | 123 | uint256 newLockedBalance = _lockedBalances[account] - amount; // will throw error if overdrawn 124 | _lockedSince[account] = newLockedBalance > 0 ? block.timestamp : 0; 125 | _lockedBalances[account] = newLockedBalance; 126 | 127 | _transfer(address(this), account, amount); 128 | } 129 | 130 | function withdrawRewards(uint256 amount) external { 131 | require(amount > 0, "amount must be greater than 0"); 132 | address account = _msgSender(); 133 | 134 | uint256 claimableRewards = getClaimableRewards(account); 135 | require(amount <= claimableRewards, "Insufficient claimable rewards"); 136 | 137 | _prevRewards[account] = claimableRewards - amount; 138 | if (_lockedSince[account] > 0) { 139 | _lockedSince[account] = block.timestamp; 140 | } 141 | 142 | _mint(account, amount); 143 | } 144 | 145 | modifier onlyOwner() { 146 | require(_owner == _msgSender(), "Caller is not the owner"); 147 | _; 148 | } 149 | 150 | modifier onlyMinter() { 151 | require(_minter == _msgSender(), "Caller is not the owner"); 152 | _; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /contracts/Token/UCTUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 6 | 7 | /// @title UCTUpgradeable 8 | /// @notice The contract of UCT, a token minted by Meson for promotional events 9 | /// UCT (USD Coupon Token) is issued by Meson team to mark participants for 10 | /// community events (such as airdrops, cashbacks, etc). UCT is not an asset 11 | /// and it has no value. UCT obtained directly from Meson during the event period 12 | /// can be redeemed for the actual USDT/USDC rewards on https://meson.fi/ at 13 | /// a ratio of 100:1. UCT has no usage other than redemption for USDT/USDC, 14 | /// and all UCTs will be destroyed at the end of the event. 15 | contract UCTUpgradeable is UUPSUpgradeable, ERC20Upgradeable { 16 | address private _owner; 17 | address private _minter; 18 | address private _mesonContract; 19 | 20 | function initialize(address minter, address mesonContract) public initializer { 21 | __ERC20_init("USD Coupon Token (https://meson.fi)", "UCT"); 22 | _owner = _msgSender(); 23 | _minter = minter; 24 | require(mesonContract != address(0), "Address of meson contract cannot be zero"); 25 | _mesonContract = mesonContract; 26 | } 27 | 28 | function decimals() public pure override returns (uint8) { 29 | return 4; 30 | } 31 | 32 | function batchMint(address[] memory targets, uint256 amount) external onlyMinter { 33 | require(targets.length > 0, "Target array is empty"); 34 | require(targets.length < 2048, "Target array is too large"); 35 | for (uint i = 0; i < targets.length; i++) { 36 | _mint(targets[i], amount); 37 | } 38 | } 39 | 40 | function batchMint2(address[] memory targets, uint256[] memory amounts) external onlyMinter { 41 | require(targets.length > 0, "Target array is empty"); 42 | require(targets.length < 2048, "Target array is too large"); 43 | require(targets.length == amounts.length, "Targets and amounts should have the same length"); 44 | for (uint i = 0; i < targets.length; i++) { 45 | _mint(targets[i], amounts[i]); 46 | } 47 | } 48 | 49 | function _authorizeUpgrade(address newImplementation) internal override { 50 | require(_msgSender() == _owner, "Unauthorized"); 51 | } 52 | 53 | /// @notice Override the default ERC20 allowance method 54 | /// mesonContract will have max allowance so users don't need to execute approve 55 | function allowance(address owner, address spender) public view override returns (uint256) { 56 | if (spender == _mesonContract) { 57 | uint256 x = 0; 58 | unchecked { x--; } 59 | return x; 60 | } 61 | return ERC20Upgradeable.allowance(owner, spender); 62 | } 63 | 64 | function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { 65 | address msgSender = _msgSender(); 66 | if (msgSender == _mesonContract && ERC20Upgradeable.allowance(sender, msgSender) < amount) { 67 | uint256 x = 0; 68 | unchecked { x--; } 69 | _approve(sender, msgSender, x); 70 | } 71 | return ERC20Upgradeable.transferFrom(sender, recipient, amount); 72 | } 73 | 74 | modifier onlyOwner() { 75 | require(_owner == _msgSender(), "Caller is not the owner"); 76 | _; 77 | } 78 | 79 | modifier onlyMinter() { 80 | require(_minter == _msgSender(), "Caller is not the owner"); 81 | _; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /contracts/UpgradableMeson.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; 5 | import "./MesonManager.sol"; 6 | 7 | contract UpgradableMeson is UUPSUpgradeable, MesonManager { 8 | function initialize(address owner, address premiumManager) external initializer { 9 | _transferOwnership(owner); 10 | _transferPremiumManager(premiumManager); 11 | } 12 | 13 | function _authorizeUpgrade(address) internal override onlyOwner {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IDepositWithBeneficiary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | /// @title Interface for depositWithBeneficiary 5 | interface IDepositWithBeneficiary { 6 | /// @notice Make a token transfer that the *signer* is paying tokens but benefits are given to the *beneficiary* 7 | /// @param token The contract address of the transferring token 8 | /// @param amount The amount of the transfer 9 | /// @param beneficiary The address that will receive benefits of this transfer 10 | /// @param data Extra data passed to the contract 11 | /// @return Returns true for a successful transfer. 12 | function depositWithBeneficiary(address token, uint256 amount, address beneficiary, uint64 data) 13 | payable external returns (bool); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20Minimal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Minimal ERC20 interface for Uniswap 5 | /// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3 6 | interface IERC20Minimal { 7 | /// @notice Returns the balance of a token 8 | /// @param account The account for which to look up the number of tokens it has, i.e. its balance 9 | /// @return The number of tokens held by the account 10 | function balanceOf(address account) external view returns (uint256); 11 | 12 | /// @notice Transfers the amount of token from the `msg.sender` to the recipient 13 | /// @param recipient The account that will receive the amount transferred 14 | /// @param amount The number of tokens to send from the sender to the recipient 15 | /// @return Returns true for a successful transfer, false for an unsuccessful transfer 16 | function transfer(address recipient, uint256 amount) external returns (bool); 17 | 18 | /// @notice Returns the current allowance given to a spender by an owner 19 | /// @param owner The account of the token owner 20 | /// @param spender The account of the token spender 21 | /// @return The current allowance granted by `owner` to `spender` 22 | function allowance(address owner, address spender) external view returns (uint256); 23 | 24 | /// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount` 25 | /// @param spender The account which will be allowed to spend a given amount of the owners tokens 26 | /// @param amount The amount of tokens allowed to be used by `spender` 27 | /// @return Returns true for a successful approval, false for unsuccessful 28 | function approve(address spender, uint256 amount) external returns (bool); 29 | 30 | /// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender` 31 | /// @param sender The account from which the transfer will be initiated 32 | /// @param recipient The recipient of the transfer 33 | /// @param amount The amount of the transfer 34 | /// @return Returns true for a successful transfer, false for unsuccessful 35 | function transferFrom( 36 | address sender, 37 | address recipient, 38 | uint256 amount 39 | ) external returns (bool); 40 | 41 | /// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`. 42 | /// @param from The account from which the tokens were sent, i.e. the balance decreased 43 | /// @param to The account to which the tokens were sent, i.e. the balance increased 44 | /// @param value The amount of tokens that were transferred 45 | event Transfer(address indexed from, address indexed to, uint256 value); 46 | 47 | /// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes. 48 | /// @param owner The account that approved spending of its tokens 49 | /// @param spender The account for which the spending allowance was modified 50 | /// @param value The new allowance from the owner to the spender 51 | event Approval(address indexed owner, address indexed spender, uint256 value); 52 | } -------------------------------------------------------------------------------- /contracts/test/ForwardTokenContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../interfaces/IERC20Minimal.sol"; 5 | import "../interfaces/IDepositWithBeneficiary.sol"; 6 | 7 | /// @notice A sample of 3rd-party dapp that interacts with meson 8 | /// With `depositWithBeneficiary`, the meson contract will be able 9 | /// to deposit cross-chain'ed stablecoins to the 3rd-party dapp contract 10 | /// on behalf of the user. The user will receive the benefits corresponding 11 | /// to this deposit. 12 | contract ForwardTokenContract is IDepositWithBeneficiary { 13 | function depositWithBeneficiary( 14 | address token, 15 | uint256 amount, 16 | address beneficiary, 17 | uint64 data 18 | ) payable external override returns (bool) { 19 | if (token == address(0)) { 20 | // ETH 21 | // No need to take ETH since it is transferred in msg.value 22 | } else { 23 | // Stablecoins 24 | // Required. Take cross-chain'ed token to dapp's contract 25 | IERC20Minimal(token).transferFrom(msg.sender, address(this), amount); 26 | } 27 | 28 | // The dapp can do it's own logic with depositing tokens 29 | // e.g. exchange for other tokens, mint NFTs, etc. 30 | // Could use data to determine specific operations. 31 | 32 | 33 | // Send benefits to the user. Here as an example we just transfer 34 | // deposited tokens to the user. 35 | if (token == address(0)) { 36 | // ETH 37 | (bool success, ) = beneficiary.call{value: amount}(""); 38 | require(success, "Transfer failed"); 39 | } else { 40 | // Stablecoins 41 | IERC20Minimal(token).transfer(beneficiary, amount); 42 | } 43 | 44 | return true; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/test/MesonPoolsTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity =0.8.16; 3 | 4 | import "../Pools/MesonPools.sol"; 5 | 6 | contract MesonPoolsTest is MesonPools { 7 | address internal _premiumManager; 8 | 9 | constructor(address token, address premiumManager) { 10 | _addSupportToken(token, 1); 11 | _premiumManager = premiumManager; 12 | } 13 | 14 | function _isPremiumManager() internal view override returns (bool) { 15 | return _premiumManager == _msgSender(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/test/MesonStatesTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity =0.8.16; 3 | 4 | import "../utils/MesonStates.sol"; 5 | 6 | contract MesonStatesTest is MesonStates { 7 | function addSupportToken(address token, uint8 index) external { 8 | _addSupportToken(token, index); 9 | } 10 | 11 | function encodeSwap( 12 | uint8 version, 13 | uint40 amount, 14 | uint80 salt, 15 | uint40 fee, 16 | uint40 expireTs, 17 | bytes2 outChain, 18 | uint8 outToken, 19 | bytes2 inChain, 20 | uint8 inToken 21 | ) external pure returns (bytes memory) { 22 | return 23 | abi.encodePacked( 24 | version, 25 | amount, 26 | salt, 27 | fee, 28 | expireTs, 29 | outChain, 30 | outToken, 31 | inChain, 32 | inToken 33 | ); 34 | } 35 | 36 | function decodeSwap(uint256 encodedSwap, uint40 poolIndex) external pure returns ( 37 | uint8 version, 38 | uint256 amount, 39 | uint256 feeForLp, 40 | uint256 serviceFee, 41 | uint80 salt, 42 | uint256 expireTs, 43 | bytes2 inChain, 44 | uint8 inTokenIndex, 45 | bytes2 outChain, 46 | uint8 outTokenIndex, 47 | bytes6 poolTokenIndexForOutToken 48 | ) { 49 | version = _versionFrom(encodedSwap); 50 | amount = _amountFrom(encodedSwap); 51 | feeForLp = _feeForLp(encodedSwap); 52 | serviceFee = _serviceFee(encodedSwap); 53 | salt = _saltFrom(encodedSwap); 54 | expireTs = _expireTsFrom(encodedSwap); 55 | inChain = bytes2(_inChainFrom(encodedSwap)); 56 | inTokenIndex = _inTokenIndexFrom(encodedSwap); 57 | outChain = bytes2(_outChainFrom(encodedSwap)); 58 | outTokenIndex = _outTokenIndexFrom(encodedSwap); 59 | poolTokenIndexForOutToken = bytes6(_poolTokenIndexForOutToken(encodedSwap, poolIndex)); 60 | } 61 | 62 | function decodePostedSwap(uint200 postedSwap) external pure returns ( 63 | address initiator, 64 | uint40 poolIndex 65 | ) { 66 | initiator = _initiatorFromPosted(postedSwap); 67 | poolIndex = _poolIndexFromPosted(postedSwap); 68 | } 69 | 70 | function lockedSwapFrom(uint256 until, uint40 poolIndex) external pure returns (uint80) { 71 | return _lockedSwapFrom(until, poolIndex); 72 | } 73 | 74 | function decodeLockedSwap(uint80 lockedSwap) external pure returns (uint40 poolIndex, uint256 until) { 75 | poolIndex = _poolIndexFromLocked(lockedSwap); 76 | until = _untilFromLocked(lockedSwap); 77 | } 78 | 79 | function poolTokenIndexFrom(uint8 tokenIndex, uint40 poolIndex) external pure returns (bytes6) { 80 | return bytes6(_poolTokenIndexFrom(tokenIndex, poolIndex)); 81 | } 82 | 83 | function decodePoolTokenIndex(uint48 poolTokenIndex) external pure returns ( 84 | uint8 tokenIndex, 85 | uint40 poolIndex 86 | ) { 87 | tokenIndex = _tokenIndexFrom(poolTokenIndex); 88 | poolIndex = _poolIndexFrom(poolTokenIndex); 89 | } 90 | 91 | function checkRequestSignature( 92 | uint256 encodedSwap, 93 | bytes32 r, 94 | bytes32 yParityAndS, 95 | address signer 96 | ) external pure { 97 | _checkRequestSignature(encodedSwap, r, yParityAndS, signer); 98 | } 99 | 100 | function checkReleaseSignature( 101 | uint256 encodedSwap, 102 | address recipient, 103 | bytes32 r, 104 | bytes32 yParityAndS, 105 | address signer 106 | ) external pure { 107 | _checkReleaseSignature(encodedSwap, recipient, r, yParityAndS, signer); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /contracts/test/MesonSwapTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity =0.8.16; 3 | 4 | import "../Swap/MesonSwap.sol"; 5 | 6 | contract MesonSwapTest is MesonSwap { 7 | constructor(address token) { 8 | _addSupportToken(token, 1); 9 | } 10 | 11 | function register(uint40 poolIndex) external { 12 | address poolOwner = _msgSender(); 13 | require(poolIndex != 0, "Cannot use index 0"); 14 | require(ownerOfPool[poolIndex] == address(0), "Pool index already registered"); 15 | require(poolOfAuthorizedAddr[poolOwner] == 0, "Signer address already registered"); 16 | ownerOfPool[poolIndex] = poolOwner; 17 | poolOfAuthorizedAddr[poolOwner] = poolIndex; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/test/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockToken is ERC20 { 7 | uint8 private _decimals; 8 | 9 | constructor( 10 | string memory name, 11 | string memory symbol, 12 | uint256 initialSupply, 13 | uint8 decimals_ 14 | ) ERC20(name, symbol) { 15 | _decimals = decimals_; 16 | _mint(msg.sender, initialSupply); 17 | } 18 | 19 | function decimals() public view override returns (uint8) { 20 | return _decimals; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/utils/MesonTokens.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | /// @title MesonTokens 5 | /// @notice The class that stores the information of Meson's supported tokens 6 | contract MesonTokens { 7 | /// @notice The whitelist of supported tokens in Meson 8 | /// Meson use a whitelist for supported stablecoins, which is specified on first deployment 9 | /// or added through `_addSupportToken` Only modify this mapping through `_addSupportToken`. 10 | /// key: `tokenIndex` in range of 1-255 11 | /// 0: unsupported 12 | /// 1-32: stablecoins with decimals 6 13 | /// 1, 9: USDC, USDC.e 14 | /// 2, 10: USDT, USDT.e 15 | /// 3: BUSD 16 | /// 6: CUSD (Viction/Tomo) 17 | /// 17: PoD USDC 18 | /// 18: PoD USDT 19 | /// 19: PoD BUSD 20 | /// 32: PoD 21 | /// 33-48: stablecoins with decimals 18 22 | /// 33: USDC 23 | /// 34: USDT 24 | /// 35: BUSD 25 | /// 36: (reserved for DAI) 26 | /// 37: cUSD (Celo) 27 | /// 39: USDB (Blast) 28 | /// 40: FDUSD 29 | /// 41: BBUSD 30 | /// 48: USD1 31 | /// 49-64: stablecoins as core (decimals 18) 32 | /// 49: USDC 33 | /// 52: DAI 34 | /// 65-112: 3rd party tokens (decimals 18) 35 | /// 65: iUSD (iZUMi Bond USD) 36 | /// 67: M-BTC (Merlin BTC) 37 | /// 69: MERL 38 | /// 71: STONE 39 | /// 73: SolvBTC.m 40 | /// 75: SolvBTC 41 | /// 77: SolvBTC.a 42 | /// 79: uBTC 43 | /// 81: xSolvBTC 44 | /// 83: SolvBTC.ENA 45 | /// 85: SolvBTC.JUP 46 | /// 87: PUMP 47 | /// 89: B2 48 | /// 113-123: 3rd party tokens (decimals 8) 49 | /// 113: pumpBTC 50 | /// 114: uniBTC 51 | /// 115: cbBTC 52 | /// 124-128: 3rd party tokens (decimals 9) 53 | /// 124: DUCK 54 | /// 129-190: (Unspecified) 55 | /// 191: No-swap core 56 | /// 192-235: (Unspecified) 57 | /// 236-239: MATIC & MATIC equivalent 58 | /// 236: PoD MATIC 59 | /// 238: ERC20 MATIC 60 | /// 239: MATIC as core 61 | /// 240-243: BTC & BTC equivalent 62 | /// 240: PoD BTC 63 | /// 241: ERC20 BTC (decimals 18) 64 | /// 242: ERC20 BTC (decimals 8 except BNB Smart Chain & BounceBit) 65 | /// 243: BTC as core 66 | /// 244-247: SOL & SOL equivalent 67 | /// 244: PoD SOL 68 | /// 246: ERC20 SOL 69 | /// 247: SOL as core 70 | /// 248-251: BNB & BNB equivalent 71 | /// 248: PoD BNB 72 | /// 250: (reserved for ERC20 BNB) 73 | /// 251: BNB as core 74 | /// 252-255: ETH & ETH equivalent 75 | /// 252: PoD ETH 76 | /// 254: Wrapped ETH 77 | /// 255: ETH as core 78 | /// value: the supported token's contract address 79 | mapping(uint8 => address) public tokenForIndex; 80 | 81 | 82 | /// @notice The mapping to get `tokenIndex` from a supported token's address 83 | /// Only modify this mapping through `_addSupportToken`. 84 | /// key: the supported token's contract address 85 | /// value: `tokenIndex` in range of 1-255 86 | mapping(address => uint8) public indexOfToken; 87 | 88 | /// @dev This empty reserved space is put in place to allow future versions to 89 | /// add new variables without shifting down storage in the inheritance chain. 90 | /// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 91 | uint256[50] private __gap; 92 | 93 | function _isCoreToken(uint8 tokenIndex) internal returns (bool) { 94 | return (tokenIndex >= 49 && tokenIndex <= 64) || ((tokenIndex > 190) && ((tokenIndex % 4) == 3)); 95 | } 96 | 97 | /// @notice Return all supported token addresses in an array ordered by `tokenIndex` 98 | /// This method will only return tokens with consecutive token indexes. 99 | function getSupportedTokens() external view returns (address[] memory tokens, uint8[] memory indexes) { 100 | uint8 i; 101 | uint8 num; 102 | for (i = 0; i < 255; i++) { 103 | if (tokenForIndex[i+1] != address(0)) { 104 | num++; 105 | } 106 | } 107 | tokens = new address[](num); 108 | indexes = new uint8[](num); 109 | uint8 j = 0; 110 | for (i = 0; i < 255; i++) { 111 | if (tokenForIndex[i+1] != address(0)) { 112 | tokens[j] = tokenForIndex[i+1]; 113 | indexes[j] = i+1; 114 | j++; 115 | } 116 | } 117 | } 118 | 119 | function _addSupportToken(address token, uint8 index) internal { 120 | require(index != 0, "Cannot use 0 as token index"); 121 | require(token != address(0), "Cannot use zero address"); 122 | require(indexOfToken[token] == 0, "Token has been added before"); 123 | require(tokenForIndex[index] == address(0), "Index has been used"); 124 | if (_isCoreToken(index)) { 125 | require(token == address(0x1), "Core token requires adddress(0x1)"); 126 | } 127 | indexOfToken[token] = index; 128 | tokenForIndex[index] = token; 129 | } 130 | 131 | function _removeSupportToken(uint8 index) internal { 132 | require(index != 0, "Cannot use 0 as token index"); 133 | address token = tokenForIndex[index]; 134 | require(token != address(0), "Token for the index does not exist"); 135 | delete indexOfToken[token]; 136 | delete tokenForIndex[index]; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0xdc28e29764ca79368f426f106458f2c427392e32d088b04f4c372ab51b25d2cf" 4 | } 5 | } -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import '@typechain/hardhat' 2 | import '@nomiclabs/hardhat-waffle' 3 | import '@nomiclabs/hardhat-ethers' 4 | import '@nomicfoundation/hardhat-verify' 5 | import '@openzeppelin/hardhat-upgrades' 6 | import '@matterlabs/hardhat-zksync-deploy' 7 | import '@matterlabs/hardhat-zksync-solc' 8 | import dotenv from 'dotenv' 9 | import { HardhatPluginError } from 'hardhat/plugins' 10 | 11 | import { task } from 'hardhat/config' 12 | import config from './config.json' 13 | 14 | import mainnets from './packages/config/networks/mainnets.json'; 15 | import testnets from './packages/config/networks/testnets.json'; 16 | 17 | dotenv.config() 18 | 19 | const { 20 | INFURA_API_KEY = '' 21 | } = process.env 22 | 23 | const mainnetConnections = Object.fromEntries( 24 | mainnets 25 | .filter(item => item.url) 26 | .map(item => [item.id, { 27 | url: item.url?.replace('${INFURA_API_KEY}', INFURA_API_KEY), 28 | zksync: item.id.startsWith('zksync') || item.id.startsWith('zklink'), 29 | ethNetwork: 'eth', 30 | timeout: 0, 31 | gas: 'auto', 32 | gasMultiplier: 1.1, 33 | }]) 34 | ) 35 | const testnetConnections = Object.fromEntries( 36 | testnets 37 | .filter(item => item.url) 38 | .map(item => [item.id, { 39 | url: item.url?.replace('${INFURA_API_KEY}', INFURA_API_KEY), 40 | zksync: item.id.startsWith('zksync'), 41 | ethNetwork: 'goerli', 42 | timeout: 0, 43 | }]) 44 | ) 45 | 46 | task('chain', 'Switch chain-specific config for Meson') 47 | .addParam('mainnet', 'Mainnet network id', '') 48 | .addParam('testnet', 'Testnet network id', 'local') 49 | .setAction(async taskArgs => { 50 | const networkId = taskArgs.mainnet || taskArgs.testnet 51 | const testnetMode = !taskArgs.mainnet 52 | const setChainConfig = require('./scripts/config/set-chain-config') 53 | await setChainConfig(networkId, testnetMode) 54 | }) 55 | 56 | task('estimate', 'Estimate gas usage for Meson') 57 | .addParam('upgradable', 'If using MesonUpgradable', 'false') 58 | .setAction(async taskArgs => { 59 | if (!['true', 'false'].includes(taskArgs.upgradable)) { 60 | throw new HardhatPluginError(`The '--upgradable' parameter can only be 'true' or 'false'`) 61 | } 62 | const estimateGas = require('./scripts/estimate-gas') 63 | await estimateGas(taskArgs.upgradable === 'true') 64 | }) 65 | 66 | async function _switchNetwork(taskArgs: any) { 67 | const networkId = taskArgs.mainnet || taskArgs.testnet 68 | if (networkId === 'local') { 69 | throw new HardhatPluginError(`Network id cannot be 'local'`) 70 | } 71 | const testnetMode = !taskArgs.mainnet 72 | const setChainConfig = require('./scripts/config/set-chain-config') 73 | const network = await setChainConfig(networkId, testnetMode) 74 | return { network, testnetMode } 75 | } 76 | 77 | task('deploy', 'Deploy Meson contract') 78 | .addParam('mainnet', 'Mainnet network id', '') 79 | .addParam('testnet', 'Testnet network id', '') 80 | .addParam('upgradable', 'If using MesonUpgradable', 'false') 81 | .setAction(async taskArgs => { 82 | if (!['true', 'false'].includes(taskArgs.upgradable)) { 83 | throw new HardhatPluginError(`The '--upgradable' parameter can only be 'true' or 'false'`) 84 | } 85 | 86 | const { network, testnetMode } = await _switchNetwork(taskArgs) 87 | const deploy = require('./scripts/deploy') 88 | await deploy(network, taskArgs.upgradable === 'true', testnetMode) 89 | }) 90 | 91 | task('upgrade', 'Upgrade Meson contract') 92 | .addParam('mainnet', 'Mainnet network id', '') 93 | .addParam('testnet', 'Testnet network id', '') 94 | .setAction(async (taskArgs, hre) => { 95 | const { network } = await _switchNetwork(taskArgs) 96 | const upgrade = require('./scripts/upgrade') 97 | await upgrade(network) 98 | }) 99 | 100 | task('pool', 'Perform pool operation') 101 | .addParam('mainnet', 'Mainnet network id', '') 102 | .addParam('testnet', 'Testnet network id', '') 103 | .setAction(async taskArgs => { 104 | const { network } = await _switchNetwork(taskArgs) 105 | const pool = require('./scripts/pool') 106 | await pool(network) 107 | }) 108 | 109 | task('uct', 'Run UCT script') 110 | .addParam('mainnet', 'Mainnet network id', '') 111 | .addParam('testnet', 'Testnet network id', '') 112 | .setAction(async (taskArgs, hre) => { 113 | const { network } = await _switchNetwork(taskArgs) 114 | const uct = require('./scripts/uct') 115 | await uct(network) 116 | }) 117 | 118 | task('deploy-forward', 'Deploy ForwardTokenContract') 119 | .addParam('mainnet', 'Mainnet network id', '') 120 | .addParam('testnet', 'Testnet network id', '') 121 | .setAction(async taskArgs => { 122 | const { network } = await _switchNetwork(taskArgs) 123 | const deployForward = require('./scripts/deploy-forward') 124 | await deployForward(network) 125 | }) 126 | 127 | export default { 128 | solidity: { 129 | version: config.compilers.solc, 130 | settings: { 131 | optimizer: config.compilers.optimizer, 132 | evmVersion: config.compilers.evmVersion, 133 | viaIR: true, 134 | metadata: { 135 | // do not include the metadata hash, since this is machine dependent 136 | // and we want all generated code to be deterministic 137 | // https://docs.soliditylang.org/en/v0.7.6/metadata.html 138 | bytecodeHash: 'none', 139 | }, 140 | }, 141 | }, 142 | zksolc: { 143 | version: '1.3.1', 144 | compilerSource: 'binary', 145 | settings: {}, 146 | }, 147 | defaultNetwork: 'hardhat', 148 | networks: { 149 | obsidians: { 150 | url: 'http://localhost:62743', 151 | accounts: 'remote', 152 | timeout: 0, 153 | }, 154 | ...mainnetConnections, 155 | ...testnetConnections, 156 | }, 157 | typechain: { 158 | outDir: 'packages/config/types', 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /packages/config/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-transform-modules-commonjs", 4 | [ 5 | "@babel/plugin-syntax-import-assertions", 6 | { 7 | "deprecatedImportAssert": true 8 | } 9 | ], 10 | [ 11 | "@babel/plugin-syntax-import-attributes", 12 | { 13 | "deprecatedImportAssert": true 14 | } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/config/.gitignore: -------------------------------------------------------------------------------- 1 | abis/*.json 2 | /types 3 | networks/index.js 4 | -------------------------------------------------------------------------------- /packages/config/abis/ERC20Abi.d.ts: -------------------------------------------------------------------------------- 1 | import {ContractData} from './index'; 2 | 3 | declare const ERC20: ContractData; 4 | 5 | export default ERC20; 6 | -------------------------------------------------------------------------------- /packages/config/abis/MesonAbi.d.ts: -------------------------------------------------------------------------------- 1 | import {ContractData} from './index'; 2 | 3 | declare const Meson: ContractData; 4 | 5 | export default Meson; 6 | -------------------------------------------------------------------------------- /packages/config/abis/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface JsonFragmentType { 2 | readonly name?: string; 3 | readonly indexed?: boolean; 4 | readonly type?: string; 5 | readonly internalType?: any; 6 | readonly components?: ReadonlyArray; 7 | } 8 | 9 | export interface JsonFragment { 10 | readonly name?: string; 11 | readonly type?: string; 12 | 13 | readonly anonymous?: boolean; 14 | 15 | readonly payable?: boolean; 16 | readonly constant?: boolean; 17 | readonly stateMutability?: string; 18 | 19 | readonly inputs?: ReadonlyArray; 20 | readonly outputs?: ReadonlyArray; 21 | 22 | readonly gas?: string; 23 | } 24 | 25 | export type ContractInterface = Array; 26 | 27 | export type ContractData = { 28 | contractName: string; 29 | abi: ContractInterface; 30 | } 31 | 32 | export declare const MesonAbi: ContractData; 33 | 34 | export declare const ERC20Abi: ContractData; 35 | -------------------------------------------------------------------------------- /packages/config/abis/index.js: -------------------------------------------------------------------------------- 1 | const MesonAbi = require('./MesonAbi.json') 2 | const ERC20Abi = require('./ERC20Abi.json') 3 | 4 | module.exports = { MesonAbi, ERC20Abi } 5 | -------------------------------------------------------------------------------- /packages/config/abis/index.mjs: -------------------------------------------------------------------------------- 1 | import MesonAbi from './MesonAbi.json' assert { type: "json" }; 2 | import ERC20Abi from './ERC20Abi.json' assert { type: "json" }; 3 | 4 | export { 5 | MesonAbi, 6 | ERC20Abi 7 | }; 8 | -------------------------------------------------------------------------------- /packages/config/config/index.d.ts: -------------------------------------------------------------------------------- 1 | import {Meson, ERC20} from '../types'; 2 | 3 | export * from '../abis'; 4 | export * from '../networks'; 5 | export * from '../types'; 6 | 7 | export type { 8 | Meson as MesonContract, 9 | ERC20 as ERC20Contract 10 | }; 11 | 12 | export * from '../types/common' 13 | -------------------------------------------------------------------------------- /packages/config/config/index.js: -------------------------------------------------------------------------------- 1 | const {MesonAbi, ERC20Abi} = require('../abis'); 2 | const { 3 | mainnets, 4 | testnets, 5 | v0_mainnets, 6 | v0_testnets, 7 | tokenCategory, 8 | tokenType, 9 | iconFromToken, 10 | } = require('../networks'); 11 | 12 | module.exports = { 13 | MesonAbi, 14 | ERC20Abi, 15 | mainnets, 16 | testnets, 17 | v0_testnets, 18 | v0_mainnets, 19 | tokenCategory, 20 | tokenType, 21 | iconFromToken, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/config/config/index.mjs: -------------------------------------------------------------------------------- 1 | export * from '../abis/index.mjs'; 2 | export * from '../networks/index.mjs'; 3 | -------------------------------------------------------------------------------- /packages/config/networks/Token.d.ts: -------------------------------------------------------------------------------- 1 | export type MesonfiAddressFormat = 2 | 'ethers' 3 | | 'bitcoin' 4 | | 'tron' 5 | | 'aptos' 6 | | 'sui' 7 | | 'solana' 8 | | 'starknet' 9 | | 'ckb' 10 | | 'ton'; 11 | 12 | export interface IMesonfiTokenData { 13 | 14 | tokenIndex: number; 15 | 16 | symbol: string; 17 | 18 | name: string; 19 | 20 | icon?: string; 21 | 22 | addr: string; 23 | 24 | decimals: number; 25 | 26 | link?: string; 27 | } 28 | 29 | export interface IMesonfiNetworkNativeCurrency { 30 | 31 | symbol: string; 32 | 33 | decimals: number; 34 | 35 | name?: string; 36 | } 37 | 38 | export interface IMesonfiNetworkMetadata { 39 | tx_hash_siglock?: string; 40 | tx_hash_refcell?: string; 41 | tx_hash_joyid?: string; 42 | tx_hash_xudt?: string; 43 | code_hash_refcell?: string; 44 | code_hash_xudt?: string; 45 | pools?: Array>; 46 | } 47 | 48 | export interface IMesonfiNetworkData { 49 | 50 | id: string; 51 | 52 | name: string; 53 | 54 | alias: string; 55 | 56 | chainId: string; 57 | 58 | explorer?: string; 59 | 60 | slip44: string; 61 | 62 | shortSlip44: string; 63 | 64 | addressFormat: MesonfiAddressFormat; 65 | 66 | url: string; 67 | 68 | mesonAddress: string; 69 | 70 | nativeCurrency?: IMesonfiNetworkNativeCurrency; 71 | 72 | extensions: string[]; 73 | 74 | metadata?: IMesonfiNetworkMetadata; 75 | 76 | tokens: IMesonfiTokenData[]; 77 | } 78 | 79 | 80 | export interface IMesonfiTokenDataWithPartner extends IMesonfiTokenData { 81 | 82 | disabled?: boolean; 83 | 84 | partner?: string; 85 | } 86 | 87 | export interface IMesonfiNetworkDataWithPartner extends Omit { 88 | 89 | disabled?: boolean; 90 | 91 | tokens: IMesonfiTokenDataWithPartner[]; 92 | } 93 | -------------------------------------------------------------------------------- /packages/config/networks/category.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": "usdc", 3 | "2": "usdt", 4 | "3": "busd", 5 | "4": "usdc", 6 | "9": "usdc", 7 | "10": "usdt", 8 | "17": "usdc", 9 | "18": "usdt", 10 | "19": "busd", 11 | "32": "pod", 12 | "33": "usdc", 13 | "34": "usdt", 14 | "35": "busd", 15 | "36": "dai", 16 | "37": "cusd", 17 | "39": "usdb", 18 | "40": "fdusd", 19 | "41": "bbusd", 20 | "48": "usd1", 21 | "49": "usdc", 22 | "52": "dai", 23 | "65": "iusd", 24 | "67": "mbtc", 25 | "69": "merl", 26 | "71": "stone", 27 | "73": "solvbtc-m", 28 | "75": "solvbtc", 29 | "77": "solvbtc-a", 30 | "79": "ubtc", 31 | "81": "xsolvbtc", 32 | "83": "solvbtc-ena", 33 | "85": "solvbtc-jup", 34 | "87": "pump", 35 | "89": "b2", 36 | "91": "solvbtc-bera", 37 | "113": "pumpbtc", 38 | "114": "unibtc", 39 | "115": "cbbtc", 40 | "124": "duck", 41 | "191": "gas-token", 42 | "236": "pol", 43 | "238": "pol", 44 | "239": "pol", 45 | "240": "btc", 46 | "241": "btc", 47 | "242": "btc", 48 | "243": "btc", 49 | "244": "sol", 50 | "246": "sol", 51 | "247": "sol", 52 | "248": "bnb", 53 | "250": "bnb", 54 | "251": "bnb", 55 | "252": "eth", 56 | "254": "eth", 57 | "255": "eth" 58 | } 59 | -------------------------------------------------------------------------------- /packages/config/networks/config.d.ts: -------------------------------------------------------------------------------- 1 | import {IMesonfiNetworkDataWithPartner} from './Token'; 2 | 3 | declare const config: IMesonfiNetworkDataWithPartner[]; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /packages/config/networks/index.d.ts: -------------------------------------------------------------------------------- 1 | import {IMesonfiNetworkDataWithPartner, IMesonfiTokenData} from './Token'; 2 | 3 | export * from './Token'; 4 | 5 | export declare const mainnets: IMesonfiNetworkDataWithPartner[]; 6 | 7 | export declare const testnets: IMesonfiNetworkDataWithPartner[]; 8 | 9 | export declare const v0_mainnets: IMesonfiNetworkDataWithPartner[]; 10 | 11 | export declare const v0_testnets: IMesonfiNetworkDataWithPartner[]; 12 | 13 | export declare function tokenType(tokenIndex: number, pod?: boolean): string; 14 | 15 | export declare function tokenCategory(tokenIndex: number): string; 16 | 17 | export declare function iconFromToken(token: IMesonfiTokenData): string; 18 | 19 | export declare function getTokenTypeFromCategory(category: string): string; 20 | -------------------------------------------------------------------------------- /packages/config/networks/index.mjs: -------------------------------------------------------------------------------- 1 | import mainnets from './mainnets.json' assert {type: 'json'}; 2 | import testnets from './testnets.json' assert {type: 'json'}; 3 | import v0_mainnets from './v0/mainnets.json' assert {type: 'json'}; 4 | import v0_testnets from './v0/testnets.json' assert {type: 'json'}; 5 | import category from './category.json' assert {type: 'json'}; 6 | 7 | function tokenCategory(i) { 8 | 9 | if (category[i]) { 10 | return category[i]; 11 | } 12 | return 'unsupported'; 13 | } 14 | 15 | function tokenType(tokenIndex, pod = false) { 16 | 17 | if (tokenIndex >= 252) { 18 | return 'eth'; 19 | } 20 | 21 | if (tokenIndex >= 248) { 22 | return 'bnb'; 23 | } 24 | 25 | if (tokenIndex >= 244) { 26 | return 'sol'; 27 | } 28 | 29 | if (tokenIndex >= 240) { 30 | return 'btc'; 31 | } 32 | 33 | if (tokenIndex >= 236) { 34 | return 'matic'; 35 | } 36 | 37 | if (tokenIndex === 191) { 38 | return 'gas-token'; 39 | } 40 | 41 | if (tokenIndex <= 64) { 42 | if (pod && tokenIndex <= 32 && tokenIndex > 16) { 43 | return 'pod'; 44 | } 45 | return 'stablecoins'; 46 | } 47 | 48 | if (tokenIndex <= 112) { 49 | // [65, 112] -> [1, 24] 50 | return (Math.floor((tokenIndex + 1) / 2) - 32).toString(); 51 | } 52 | 53 | if (tokenIndex <= 128) { 54 | // [113, 128] -> [33, 48] 55 | return (tokenIndex - 80).toString(); 56 | } 57 | 58 | return 'unknown'; 59 | } 60 | 61 | function iconFromToken(token) { 62 | 63 | if (token.icon) { 64 | return token.icon; 65 | } 66 | 67 | return tokenCategory(token.tokenIndex); 68 | } 69 | 70 | const TOKEN_CATEGORY_TYPE_MAP = Object.entries(category).reduce((acc, [key, value]) => { 71 | acc[value] = acc[value] || tokenType(+key); 72 | return acc; 73 | }, []); 74 | 75 | function getTokenTypeFromCategory(tokenCategory) { 76 | return TOKEN_CATEGORY_TYPE_MAP[tokenCategory] || 'unknown'; 77 | } 78 | 79 | export { 80 | mainnets, 81 | testnets, 82 | v0_mainnets, 83 | v0_testnets, 84 | tokenCategory, 85 | tokenType, 86 | iconFromToken, 87 | getTokenTypeFromCategory 88 | }; 89 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mesonfi/config", 3 | "version": "1.26.4", 4 | "description": "", 5 | "license": "MIT", 6 | "repository": "https://github.com/MesonFi/meson-contracts-solidity", 7 | "main": "config/index.js", 8 | "module": "config/index.mjs", 9 | "typings": "config/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": "./config/index.mjs", 13 | "require": "./config/index.js", 14 | "types": "./config/index.d.ts" 15 | }, 16 | "./abis": { 17 | "import": "./abis/index.mjs", 18 | "require": "./abis/index.js", 19 | "types": "./abis/index.d.ts" 20 | }, 21 | "./MesonAbi": { 22 | "import": "./abis/MesonAbi.json", 23 | "require": "./abis/MesonAbi.json", 24 | "types": "./abis/MesonAbi.d.ts" 25 | }, 26 | "./ERC20Abi": { 27 | "import": "./abis/ERC20Abi.json", 28 | "require": "./abis/ERC20Abi.json", 29 | "types": "./abis/ERC20Abi.d.ts" 30 | }, 31 | "./networks": { 32 | "import": "./networks/index.mjs", 33 | "require": "./networks/index.js", 34 | "types": "./networks/index.d.ts" 35 | }, 36 | "./mainnets": { 37 | "import": "./networks/mainnets.json", 38 | "require": "./networks/mainnets.json", 39 | "types": "./networks/config.d.ts" 40 | }, 41 | "./testnets": { 42 | "import": "./networks/testnets.json", 43 | "require": "./networks/testnets.json", 44 | "types": "./networks/config.d.ts" 45 | }, 46 | "./v0/mainnets": { 47 | "import": "./networks/v0/mainnets.json", 48 | "require": "./networks/v0/mainnets.json", 49 | "types": "./networks/config.d.ts" 50 | }, 51 | "./v0/testnets": { 52 | "import": "./networks/v0/testnets.json", 53 | "require": "./networks/v0/testnets.json", 54 | "types": "./networks/config.d.ts" 55 | } 56 | }, 57 | "scripts": { 58 | "build": "npm run build:networks", 59 | "build:networks": "babel networks/index.mjs --out-file networks/index.js --extensions '.mjs,.json'", 60 | "prepare": "npm run build" 61 | }, 62 | "files": [ 63 | "config", 64 | "networks", 65 | "types", 66 | "abis" 67 | ], 68 | "devDependencies": { 69 | "@babel/cli": "^7.27.0", 70 | "@babel/core": "^7.26.10", 71 | "@babel/plugin-transform-modules-commonjs": "^7.26.3", 72 | "@babel/plugin-syntax-import-attributes": "^7.26.0", 73 | "@babel/plugin-syntax-import-assertions": "^7.26.0" 74 | }, 75 | "publishConfig": { 76 | "registry": "https://npm.pkg.github.com/" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/presets/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | -------------------------------------------------------------------------------- /packages/presets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mesonfi/presets", 3 | "version": "1.26.0", 4 | "description": "", 5 | "license": "MIT", 6 | "repository": "https://github.com/MesonFi/meson-contracts-solidity", 7 | "main": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "publishConfig": { 10 | "registry": "https://npm.pkg.github.com/" 11 | }, 12 | "files": [ 13 | "lib", 14 | "tsconfig.json" 15 | ], 16 | "scripts": { 17 | "build": "tsc", 18 | "prepare": "yarn build" 19 | }, 20 | "peerDependencies": { 21 | "@mesonfi/config": "^1.26.0" 22 | }, 23 | "dependencies": { 24 | "@aptos-labs/ts-sdk": "1.35.0", 25 | "@ckb-lumos/lumos": "^0.22.2", 26 | "@mesonfi/sdk": "^1.26.0", 27 | "@mysten/sui": "1.21.0", 28 | "@solana/web3.js": "1.87.5", 29 | "ethers": "5.7.2", 30 | "lodash": "^4.17.21", 31 | "starknet": "^5.24.3", 32 | "tronweb": "5.2.0" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^22.13.11", 36 | "typescript": "^5.4.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/presets/scripts/make-mainnets.js: -------------------------------------------------------------------------------- 1 | const chains = require('./chains.json') 2 | 3 | const SELECTED_CHAIN_IDS = [1, 56, 100, 137, 250, 43114, 1666600000] 4 | // conflux 1030 5 | 6 | const SLIP44 = { 7 | 250: 1007, 8 | 1666600000: 1023 9 | } 10 | 11 | const selected = chains.filter(item => SELECTED_CHAIN_IDS.indexOf(item.chainId) > -1) 12 | 13 | 14 | const presets = selected.map(chain => { 15 | const standard = chain.explorers && chain.explorers[0].standard 16 | const slip44 = chain.slip44 || SLIP44[chain.chainId] 17 | return { 18 | id: chain.chain.toLowerCase(), 19 | name: chain.name, 20 | alias: chain.shortName, 21 | chainId: '0x' + Number(chain.chainId).toString(16), 22 | slip44: '0x' + Number(0x80000000 + slip44).toString(16), 23 | extensions: standard === 'EIP3091' ? ['metamask'] : [], 24 | addressFormat: standard === 'EIP3091' ? 'ethers' : 'unknown', 25 | url: chain.rpc[0], 26 | explorer: chain.explorers[0].url, 27 | nativeCurrency: chain.nativeCurrency, 28 | mesonAddress: '', 29 | tokens: [], 30 | } 31 | }) 32 | 33 | console.log(JSON.stringify(presets, null, 2)) -------------------------------------------------------------------------------- /packages/presets/scripts/make-testnets.js: -------------------------------------------------------------------------------- 1 | const chains = require('./chains.json') 2 | 3 | const SELECTED_CHAIN_IDS = [3, 97, 80001, 4002, 43113, 1666700000] 4 | 5 | const SLIP44 = { 6 | 3: 60, 7 | 97: 714, 8 | 80001: 966, 9 | 4002: 1007, 10 | 43113: 9000, 11 | 1666700000: 1023 12 | } 13 | 14 | const selected = chains.filter(item => SELECTED_CHAIN_IDS.indexOf(item.chainId) > -1) 15 | 16 | 17 | const presets = selected.map(chain => { 18 | const explorer = chain.explorers && chain.explorers[0] || {} 19 | const slip44 = chain.slip44 || SLIP44[chain.chainId] 20 | return { 21 | id: chain.chain.toLowerCase() + '-testnet', 22 | name: chain.name, 23 | alias: chain.shortName, 24 | chainId: '0x' + Number(chain.chainId).toString(16), 25 | slip44: '0x' + Number(0x80000000 + slip44).toString(16), 26 | extensions: explorer.standard === 'EIP3091' ? ['metamask'] : [], 27 | addressFormat: explorer.standard === 'EIP3091' ? 'ethers' : 'unknown', 28 | url: chain.rpc[0], 29 | explorer: explorer.url, 30 | nativeCurrency: chain.nativeCurrency, 31 | mesonAddress: '', 32 | tokens: [], 33 | } 34 | }) 35 | 36 | console.log(JSON.stringify(presets, null, 2)) -------------------------------------------------------------------------------- /packages/presets/src/CustomFeeProvider.ts: -------------------------------------------------------------------------------- 1 | import { providers, BigNumber, utils } from 'ethers' 2 | 3 | export const CustomFeeHttpProvider = extendProvider(providers.StaticJsonRpcProvider) as typeof providers.StaticJsonRpcProvider 4 | export const CustomFeeWsProvider = extendProvider(providers.WebSocketProvider) as typeof providers.WebSocketProvider 5 | 6 | type Class = new (...args: any[]) => T 7 | function extendProvider(Provider: Class) { 8 | class CustomFeeProvider extends Provider { 9 | override async estimateGas(transaction: utils.Deferrable): Promise { 10 | const gasLimit = await super.estimateGas(transaction) 11 | // TODO: logger.debug('Transaction', `estimate gas success`, { estimateGas: gasLimit.toString() }) 12 | // TODO: log errors 13 | return gasLimit.mul(1.2 * 100).div(100) 14 | } 15 | 16 | override async getFeeData(): Promise { 17 | const feeData = await this._checkEip1559() || { gasPrice: await this.getGasPrice() } 18 | return feeData 19 | } 20 | 21 | #eip1559FeeData = undefined 22 | async _checkEip1559 () { 23 | if (typeof this.#eip1559FeeData !== 'undefined') { 24 | return this.#eip1559FeeData 25 | } 26 | 27 | const block = await this.getBlockWithTransactions('latest') 28 | const transactions = [...block.transactions] 29 | let offset = 0 30 | while (transactions.length < 20) { 31 | offset++ 32 | transactions.push(...(await this.getBlockWithTransactions(block.number - offset)).transactions) 33 | if (offset > 5) { 34 | break 35 | } 36 | } 37 | const eip1559Transactions = transactions.filter(tx => tx.maxFeePerGas && tx.maxPriorityFeePerGas) 38 | 39 | setTimeout(() => this.#eip1559FeeData = undefined, 60_000) 40 | 41 | if (!transactions.length || eip1559Transactions.length / transactions.length < 0.5) { 42 | this.#eip1559FeeData = null 43 | return 44 | } 45 | 46 | const sorted = eip1559Transactions.sort((tx1, tx2) => tx1.maxFeePerGas.lt(tx2.maxFeePerGas) ? -1 : 1) 47 | const index = Math.floor(sorted.length * 0.5) 48 | const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = sorted[index] 49 | this.#eip1559FeeData = { gasPrice, maxFeePerGas, maxPriorityFeePerGas } 50 | 51 | return this.#eip1559FeeData 52 | } 53 | } 54 | return CustomFeeProvider as unknown 55 | } 56 | -------------------------------------------------------------------------------- /packages/presets/src/index.ts: -------------------------------------------------------------------------------- 1 | import presets from './MesonPresets' 2 | 3 | export { PresetNetwork, PresetToken, MesonPresets } from './MesonPresets' 4 | export { CustomFeeHttpProvider, CustomFeeWsProvider } from './CustomFeeProvider' 5 | 6 | export { mainnets, testnets } from '@mesonfi/config' 7 | 8 | export default presets 9 | -------------------------------------------------------------------------------- /packages/presets/src/providers.ts: -------------------------------------------------------------------------------- 1 | import { RPC as CkbRPC } from '@ckb-lumos/lumos' 2 | import { TonClient, type TonClientParameters } from '@ton/ton' 3 | import { Aptos as AptosClient, Network, AptosConfig } from '@aptos-labs/ts-sdk' 4 | 5 | export class ExtendedAptosClient extends AptosClient { 6 | constructor(url: string) { 7 | super(new AptosConfig({ fullnode: url, indexer: `${url}/graphql`, network: Network.CUSTOM })) 8 | } 9 | } 10 | 11 | export class ExtendedCkbClient extends CkbRPC { 12 | readonly metadata: any 13 | 14 | constructor(url: string, config: any, metadata: any) { 15 | super(url, config || {}) 16 | this.metadata = metadata 17 | } 18 | } 19 | 20 | export class ExtendedTonClient extends TonClient { 21 | readonly metadata: any 22 | 23 | constructor(parameters: TonClientParameters, metadata: any) { 24 | super(parameters) 25 | this.metadata = metadata 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/presets/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "commonjs", 5 | "strict": false, 6 | "esModuleInterop": true, 7 | "outDir": "lib", 8 | "declaration": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "typeRoots": ["./node_modules/@types"] 12 | }, 13 | "include": ["./src"], 14 | "ts-node": { 15 | "transpileOnly": true, 16 | "transpiler": "ts-node/transpilers/swc-experimental" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/sdk/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | -------------------------------------------------------------------------------- /packages/sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mesonfi/sdk", 3 | "version": "1.26.1", 4 | "description": "", 5 | "license": "MIT", 6 | "repository": "https://github.com/MesonFi/meson-contracts-solidity", 7 | "main": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "publishConfig": { 10 | "registry": "https://npm.pkg.github.com/" 11 | }, 12 | "files": [ 13 | "lib", 14 | "tsconfig.json" 15 | ], 16 | "scripts": { 17 | "test": "mocha -r ts-node/register 'tests/**/*.spec.ts'", 18 | "build": "tsc", 19 | "prepare": "yarn build" 20 | }, 21 | "peerDependencies": { 22 | "@mesonfi/config": "^1.26.0" 23 | }, 24 | "dependencies": { 25 | "@aptos-labs/ts-sdk": "1.35.0", 26 | "@bitcoinerlab/secp256k1": "^1.1.1", 27 | "@ckb-lumos/lumos": "^0.22.2", 28 | "@mysten/sui": "1.21.0", 29 | "@solana/spl-token": "0.3.8", 30 | "@solana/web3.js": "1.87.5", 31 | "@ton/core": "0.56.3", 32 | "@ton/crypto": "3.3.0", 33 | "@ton/ton": "14.0.0", 34 | "bitcoinjs-lib": "^6.1.6", 35 | "bs58": "^4.0.1", 36 | "cross-fetch": "^3.1.5", 37 | "ecpair": "^2.1.0", 38 | "ethers": "5.7.2", 39 | "lodash": "^4.17.21", 40 | "starknet": "^5.24.3", 41 | "tonapi-sdk-js": "2.0.3", 42 | "tronweb": "5.2.0", 43 | "tweetnacl": "1.0.3", 44 | "zksync-web3": "0.13.4" 45 | }, 46 | "devDependencies": { 47 | "@types/chai": "^4.3.3", 48 | "@types/chai-as-promised": "^7.1.5", 49 | "@types/lodash": "^4.14.186", 50 | "@types/mocha": "^9.1.1", 51 | "@types/node": "^20.9.0", 52 | "abi-wan-kanabi": "^2.1.0", 53 | "chai": "^4.3.6", 54 | "chai-as-promised": "^7.1.1", 55 | "ethereum-waffle": "^4.0.4", 56 | "mocha": "^10.0.0", 57 | "ts-node": "^10.9.1", 58 | "typescript": "^5.4.5" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/sdk/src/Rpc.ts: -------------------------------------------------------------------------------- 1 | import { IAdaptor } from './adaptors/types' 2 | 3 | export type Block = { 4 | hash: string 5 | parentHash: string 6 | number: string 7 | timestamp: string 8 | transactions: Transaction[] 9 | uncles?: string[] 10 | } 11 | 12 | export type Transaction = { 13 | hash: string 14 | from: string 15 | to: string 16 | value: string 17 | input: string 18 | blockHash?: string 19 | blockNumber: string 20 | timestamp: string 21 | } 22 | 23 | export type Receipt = { 24 | status: string 25 | blockNumber: string 26 | [key: string]: any 27 | } 28 | 29 | export class Rpc { 30 | #provider: IAdaptor 31 | 32 | constructor(provider: IAdaptor) { 33 | this.#provider = provider 34 | } 35 | 36 | async getLatestBlock(): Promise { 37 | return await this.#provider.send('eth_getBlockByNumber', ['latest', true]) 38 | } 39 | 40 | async getBlockByNumber(blockNumber: number, withTransactions: boolean = false): Promise { 41 | const hexBlockNumber = `0x${blockNumber.toString(16)}` 42 | return await this.#provider.send('eth_getBlockByNumber', [hexBlockNumber, withTransactions]) 43 | } 44 | 45 | async getBlockByHash(blockHash: string, withTransactions: boolean = false): Promise { 46 | return await this.#provider.send('eth_getBlockByHash', [blockHash, withTransactions]) 47 | } 48 | 49 | async getTransaction(hash: string): Promise { 50 | return await this.#provider.send('eth_getTransactionByHash', [hash]) 51 | } 52 | 53 | async getReceipt(hash: string): Promise { 54 | return await this.#provider.send('eth_getTransactionReceipt', [hash]) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/sdk/src/SignedSwap.ts: -------------------------------------------------------------------------------- 1 | import { utils } from 'ethers' 2 | 3 | import type { CompactSignature } from './SwapSigner' 4 | import { Swap } from './Swap' 5 | import { SwapSigner } from './SwapSigner' 6 | 7 | export interface SignedSwapRequestData { 8 | testnet?: boolean, 9 | encoded: string, 10 | initiator: string, 11 | fromContract?: string, 12 | signature: CompactSignature, 13 | } 14 | 15 | export interface SignedSwapReleaseData extends SignedSwapRequestData { 16 | recipient: string, 17 | } 18 | 19 | export class SignedSwapRequest implements SignedSwapRequestData { 20 | readonly testnet: boolean 21 | readonly swap: Swap 22 | readonly encoded: string 23 | readonly initiator: string 24 | readonly fromContract?: string 25 | readonly signature: CompactSignature 26 | 27 | constructor (data: SignedSwapRequestData) { 28 | if (!data.encoded) { 29 | throw new Error('Missing encoded') 30 | } else if (!data.initiator) { 31 | throw new Error('Missing initiator') 32 | } else if (!data.signature) { 33 | throw new Error('Missing signature') 34 | } 35 | 36 | this.testnet = Boolean(data.testnet) 37 | this.swap = Swap.decode(data.encoded) 38 | 39 | this.encoded = data.encoded 40 | this.initiator = data.initiator.toLowerCase() 41 | if (data.fromContract) { 42 | this.fromContract = data.fromContract 43 | } 44 | this.signature = data.signature 45 | } 46 | 47 | getDigest () { 48 | return SwapSigner.hashRequest(this.encoded, this.testnet) 49 | } 50 | 51 | checkSignature () { 52 | const recovered = utils.recoverAddress(this.getDigest(), this.signature).toLowerCase() 53 | if (recovered !== this.initiator) { 54 | throw new Error('Invalid signature') 55 | } 56 | } 57 | 58 | toObject (): SignedSwapRequestData { 59 | const data: SignedSwapRequestData = { 60 | encoded: this.encoded, 61 | initiator: this.initiator, 62 | signature: this.signature, 63 | } 64 | if (this.fromContract) { 65 | data.fromContract = this.fromContract 66 | } 67 | if (this.testnet) { 68 | data.testnet = true 69 | } 70 | return data 71 | } 72 | } 73 | 74 | export class SignedSwapRelease extends SignedSwapRequest implements SignedSwapReleaseData { 75 | readonly recipient: string; 76 | 77 | constructor (data: SignedSwapReleaseData) { 78 | super(data) 79 | 80 | if (!data.recipient) { 81 | throw new Error('Missing recipient') 82 | } 83 | this.recipient = data.recipient 84 | } 85 | 86 | getDigest () { 87 | return SwapSigner.hashRelease(this.encoded, this.recipient, this.testnet) 88 | } 89 | 90 | toObject (): SignedSwapReleaseData { 91 | return { 92 | ...super.toObject(), 93 | recipient: this.recipient, 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/sdk/src/SwapWithSigner.ts: -------------------------------------------------------------------------------- 1 | import { Swap, SwapData } from './Swap' 2 | import { SwapSigner } from './SwapSigner' 3 | import { SignedSwapRequestData, SignedSwapReleaseData } from './SignedSwap' 4 | 5 | export class SwapWithSigner extends Swap { 6 | readonly swapSigner: SwapSigner 7 | 8 | constructor(req: SwapData, swapSigner: SwapSigner) { 9 | super(req) 10 | this.swapSigner = swapSigner 11 | } 12 | 13 | async signForRequest(testnet: boolean): Promise { 14 | const signature = await this.swapSigner.signSwapRequest(this.encoded, testnet) 15 | const initiator = this.swapSigner.getAddress(this.encoded) 16 | const data: SignedSwapRequestData = { 17 | encoded: this.encoded, 18 | initiator, 19 | signature, 20 | } 21 | if (testnet) { 22 | data.testnet = true 23 | } 24 | return data 25 | } 26 | 27 | async signForRelease(recipient: string, testnet: boolean): Promise { 28 | const signature = await this.swapSigner.signSwapRelease(this.encoded, recipient, testnet) 29 | const initiator = this.swapSigner.getAddress(this.encoded) 30 | const data: SignedSwapReleaseData = { 31 | encoded: this.encoded, 32 | initiator, 33 | recipient, 34 | signature, 35 | } 36 | if (testnet) { 37 | data.testnet = true 38 | } 39 | return data 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/FailoverAdaptors.ts: -------------------------------------------------------------------------------- 1 | import type { IAdaptor, AdaptorConstructor } from './types' 2 | import { timeout } from '../utils' 3 | 4 | import EthersAdaptor from './ethers/EthersAdaptor' 5 | import ZksyncAdaptor from './zksync/ZksyncAdaptor' 6 | import AptosAdaptor from './aptos/AptosAdaptor' 7 | import BtcAdaptor from './bitcoin/BtcAdaptor' 8 | import CkbAdaptor from './ckb/CkbAdaptor' 9 | import SolanaAdaptor from './solana/SolanaAdaptor' 10 | import StarkAdaptor from './starknet/StarkAdaptor' 11 | import SuiAdaptor from './sui/SuiAdaptor' 12 | import TonAdaptor from './ton/TonAdaptor' 13 | import TronAdaptor from './tron/TronAdaptor' 14 | 15 | function extendToFailoverAdaptor(Adaptor: ClassAdaptor): ClassAdaptor { 16 | return class FailoverAdaptor extends Adaptor { 17 | readonly adaptors: IAdaptor[] 18 | 19 | #timeout: number 20 | 21 | constructor(...args: any[]) { 22 | const [adaptors, opt = {}] = args[0] as [IAdaptor[], any] 23 | super(adaptors[0].client) 24 | this.adaptors = adaptors 25 | this.#timeout = opt.timeout || 3000 26 | } 27 | 28 | // override get nodeUrl() { 29 | // return this.adaptors.map(adp => adp.nodeUrl).join('\n') 30 | // } 31 | 32 | switch() { 33 | const adaptors = [...this.adaptors].sort(() => Math.random() - 0.5) 34 | super.client = adaptors[0].client 35 | } 36 | 37 | get currentClient() { 38 | return super.client 39 | } 40 | 41 | get client() { 42 | const that = this 43 | return new Proxy({}, { 44 | get(target, prop: string) { 45 | if (typeof that.currentClient[prop] !== 'function') { 46 | return that.currentClient[prop] 47 | } 48 | const adaptors = [...that.adaptors].sort(() => Math.random() - 0.5) 49 | 50 | return (...args) => { 51 | const current = adaptors.pop() 52 | const result = current.client[prop](...args) 53 | if (!result.then) { 54 | return result 55 | } 56 | 57 | const errors = [] 58 | return new Promise(async (resolve, reject) => { 59 | try { 60 | resolve(await timeout(result, that.#timeout)) 61 | return 62 | } catch (e) { 63 | errors.push(e) 64 | } 65 | 66 | while (adaptors.length) { 67 | const current = adaptors.pop() 68 | try { 69 | resolve(await timeout(current.client[prop](...args), that.#timeout)) 70 | return 71 | } catch (e) { 72 | errors.push(e) 73 | } 74 | } 75 | 76 | reject(new AggregateError(errors, 'All failed')) 77 | }) 78 | } 79 | } 80 | }) 81 | } 82 | 83 | async send(method, params) { 84 | return await timeout(this._send(method, params), 30_000) 85 | } 86 | 87 | private async _send(method, params) { 88 | const adaptors = [...this.adaptors].sort(() => Math.random() - 0.5) 89 | const errors = [] 90 | while (adaptors.length) { 91 | const current = adaptors.pop() 92 | try { 93 | return await timeout(current.send(method, params), this.#timeout) 94 | } catch (e) { 95 | errors.push(e) 96 | } 97 | } 98 | throw new AggregateError(errors, 'All failed') 99 | } 100 | 101 | async sendTransaction(tx: any) { 102 | return new Promise((resolve, reject) => { 103 | let success = false 104 | const errors = [] 105 | Promise.all(this.adaptors.map(adp => (adp as any).sendTransaction(tx) 106 | .then((result: any) => { 107 | success = true 108 | resolve(result) 109 | }) 110 | .catch((e: Error) => errors.push(e)) 111 | )).then(() => { 112 | if (!success) { 113 | reject(new AggregateError(errors, 'All failed (sendTransaction)')) 114 | } 115 | }) 116 | }) 117 | } 118 | } 119 | } 120 | 121 | export class FailoverEthersAdaptor extends extendToFailoverAdaptor(EthersAdaptor) { 122 | constructor(adaptors: EthersAdaptor[], opt: any = {}) { 123 | super([adaptors, opt] as any) 124 | } 125 | } 126 | 127 | export class FailoverZksyncAdaptor extends extendToFailoverAdaptor(ZksyncAdaptor) { 128 | constructor(adaptors: ZksyncAdaptor[], opt: any = {}) { 129 | super([adaptors, opt] as any) 130 | } 131 | } 132 | 133 | export class FailoverAptosAdaptor extends extendToFailoverAdaptor(AptosAdaptor) { 134 | constructor(adaptors: AptosAdaptor[], opt: any = {}) { 135 | super([adaptors, opt] as any) 136 | } 137 | } 138 | 139 | export class FailoverBtcAdaptor extends extendToFailoverAdaptor(BtcAdaptor) { 140 | constructor(adaptors: BtcAdaptor[], opt: any = {}) { 141 | super([adaptors, opt] as any) 142 | } 143 | } 144 | 145 | export class FailoverCkbAdaptor extends extendToFailoverAdaptor(CkbAdaptor) { 146 | constructor(adaptors: CkbAdaptor[], opt: any = {}) { 147 | super([adaptors, opt] as any) 148 | } 149 | } 150 | 151 | export class FailoverSolanaAdaptor extends extendToFailoverAdaptor(SolanaAdaptor) { 152 | constructor(adaptors: SolanaAdaptor[], opt: any = {}) { 153 | super([adaptors, opt] as any) 154 | } 155 | } 156 | 157 | export class FailoverStarkAdaptor extends extendToFailoverAdaptor(StarkAdaptor) { 158 | constructor(adaptors: StarkAdaptor[], opt: any = {}) { 159 | super([adaptors, opt] as any) 160 | } 161 | } 162 | 163 | export class FailoverSuiAdaptor extends extendToFailoverAdaptor(SuiAdaptor) { 164 | constructor(adaptors: SuiAdaptor[], opt: any = {}) { 165 | super([adaptors, opt] as any) 166 | } 167 | } 168 | 169 | export class FailoverTonAdaptor extends extendToFailoverAdaptor(TonAdaptor) { 170 | constructor(adaptors: TonAdaptor[], opt: any = {}) { 171 | super([adaptors, opt] as any) 172 | } 173 | } 174 | 175 | export class FailoverTronAdaptor extends extendToFailoverAdaptor(TronAdaptor) { 176 | constructor(adaptors: TronAdaptor[], opt: any = {}) { 177 | super([adaptors, opt] as any) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/aptos/AptosAdaptor.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, utils } from 'ethers' 2 | import { Aptos as AptosClient } from '@aptos-labs/ts-sdk' 3 | 4 | import { IAdaptor } from '../types' 5 | 6 | // ad hoc 7 | const mesonAddress = '0x521ac831177db52270301c7e4f068f8ee4231e47ba571fab86de718dc181f4cf' 8 | 9 | export default class AptosAdaptor implements IAdaptor { 10 | #client: AptosClient 11 | 12 | constructor(client: AptosClient) { 13 | this.#client = client 14 | } 15 | 16 | get client() { 17 | return this.#client 18 | } 19 | 20 | protected set client(c) { 21 | this.#client = c 22 | } 23 | 24 | get nodeUrl() { 25 | return this.client.config.fullnode 26 | } 27 | 28 | async detectNetwork(): Promise { 29 | return await this.client.getLedgerInfo() 30 | } 31 | 32 | async getBlockNumber() { 33 | const info = await this.detectNetwork() 34 | return Number(info.block_height) 35 | } 36 | 37 | async getGasPrice() { 38 | return BigNumber.from(0) 39 | } 40 | 41 | async getTransactionCount(addr: string) { 42 | } 43 | 44 | async getBalance(addr) { 45 | const type = `0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>` 46 | try { 47 | const result = await this.client.getAccountResource({ accountAddress: addr, resourceType: type }) 48 | return BigNumber.from(result.coin.value) 49 | } catch (e) { 50 | if (e.errors) { 51 | e = e.errors[0] 52 | } 53 | if (e.data?.error_code === 'resource_not_found') { 54 | return BigNumber.from(0) 55 | } 56 | throw e 57 | } 58 | } 59 | 60 | async getCode(addr: string): Promise { 61 | // TODO 62 | return '' 63 | } 64 | 65 | async getLogs(filter) { 66 | const list1 = await this.client.getAccountEventsByEventType({ 67 | accountAddress: '0x0', 68 | eventType: `${filter.address}::MesonSwap::SwapExecuted`, 69 | options: { limit: 20, orderBy: [{ transaction_block_height: 'desc' }] } 70 | }) 71 | const list2 = await this.client.getAccountEventsByEventType({ 72 | accountAddress: '0x0', 73 | eventType: `${filter.address}::MesonSwap::SwapPosted`, 74 | options: { limit: 20, orderBy: [{ transaction_block_height: 'desc' }] } 75 | }) 76 | return [...list1, ...list2] 77 | .map(raw => _wrapAptosEvent(raw, filter.address)) 78 | .sort((e1, e2) => e2.blockNumber - e1.blockNumber) 79 | } 80 | 81 | on () {} 82 | removeAllListeners () {} 83 | 84 | async send(method, params) { 85 | if (method === 'eth_getBlockByNumber') { 86 | if (params[0] === 'latest') { 87 | const number = await this.getBlockNumber() 88 | const block = await this.client.getBlockByHeight({ blockHeight: number, options: { withTransactions: false } }) 89 | return _wrapAptosBlock(block) 90 | } else { 91 | const number = parseInt(params[0]) 92 | const block = await this.client.getBlockByHeight({ blockHeight: number, options: { withTransactions: params[1] } }) 93 | return _wrapAptosBlock(block) 94 | } 95 | } else if (method === 'eth_getBlockByHash') { 96 | if (params[0] === 'n/a') { 97 | return {} 98 | } 99 | const number = parseInt(params[0]) 100 | const block = await this.client.getBlockByHeight({ blockHeight: number, options: { withTransactions: params[1] } }) 101 | return _wrapAptosBlock(block) 102 | } else if (method === 'eth_getTransactionByHash') { 103 | if (params[0].startsWith('0x')) { 104 | return _wrapAptosTx(await this.client.getTransactionByHash({ transactionHash: params[0] })) 105 | } 106 | return _wrapAptosTx(await this.client.getTransactionByVersion({ ledgerVersion: params[0] })) 107 | } else if (method === 'eth_getTransactionReceipt') { 108 | if (params[0].startsWith('0x')) { 109 | return _wrapAptosTx(await this.client.getTransactionByHash({ transactionHash: params[0] })) 110 | } 111 | return _wrapAptosTx(await this.client.getTransactionByVersion({ ledgerVersion: params[0] })) 112 | } 113 | } 114 | 115 | async waitForTransaction(hash: string, confirmations?: number, timeout?: number) { 116 | const result = await this.client.waitForTransaction({ 117 | transactionHash: hash, 118 | options: { 119 | checkSuccess: !!confirmations, 120 | timeoutSecs: timeout || 20 121 | } 122 | }) 123 | return _wrapAptosTx(result) 124 | } 125 | } 126 | 127 | function _wrapAptosEvent(raw, address) { 128 | const { transaction_block_height, transaction_version } = raw 129 | return { 130 | blockNumber: transaction_block_height, 131 | address, 132 | transactionHash: transaction_version.toString(), 133 | } 134 | } 135 | 136 | function _wrapAptosBlock(raw) { 137 | return { 138 | hash: '0x' + Number(raw.block_height).toString(16), 139 | parentHash: '0x' + Number(raw.block_height - 1).toString(16), 140 | number: raw.block_height, 141 | timestamp: Math.floor(raw.block_timestamp / 1000000).toString(), 142 | transactions: raw.transactions?.filter(tx => tx.type === 'user_transaction').map(_wrapAptosTx) || [] 143 | } 144 | } 145 | 146 | function _wrapAptosTx(raw) { 147 | let to = raw.payload?.function?.split('::')?.[0] || '0x' 148 | let payload = raw.payload 149 | 150 | // ad hoc for okx 151 | if (raw.payload?.function === '0xdd6dba734fc12f45618be09d9456dbe101698fd2eca68bb2e4e64172a122b64f::meson_adapter::xbridge_meson') { 152 | payload = { 153 | type: 'entry_function_payload', 154 | function: `${mesonAddress}::MesonSwap::postSwapFromInitiator`, 155 | arguments: [ 156 | payload.arguments[7], 157 | payload.arguments[8], 158 | '1', // payload.arguments[9], 159 | ], 160 | contractAddress: to, 161 | } 162 | to = mesonAddress 163 | } 164 | 165 | return { 166 | blockHash: 'n/a', 167 | blockNumber: '', 168 | hash: utils.hexZeroPad(raw.hash, 32), 169 | from: utils.hexZeroPad(raw.sender, 32), 170 | to: utils.hexZeroPad(to, 32), 171 | value: '0', 172 | input: JSON.stringify(payload), 173 | timestamp: Math.floor(raw.timestamp / 1000000).toString(), 174 | status: raw.success ? '0x1' : '0x0' 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/aptos/AptosWallet.ts: -------------------------------------------------------------------------------- 1 | import { utils } from 'ethers' 2 | import { Account as AptosAccount } from '@aptos-labs/ts-sdk' 3 | import AptosAdaptor from './AptosAdaptor' 4 | 5 | export default class AptosWallet extends AptosAdaptor { 6 | readonly account: AptosAccount 7 | 8 | constructor(adaptor: AptosAdaptor, account: AptosAccount) { 9 | super(adaptor.client) 10 | this.account = account 11 | } 12 | 13 | get address(): string { 14 | return this.account.accountAddress.toString() 15 | } 16 | 17 | signMessage (msg: string) { 18 | return this.account.sign(utils.toUtf8Bytes(msg)).toString() 19 | } 20 | 21 | async sendTransaction(payload, options) { 22 | const transaction = await this.client.transaction.build.simple({ 23 | sender: this.address, 24 | data: { 25 | function: payload.function, 26 | functionArguments: payload.arguments, 27 | typeArguments: payload.type_arguments, 28 | } 29 | }) 30 | const tx = await this.client.signAndSubmitTransaction({ signer: this.account, transaction }) 31 | 32 | return { 33 | hash: utils.hexZeroPad(tx.hash, 32), 34 | wait: () => this.waitForTransaction(tx.hash) 35 | } 36 | } 37 | 38 | async deploy(module: string, metadata: string) { 39 | // let hash = await this.client.publishPackage( 40 | // this.account, 41 | // new HexString(metadata).toUint8Array(), 42 | // [new TxnBuilderTypes.Module(new HexString(module).toUint8Array())] 43 | // ) 44 | // hash = utils.hexZeroPad(hash, 32) 45 | 46 | // return { 47 | // hash, 48 | // wait: () => this.waitForTransaction(hash) 49 | // } 50 | } 51 | } 52 | 53 | export class AptosExtWallet extends AptosWallet { 54 | readonly ext: any 55 | 56 | constructor(adaptor: AptosAdaptor, ext) { 57 | super(adaptor, null) 58 | this.ext = ext 59 | } 60 | 61 | get address() { 62 | // TODO: might be different for different exts 63 | return this.ext.signer.account() as string 64 | } 65 | 66 | async sendTransaction(payload, options) { 67 | // This method is provided by `@manahippo/aptos-wallet-adapter` 68 | const tx = await this.ext.signer.signAndSubmitTransaction(payload, options) 69 | // TODO: error handling 70 | return { 71 | hash: utils.hexZeroPad(tx.hash, 32), 72 | wait: () => this.waitForTransaction(tx.hash) 73 | } 74 | } 75 | 76 | async deploy(): Promise { 77 | throw new Error('Cannot deploy with extention wallet') 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/bitcoin/BtcClient.ts: -------------------------------------------------------------------------------- 1 | import { networks } from 'bitcoinjs-lib' 2 | 3 | export default class BtcClient { 4 | readonly url: string 5 | readonly isTestnet: boolean 6 | readonly mesonAddress: string 7 | 8 | constructor(url: string, isTestnet?: boolean, mesonAddress?: string) { 9 | this.url = url 10 | this.isTestnet = isTestnet 11 | this.mesonAddress = mesonAddress 12 | } 13 | 14 | get network() { 15 | return this.isTestnet ? networks.testnet : networks.bitcoin 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/bitcoin/BtcWallet.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import axios from 'axios' 3 | import * as btclib from 'bitcoinjs-lib' 4 | import ecc from '@bitcoinerlab/secp256k1' 5 | 6 | import BtcAdaptor from './BtcAdaptor' 7 | import { ECPairInterface, ECPairFactory } from 'ecpair' 8 | 9 | btclib.initEccLib(ecc) 10 | const ECPair = ECPairFactory(ecc) 11 | 12 | export default class BtcWallet extends BtcAdaptor { 13 | readonly #keypair: ECPairInterface 14 | readonly #pubkey: any 15 | readonly #address: string 16 | 17 | readonly #dustValue: number 18 | readonly #tolerance: number 19 | 20 | constructor(adaptor: BtcAdaptor, keypair?: any) { 21 | super(adaptor.client) 22 | if (keypair) { 23 | this.#keypair = keypair 24 | this.#pubkey = keypair.publicKey 25 | this.#address = btclib.payments.p2wpkh({ pubkey: this.pubkey, network: this.network }).address 26 | } 27 | 28 | this.#dustValue = 600 29 | this.#tolerance = 4 30 | } 31 | 32 | get pubkey(): any { 33 | return this.#pubkey 34 | } 35 | 36 | get address(): string { 37 | return this.#address 38 | } 39 | 40 | async _getTransferSkeleton({ to, value }) { 41 | // Fetch utxos 42 | const response = await fetch(`${this.nodeUrl}/address/${this.#address}/utxo`) 43 | const utxos = (await response.json()).sort(() => Math.sign(Math.random() - 0.5)) 44 | const feeRate = (await this._getFeeRate()).fastestFee 45 | 46 | // Collect utxos until reach the value 47 | let collectedValue = 0 48 | let utxoHexs = [] 49 | let fee = Math.ceil(( 50 | 10.5 + // vBytes for Overhead. See https://bitcoinops.org/en/tools/calc-size/ 51 | 31 * 2 + // vBytes for 2 Outputs (P2WPKH) 52 | this.#tolerance // vBytes for fault tolerance 53 | ) * feeRate) 54 | 55 | while (collectedValue < value + fee) { 56 | const utxo = utxos.pop() 57 | if (!utxo) throw new Error('Insufficient balance') 58 | if (utxo.value < this.#dustValue) continue 59 | const response = await fetch(`${this.nodeUrl}/tx/${utxo.txid}/hex`) 60 | const utxoHex = await response.text() 61 | utxoHexs.push([utxo.txid, utxo.vout, utxoHex]) 62 | collectedValue += utxo.value 63 | fee += 68 * feeRate // vBytes for an extra Input (P2WPKH) 64 | } 65 | 66 | // Build psbt 67 | const psbt = new btclib.Psbt({ network: this.network }) 68 | for (const [txid, vout, hex] of utxoHexs) { 69 | psbt.addInput({ 70 | hash: txid, 71 | index: vout, 72 | nonWitnessUtxo: Buffer.from(hex, 'hex'), 73 | }) 74 | } 75 | 76 | // Add outputs 77 | psbt.addOutput({ 78 | address: to, 79 | value: value, 80 | }) 81 | psbt.addOutput({ 82 | address: this.#address, 83 | value: collectedValue - value - Math.ceil(fee), 84 | }) 85 | 86 | return psbt 87 | } 88 | 89 | 90 | async transfer({ to, value }) { 91 | // Collect signatures 92 | const psbt = await this._getTransferSkeleton({ to, value }) 93 | psbt.signAllInputs(this.#keypair) 94 | 95 | // Validate and finalize 96 | const validator = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => 97 | ECPair.fromPublicKey(pubkey).verify(msghash, signature) 98 | psbt.validateSignaturesOfInput(0, validator) 99 | psbt.finalizeAllInputs() 100 | 101 | // Broadcast 102 | const txHex = psbt.extractTransaction().toHex() 103 | const response = await axios.post(`${this.nodeUrl}/tx`, txHex, { 104 | headers: { 'Content-Type': 'application/x-www-form-urlencoded' } 105 | }) 106 | 107 | return { 108 | hash: response.data, 109 | wait: (confirmations: number) => this.waitForTransaction(response.data, confirmations) 110 | } 111 | } 112 | 113 | async sendTransaction({ to, value }: { swapId?: string, to: string, value: number }) { 114 | return this.transfer({ to, value }) 115 | } 116 | } 117 | 118 | export class BtcWalletFromExtension extends BtcWallet { 119 | readonly ext: any 120 | 121 | constructor(adaptor: BtcAdaptor, ext: any) { 122 | super(adaptor) 123 | this.ext = ext 124 | } 125 | 126 | get address(): string { 127 | return this.ext?._selectedAddress 128 | } 129 | 130 | async deploy(): Promise { 131 | throw new Error('Cannot deploy with extention wallet') 132 | } 133 | 134 | override async transfer({ to, value }) { 135 | const hash = await this.ext?.transfer(to, BigNumber.from(value).toNumber()) 136 | return { 137 | hash, 138 | wait: () => this.waitForTransaction(hash) 139 | } 140 | } 141 | 142 | override async sendTransaction({ to, value }) { 143 | return this.transfer({ to, value }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/bitcoin/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { ECPairFactory } from 'ecpair' 3 | import * as btclib from 'bitcoinjs-lib' 4 | import ecc from '@bitcoinerlab/secp256k1' 5 | 6 | import { Swap } from '../../Swap' 7 | import { getSwapId } from '../../utils' 8 | import BtcAdaptor from './BtcAdaptor' 9 | import BtcWallet, { BtcWalletFromExtension } from './BtcWallet' 10 | 11 | const ECPair = ECPairFactory(ecc) 12 | 13 | export function getWallet(input: string = '', adaptor: BtcAdaptor, Wallet = BtcWallet): BtcWallet { 14 | if (input.startsWith('0x')) { 15 | // HEX format 16 | const buffer = Buffer.from(input.substring(2), 'hex') 17 | const keypair = ECPair.fromPrivateKey(buffer, { network: adaptor.client.network }) 18 | return new Wallet(adaptor, keypair) 19 | } else if (input) { 20 | // WIF format 21 | const keypair = ECPair.fromWIF(input, adaptor.client.network) 22 | return new Wallet(adaptor, keypair) 23 | } 24 | } 25 | 26 | export function getWalletFromExtension(ext, adaptor: BtcAdaptor): BtcWalletFromExtension { 27 | return new BtcWalletFromExtension(adaptor, ext) 28 | } 29 | 30 | export function getContract(address: string, abi, adaptor: BtcAdaptor) { 31 | return new Proxy({}, { 32 | get(target, prop: string) { 33 | if (prop === 'address') { 34 | return address 35 | } else if (prop === 'provider') { 36 | return adaptor 37 | } else if (prop === 'signer') { 38 | if (adaptor instanceof BtcWallet) { 39 | return adaptor 40 | } 41 | throw new Error(`BtcContract doesn't have a signer.`) 42 | } else if (prop === 'interface') { 43 | return { 44 | format: () => abi, 45 | parseTransaction: tx => { 46 | // tx.data 47 | return { name: '', args: [] } 48 | } 49 | } 50 | } else if (['queryFilter', 'on', 'removeAllListeners'].includes(prop)) { 51 | return () => { } 52 | } else if (prop === 'connect') { 53 | return (wallet: BtcWallet) => getContract(address, abi, wallet) 54 | } else if (prop === 'filters') { 55 | throw new Error('BtcContract.filters not implemented') 56 | } else if (prop === 'pendingTokenBalance') { 57 | return async (tokenIndex: number) => { 58 | } 59 | } 60 | 61 | let method = abi.find(item => item.name === prop) 62 | if (method?.type === 'function') { 63 | 64 | if (['view', 'pure'].includes(method.stateMutability)) { 65 | return async (...args) => { 66 | // ERC20 like 67 | if (prop === 'name') { 68 | return 'Bitcoin' 69 | } else if (prop === 'symbol') { 70 | return 'BTC' 71 | } else if (prop === 'decimals') { 72 | return 8 73 | } else if (prop === 'balanceOf') { 74 | return await adaptor.getBalance(args[0]) 75 | } else if (prop === 'allowance') { 76 | return BigNumber.from(2).pow(128).sub(1) 77 | } 78 | 79 | // Meson 80 | if (prop === 'getShortCoinType') { 81 | return '0x0000' 82 | } else if (prop === 'getSupportedTokens') { 83 | return { tokens: ['0x0000000000000000000000000000000000000001'], indexes: [243] } 84 | } else if (prop === 'poolTokenBalance') { 85 | const balance = await adaptor.getBalance(address) 86 | return balance.div(100) // decimals 8 -> 6 87 | } else if (prop === 'serviceFeeCollected') { 88 | return BigNumber.from(0) 89 | } 90 | } 91 | } else { 92 | return async (...args) => { 93 | let options 94 | if (args.length > method.inputs.length) { 95 | options = args.pop() 96 | } 97 | 98 | const swap = Swap.decode(args[0]) 99 | if (prop === 'directRelease') { 100 | const [_encoded, _r, _yParityAndS, _initiator, recipient] = args 101 | const swapId = getSwapId(_encoded, _initiator) 102 | return await (adaptor as BtcWallet).sendTransaction({ 103 | swapId, 104 | to: recipient, 105 | value: swap.amount.sub(swap.fee).mul(100).toNumber(), 106 | }) 107 | } else if (prop === 'directExecuteSwap') { 108 | return await (adaptor as BtcWallet).sendTransaction({ 109 | to: address, 110 | value: swap.amount.mul(100).toNumber(), 111 | }) 112 | } else { 113 | throw new Error(`Method ${prop} is not implemented.`) 114 | } 115 | } 116 | } 117 | } 118 | } 119 | }) 120 | } 121 | 122 | export function parseAddress(addr: string) { 123 | try { 124 | const result = btclib.address.fromBech32(addr) 125 | return { addr, format: 'bech32', hex: '0x' + result.data.toString('hex') } 126 | } catch (e) {} 127 | 128 | try { 129 | const result = btclib.address.fromBase58Check(addr) 130 | return { addr, format: 'base58', hex: '0x' + result.hash.toString('hex') } 131 | } catch (e) {} 132 | } 133 | 134 | export function formatAddress(addr: string) { 135 | return parseAddress(addr)?.addr 136 | } 137 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/ckb/CkbWallet.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, type BigNumberish } from 'ethers' 2 | import { 3 | hd, 4 | helpers, 5 | commons, 6 | utils, 7 | type Script, 8 | } from '@ckb-lumos/lumos' 9 | import CkbAdaptor from './CkbAdaptor' 10 | 11 | export default class CkbWallet extends CkbAdaptor { 12 | readonly #address: string 13 | readonly #privateKey: string 14 | readonly publicKey: string 15 | 16 | protected _pkhPrefix: string 17 | 18 | constructor(adaptor: CkbAdaptor, opt: { address?: string, privateKey?: string }) { 19 | super(adaptor.client) 20 | if (opt.privateKey) { 21 | this._pkhPrefix = '0x000000' 22 | this.#privateKey = opt.privateKey 23 | this.#address = helpers.encodeToAddress({ 24 | codeHash: this.network.SCRIPTS.SECP256K1_BLAKE160.CODE_HASH, 25 | hashType: this.network.SCRIPTS.SECP256K1_BLAKE160.HASH_TYPE, 26 | args: hd.key.publicKeyToBlake160(hd.key.privateToPublic(opt.privateKey)), 27 | }, { config: this.network }) 28 | } else if (opt.address) { 29 | this.#address = opt.address 30 | this._pkhPrefix = this._prefixFromCodeHash(this.lockScript.codeHash) 31 | } 32 | } 33 | 34 | get address(): string { 35 | return this.#address 36 | } 37 | 38 | get lockScript(): Script { 39 | return helpers.parseAddress(this.address, { config: this.network }) 40 | } 41 | 42 | get lockHash(): string { 43 | return utils.computeScriptHash(this.lockScript) 44 | } 45 | 46 | get pkh(): string { 47 | return this.lockScript.args.replace('0x', this._pkhPrefix) 48 | } 49 | 50 | async deploy() {} 51 | 52 | async _getTransferSkeleton({ to, value }) { 53 | const cellProvider = { 54 | collector: query => this.indexer.collector({ type: 'empty', data: '0x', ...query }) 55 | } 56 | 57 | let txSkeleton = helpers.TransactionSkeleton({ cellProvider }) 58 | txSkeleton = await commons.common.transfer( 59 | txSkeleton, 60 | [this.#address], 61 | to, 62 | BigNumber.from(value).toBigInt(), 63 | undefined, 64 | undefined, 65 | { config: this.network }, 66 | ) 67 | 68 | txSkeleton = await commons.common.payFeeByFeeRate( 69 | txSkeleton, 70 | [this.#address], 71 | 1500, 72 | undefined, 73 | { config: this.network }, 74 | ) 75 | 76 | return txSkeleton 77 | } 78 | 79 | async sendTransaction(txObject: helpers.TransactionSkeletonType | { to: string, value: BigNumberish }, ...args: any[]) { 80 | if (!this.#address) { 81 | throw new Error('Cannot sign the transaction. No private key.') 82 | } 83 | let txSkeleton: helpers.TransactionSkeletonType 84 | if (!(txObject instanceof helpers.TransactionSkeleton)) { 85 | txSkeleton = await this._getTransferSkeleton(txObject as { to: string, value: any }) 86 | } else { 87 | txSkeleton = txObject 88 | } 89 | const prepared = commons.common.prepareSigningEntries(txSkeleton, { config: this.network }) 90 | const signatures = prepared.get('signingEntries') 91 | .map(({ message }) => hd.key.signRecoverable(message, this.#privateKey)) 92 | const tx = helpers.sealTransaction(prepared, signatures.toJSON()) 93 | const hash = await this.client.sendTransaction(tx, 'passthrough') 94 | return { 95 | hash, 96 | wait: () => this.waitForTransaction(hash) 97 | } 98 | } 99 | } 100 | 101 | export class CkbWalletFromJoyId extends CkbWallet { 102 | readonly ext: any 103 | 104 | constructor(adaptor: CkbAdaptor, ext) { 105 | super(adaptor, {}) 106 | this.ext = ext 107 | this._pkhPrefix = '0x01' 108 | } 109 | 110 | get address(): string { 111 | return this.ext?.currentAccount?.address 112 | } 113 | 114 | async deploy(): Promise { 115 | throw new Error('Cannot deploy with extention wallet') 116 | } 117 | 118 | async sendTransaction(txSkeleton: helpers.TransactionSkeletonType, witnessIndex: number = 0) { 119 | const converted = this.#convertTxSkeleton(txSkeleton) 120 | const tx = await this.ext?.signRawTransaction(converted, witnessIndex) 121 | const hash = await this.client.sendTransaction(tx, 'passthrough') 122 | return { 123 | hash, 124 | wait: () => this.waitForTransaction(hash) 125 | } 126 | } 127 | 128 | #convertTxSkeleton(txSkeleton: helpers.TransactionSkeletonType) { 129 | return { 130 | version: '0x0', 131 | cellDeps: txSkeleton.cellDeps.toJSON(), 132 | headerDeps: [], 133 | inputs: txSkeleton.inputs.toJSON().map((input, inputIndex) => ({ 134 | previousOutput: input.outPoint, 135 | since: txSkeleton.inputSinces.get(inputIndex) || '0x0' 136 | })), 137 | outputs: txSkeleton.outputs.toJSON().map(output => output.cellOutput), 138 | outputsData: txSkeleton.outputs.toJSON().map(x => x.data), 139 | witnesses: txSkeleton.witnesses.toJSON(), 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/ethers/EthersAdaptor.ts: -------------------------------------------------------------------------------- 1 | import { providers } from 'ethers' 2 | 3 | import { IAdaptor } from '../types' 4 | import extendProvider from './extendProvider' 5 | 6 | export default class EthersAdaptor extends extendProvider(providers.StaticJsonRpcProvider) implements IAdaptor { 7 | constructor(client: providers.JsonRpcProvider) { 8 | super(client as any) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/ethers/extendProvider.ts: -------------------------------------------------------------------------------- 1 | import { providers, errors } from 'ethers' 2 | 3 | import type { IAdaptor, WrappedTransaction } from '../types' 4 | 5 | export type ProviderConstructor = new (...args: any) => providers.StaticJsonRpcProvider 6 | 7 | export default function extendProvider(Provider: ClassProvider) { 8 | return class ProviderAdaptor extends Provider implements IAdaptor { 9 | #client: providers.StaticJsonRpcProvider | any 10 | 11 | readonly #url: string 12 | 13 | constructor(...args: any[]) { 14 | const client: providers.StaticJsonRpcProvider = args[0] 15 | const url = client.connection.url 16 | super({ 17 | url, 18 | timeout: 10_000, 19 | throttleLimit: 3, 20 | throttleCallback: async (attempt: number, url: string) => { 21 | console.log(`[429]`, url, attempt) 22 | return true 23 | } 24 | }, client.network) 25 | 26 | this.#client = client 27 | this.#url = url 28 | } 29 | 30 | get client() { 31 | return this.#client 32 | } 33 | 34 | protected set client(c) { 35 | this.#client = c 36 | } 37 | 38 | get nodeUrl() { 39 | return this.#url 40 | } 41 | 42 | async sendTransaction(signedTransaction: string | Promise): Promise { 43 | return new Promise((resolve, reject) => { 44 | const h = setTimeout(() => { 45 | console.log('tx_timeout') 46 | reject(new Error('Time out')) 47 | }, 10_000) 48 | 49 | super.sendTransaction(signedTransaction).then(res => { 50 | clearTimeout(h) 51 | resolve(res) 52 | }).catch(reject) 53 | }) 54 | } 55 | 56 | async perform(method, params) { 57 | try { 58 | return await super.perform(method, params) 59 | } catch (e) { 60 | if (e.code === errors.CALL_EXCEPTION) { 61 | e.code = errors.SERVER_ERROR 62 | } 63 | throw e 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/ethers/index.ts: -------------------------------------------------------------------------------- 1 | import { ethers, providers, Signer } from 'ethers' 2 | import EthersAdaptor from './EthersAdaptor' 3 | 4 | export function getWallet(privateKey: string, adaptor: EthersAdaptor | providers.JsonRpcProvider, Wallet = ethers.Wallet): ethers.Wallet { 5 | if (!privateKey) { 6 | const wallet = Wallet.createRandom() 7 | return wallet.connect(adaptor) 8 | } 9 | return new Wallet(privateKey, adaptor) 10 | } 11 | 12 | export function getContract(address: string, abi, adaptor: EthersAdaptor | providers.JsonRpcProvider | Signer) { 13 | return new ethers.Contract(address, abi, adaptor) 14 | } 15 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/solana/SolanaWallet.ts: -------------------------------------------------------------------------------- 1 | import { utils } from 'ethers' 2 | import { 3 | Keypair as SolKeypair, 4 | PublicKey as SolPublicKey, 5 | Transaction as SolTransaction, 6 | SystemProgram, 7 | ComputeBudgetProgram, 8 | } from '@solana/web3.js' 9 | import nacl from 'tweetnacl' 10 | import SolanaAdaptor from './SolanaAdaptor' 11 | 12 | export default class SolanaWallet extends SolanaAdaptor { 13 | readonly keypair: SolKeypair 14 | 15 | constructor(adaptor: SolanaAdaptor, keypair: SolKeypair) { 16 | super(adaptor.client) 17 | this.keypair = keypair 18 | } 19 | 20 | get publicKey() { 21 | return this.keypair.publicKey 22 | } 23 | 24 | get address() { 25 | return this.publicKey.toString() 26 | } 27 | 28 | signMessage(msg: string) { 29 | let signData: Uint8Array 30 | if (utils.isHexString(msg)) { 31 | signData = utils.arrayify(msg) 32 | } else { 33 | signData = utils.toUtf8Bytes(msg) 34 | } 35 | const signature = nacl.sign.detached(signData, this.keypair.secretKey) 36 | return utils.hexlify(signature) 37 | } 38 | 39 | async sendTransaction(tx, options: { gasPrice?: number } = { gasPrice: 500_000 }) { 40 | if (!(tx instanceof SolTransaction)) { 41 | tx = new SolTransaction().add( 42 | SystemProgram.transfer({ 43 | fromPubkey: this.publicKey, 44 | toPubkey: new SolPublicKey(tx.to), 45 | lamports: tx.value.toString(), 46 | }) 47 | ) 48 | } 49 | if (options.gasPrice) { 50 | tx.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: options.gasPrice })) 51 | } 52 | const hash = await this.client.sendTransaction(tx, [this.keypair]) 53 | return { 54 | hash, 55 | wait: () => this.waitForTransaction(hash, 1) 56 | } 57 | } 58 | 59 | async deploy(module: string, metadata: string) { 60 | } 61 | } 62 | 63 | export class SolanaExtWallet extends SolanaWallet { 64 | readonly ext: any 65 | 66 | constructor(adaptor: SolanaAdaptor, ext) { 67 | super(adaptor, null) 68 | this.ext = ext 69 | } 70 | 71 | get publicKey() { 72 | return new SolPublicKey(this.address) 73 | } 74 | 75 | get address() { 76 | return this.ext?.currentAccount?.address 77 | } 78 | 79 | async sendTransaction(tx: SolTransaction, options?) { 80 | tx.recentBlockhash = await this.detectNetwork() 81 | tx.feePayer = this.publicKey 82 | const hash = await this.ext.sendTransaction(tx) 83 | return { 84 | hash, 85 | wait: () => this.waitForTransaction(hash, 1) 86 | } 87 | } 88 | 89 | async deploy(): Promise { 90 | throw new Error('Cannot deploy with extention wallet') 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/starknet/StarkAdaptor.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, type providers } from 'ethers' 2 | import { 3 | type RpcProvider as StarkProvider, 4 | Contract as StarkContract, 5 | type RPC, 6 | } from 'starknet' 7 | 8 | import { timer } from '../../utils' 9 | import type { IAdaptor, WrappedTransaction } from '../types' 10 | 11 | import AbiERC20 from './abi/ERC20.json' 12 | import parseCalldata from './parse' 13 | 14 | const ETH_ADDRESS = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7' 15 | 16 | export default class StarkAdaptor implements IAdaptor { 17 | #client: StarkProvider | any 18 | 19 | protected readonly _coreToken: StarkContract 20 | 21 | constructor(client: StarkProvider) { 22 | this.#client = client 23 | this._coreToken = new StarkContract(AbiERC20, ETH_ADDRESS, client) 24 | } 25 | 26 | get client() { 27 | return this.#client 28 | } 29 | 30 | protected set client(c) { 31 | this.#client = c 32 | } 33 | 34 | get nodeUrl() { 35 | return '' 36 | } 37 | 38 | async detectNetwork(): Promise { 39 | return await this.client.getChainId() 40 | } 41 | 42 | async getBlockNumber() { 43 | const result = await this.client.getBlockLatestAccepted() 44 | return result.block_number 45 | } 46 | 47 | async getGasPrice() { 48 | return BigNumber.from(0) 49 | } 50 | 51 | async getTransactionCount(addr: string) { 52 | await this.client.getNonceForAddress(addr) 53 | } 54 | 55 | async getBalance(addr: string) { 56 | const balance = await this._coreToken.balanceOf(addr) 57 | return BigNumber.from(balance) 58 | } 59 | 60 | async getCode(addr: string): Promise { 61 | // TODO 62 | return '' 63 | } 64 | 65 | async getLogs(filter: providers.Filter) { 66 | const eventFilter: RPC.EventFilter = { 67 | from_block: { block_number: Number(filter.fromBlock) }, 68 | to_block: { block_number: Number(filter.toBlock) }, 69 | address: filter.address, 70 | chunk_size: 100, // TODO 71 | } 72 | const result = await this.client.getEvents(eventFilter) 73 | return result.events.map(raw => _wrapStarknetEvent(raw)).reverse() 74 | } 75 | 76 | on () {} 77 | removeAllListeners () {} 78 | 79 | async send(method, params) { 80 | if (method === 'eth_getTransactionByHash') { 81 | return _wrapStarknetTx(await this.client.getTransactionByHash(params[0])) 82 | } else if (method === 'eth_getTransactionReceipt') { 83 | return _wrapStarknetReceipt(await this.client.getTransactionReceipt(params[0])) 84 | } 85 | 86 | if (method === 'eth_getBlockByNumber' || method === 'eth_getBlockByHash') { 87 | if (params[0] === 'n/a') { 88 | return {} 89 | } 90 | if (params[1]) { 91 | return _wrapStarknetBlock(await this.client.getBlockWithTxs(params[0])) 92 | } else { 93 | return _wrapStarknetBlock(await this.client.getBlockWithTxHashes(params[0])) 94 | } 95 | } 96 | } 97 | 98 | async waitForTransaction(hash: string, confirmations?: number, timeout?: number) { 99 | return new Promise((resolve, reject) => { 100 | const tryGetTransaction = async () => { 101 | try { 102 | const receipt = await this.client.getTransactionReceipt(hash) 103 | if (receipt.finality_status) { 104 | clearInterval(h) 105 | resolve(_wrapStarknetReceipt(receipt)) 106 | } 107 | } catch {} 108 | } 109 | const h = setInterval(tryGetTransaction, 5000) 110 | tryGetTransaction() 111 | 112 | if (timeout) { 113 | timer(timeout * 1000).then(() => { 114 | clearInterval(h) 115 | reject(new Error('Time out')) 116 | }) 117 | } 118 | }) 119 | } 120 | } 121 | 122 | function _wrapStarknetEvent(raw) { 123 | console.log(raw) 124 | return raw 125 | } 126 | 127 | function _wrapStarknetBlock(raw: RPC.BlockWithTxs | RPC.BlockWithTxHashes) { 128 | if (!('block_hash' in raw)) { 129 | return 130 | } 131 | return { 132 | hash: raw.block_hash, 133 | parentHash: raw.parent_hash, 134 | number: raw.block_number, 135 | timestamp: raw.timestamp, 136 | transactions: raw.transactions.map(tx => _wrapStarknetTx(tx)).filter(Boolean) 137 | } 138 | } 139 | 140 | function _wrapStarknetTx(raw: RPC.TransactionWithHash) { 141 | if (!raw?.transaction_hash) { 142 | return 143 | } 144 | const { transaction_hash, type, version } = raw 145 | if (type === 'INVOKE' && version === '0x1') { 146 | const { max_fee, sender_address, calldata } = raw 147 | const parsed = parseCalldata(calldata) 148 | return { 149 | blockHash: 'n/a', 150 | hash: transaction_hash, 151 | from: sender_address, 152 | to: parsed[0].to, 153 | value: '0', 154 | input: parsed, 155 | } 156 | } 157 | } 158 | 159 | function _wrapStarknetReceipt(raw: RPC.TransactionReceipt) { 160 | const { 161 | transaction_hash, 162 | actual_fee, 163 | execution_status, 164 | finality_status, 165 | } = raw 166 | if (!finality_status) { 167 | return 168 | } 169 | return { 170 | blockHash: raw['block_hash'], 171 | blockNumber: raw['block_number'], 172 | hash: transaction_hash, 173 | status: execution_status === 'SUCCEEDED' ? '0x1' : '0x0', 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/starknet/StarkWallet.ts: -------------------------------------------------------------------------------- 1 | import { utils } from 'ethers' 2 | import { 3 | Account as StarkAccount, 4 | CallData, 5 | ec, 6 | hash, 7 | } from 'starknet' 8 | import StarkAdaptor from './StarkAdaptor' 9 | 10 | const OZ_ACCOUNT_CLASSHASH = '0x4c6d6cf894f8bc96bb9c525e6853e5483177841f7388f74a46cfda6f028c755' 11 | 12 | export default class StarkWallet extends StarkAdaptor { 13 | readonly #privateKey: string 14 | readonly publicKey: string 15 | readonly #calldata: string[] 16 | readonly address: string 17 | readonly account: StarkAccount 18 | 19 | constructor(adaptor: StarkAdaptor, opt: { privateKey?: string, account?: StarkAccount }) { 20 | super(adaptor.client) 21 | if (opt.privateKey) { 22 | this.#privateKey = opt.privateKey 23 | this.publicKey = ec.starkCurve.getStarkKey(opt.privateKey) 24 | this.#calldata = CallData.compile({ publicKey: this.publicKey }) 25 | this.address = utils.hexZeroPad(hash.calculateContractAddressFromHash( 26 | this.publicKey, 27 | OZ_ACCOUNT_CLASSHASH, 28 | this.#calldata, 29 | 0 30 | ), 32) 31 | this.account = new StarkAccount(adaptor.client, this.address, opt.privateKey) 32 | } else if (opt.account) { 33 | this.account = opt.account 34 | this.address = utils.hexZeroPad(this.account.address, 32) 35 | } 36 | } 37 | 38 | async deploy() { 39 | const tx = await this.account.deployAccount({ 40 | classHash: OZ_ACCOUNT_CLASSHASH, 41 | constructorCalldata: this.#calldata, 42 | addressSalt: this.publicKey, 43 | }) 44 | return { 45 | hash: tx.transaction_hash, 46 | wait: (confirmations: number) => this.waitForTransaction(tx.transaction_hash, confirmations) 47 | } 48 | } 49 | 50 | async transfer({ to, value }) { 51 | this._coreToken.connect(this.account) 52 | const tx = await this._coreToken.transfer(to, value) 53 | this._coreToken.connect(null) 54 | return { 55 | hash: tx.transaction_hash, 56 | wait: (confirmations: number) => this.waitForTransaction(tx.transaction_hash, confirmations) 57 | } 58 | } 59 | 60 | async sendTransaction({ instance, method, args }) { 61 | const tx = await instance[method](...args) 62 | return { 63 | hash: tx.transaction_hash, 64 | wait: (confirmations: number) => this.waitForTransaction(tx.transaction_hash, confirmations) 65 | } 66 | } 67 | 68 | async signMessage(msg: string) { 69 | let signData: Uint8Array 70 | if (utils.isHexString(msg)) { 71 | signData = utils.arrayify(msg) 72 | } else { 73 | signData = utils.toUtf8Bytes(msg) 74 | } 75 | const msgHash = hash.computeHashOnElements([...signData]) 76 | const { r, s, recovery } = ec.starkCurve.sign(msgHash, this.#privateKey) 77 | return utils.splitSignature({ r: utils.hexlify(r), s: utils.hexlify(s), recoveryParam: recovery }).compact 78 | } 79 | } 80 | 81 | export class StarkExtWallet extends StarkWallet { 82 | readonly ext: any 83 | 84 | constructor(adaptor: StarkAdaptor, ext) { 85 | super(adaptor, { account: ext?.connection?.account }) 86 | this.ext = ext 87 | } 88 | 89 | async deploy(): Promise { 90 | throw new Error('Cannot deploy with extention wallet') 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/starknet/parse/index.ts: -------------------------------------------------------------------------------- 1 | import { utils } from 'ethers' 2 | import { CallData } from 'starknet' 3 | import v0 from './v0.json' 4 | import v1 from './v1.json' 5 | 6 | export default function parse(calldata) { 7 | try { 8 | return parse_v0(calldata) 9 | } catch { 10 | return parse_v1(calldata) 11 | } 12 | } 13 | 14 | const parser_v0 = new CallData(v0) 15 | function parse_v0(calldata) { 16 | const result = parser_v0.parse('__execute__', calldata) 17 | const { calls, data } = result as { calls: any[], data: bigint[] } 18 | return calls.map(({ to, selector, offset, len }) => ({ 19 | to: utils.hexZeroPad(to, 32), 20 | selector: utils.hexZeroPad(selector, 32), 21 | data: data.slice(Number(offset), Number(offset) + Number(len)).map(x => utils.hexlify(x)) 22 | })) 23 | } 24 | 25 | const parser_v1 = new CallData(v1) 26 | function parse_v1(calldata) { 27 | const result = parser_v1.parse('__execute__', calldata) 28 | return (result as any).calls.map(({ to, selector, data }) => ({ 29 | to: utils.hexZeroPad(to, 32), 30 | selector: utils.hexZeroPad(selector, 32), 31 | data: data.map(x => utils.hexlify(x)) 32 | })) 33 | } 34 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/starknet/parse/v0.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "members": [ 4 | { 5 | "name": "to", 6 | "offset": 0, 7 | "type": "felt" 8 | }, 9 | { 10 | "name": "selector", 11 | "offset": 1, 12 | "type": "felt" 13 | }, 14 | { 15 | "name": "offset", 16 | "offset": 2, 17 | "type": "felt" 18 | }, 19 | { 20 | "name": "len", 21 | "offset": 3, 22 | "type": "felt" 23 | } 24 | ], 25 | "name": "CallArray", 26 | "size": 4, 27 | "type": "struct" 28 | }, 29 | { 30 | "outputs": [ 31 | { 32 | "name": "calls_len", 33 | "type": "felt" 34 | }, 35 | { 36 | "name": "calls", 37 | "type": "CallArray*" 38 | }, 39 | { 40 | "name": "data_len", 41 | "type": "felt" 42 | }, 43 | { 44 | "name": "data", 45 | "type": "felt*" 46 | } 47 | ], 48 | "name": "__execute__", 49 | "inputs": [], 50 | "type": "function" 51 | } 52 | ] -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/starknet/parse/v1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "members": [ 4 | { 5 | "name": "to", 6 | "offset": 0, 7 | "type": "felt" 8 | }, 9 | { 10 | "name": "selector", 11 | "offset": 1, 12 | "type": "felt" 13 | }, 14 | { 15 | "name": "data", 16 | "offset": 2, 17 | "type": "felt*" 18 | } 19 | ], 20 | "name": "CallArray", 21 | "size": 3, 22 | "type": "struct" 23 | }, 24 | { 25 | "outputs": [ 26 | { 27 | "name": "calls_len", 28 | "type": "felt" 29 | }, 30 | { 31 | "name": "calls", 32 | "type": "CallArray*" 33 | } 34 | ], 35 | "name": "__execute__", 36 | "inputs": [], 37 | "type": "function" 38 | } 39 | ] -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/sui/SuiAdaptor.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, utils } from 'ethers' 2 | import { SuiClient, type SuiTransactionBlockResponse } from '@mysten/sui/client' 3 | 4 | import type { IAdaptor, WrappedTransaction } from '../types' 5 | 6 | const mesonAddress = '0x371a30d40fcc357a37d412c4750a57303d58c9482e5f51886e46f7bf774028f3' 7 | 8 | export default class SuiAdaptor implements IAdaptor { 9 | #client: SuiClient | any 10 | 11 | constructor(client: SuiClient) { 12 | this.#client = client 13 | } 14 | 15 | get client() { 16 | return this.#client 17 | } 18 | 19 | protected set client(c) { 20 | this.#client = c 21 | } 22 | 23 | get nodeUrl() { 24 | // TODO 25 | return '[Sui nodeUrl not available]' 26 | } 27 | 28 | async detectNetwork(): Promise { 29 | return await this.client.getRpcApiVersion() 30 | } 31 | 32 | async getBlockNumber() { 33 | // TODO 34 | return Number(0) 35 | } 36 | 37 | async getGasPrice() { 38 | return BigNumber.from(0) 39 | } 40 | 41 | async getTransactionCount(addr: string) { 42 | } 43 | 44 | async getBalance(addr) { 45 | const data = await this.client.getBalance({ owner: addr }) 46 | return BigNumber.from(data.totalBalance) 47 | } 48 | 49 | async getCode(addr: string): Promise { 50 | // TODO 51 | return '' 52 | } 53 | 54 | async getLogs(filter) { 55 | const data = await this.client.queryTransactionBlocks({ 56 | filter: { InputObject: filter.address }, 57 | order: 'descending', 58 | }) 59 | return data.data.map(({ digest }) => ({ transactionHash: digest })) 60 | } 61 | 62 | on () {} 63 | removeAllListeners () {} 64 | 65 | async send(method, params) { 66 | if (method === 'eth_getBlockByNumber') { 67 | if (params[0] === 'latest') { 68 | return this.#getTransactionBlock(mesonAddress) 69 | } else { 70 | return 71 | } 72 | } else if (method === 'eth_getBlockByHash') { 73 | if (params[0] === 'n/a') { 74 | return {} 75 | } 76 | return this.#getTransactionBlock(mesonAddress, params[0]) 77 | } else if (method === 'eth_getTransactionByHash' || method === 'eth_getTransactionReceipt') { 78 | return await this.waitForTransaction(params[0]) 79 | } 80 | } 81 | 82 | async waitForTransaction(digest: string, confirmations?: number, timeout?: number) { 83 | const txRes = await this.client.getTransactionBlock({ digest, options: { showInput: true } }) 84 | return this._wrapSuiTx(txRes) 85 | } 86 | 87 | async #getTransactionBlock(digest: string, cursor = null) { 88 | const result = await this.client.queryTransactionBlocks({ 89 | filter: { InputObject: digest }, 90 | options: { showInput: true }, 91 | limit: 2, 92 | cursor, 93 | }) 94 | const txbs = result.data || [] 95 | return { 96 | hash: txbs[0]?.digest, 97 | parentHash: result.nextCursor, 98 | number: '', 99 | timestamp: Math.floor(Number(txbs[0]?.timestampMs) / 1000).toString(), 100 | transactions: txbs.map(tx => this._wrapSuiTx(tx)) 101 | } 102 | } 103 | 104 | protected _wrapSuiTx(txRes: SuiTransactionBlockResponse) { 105 | const moveCalls = this.#moveCallsFromTxResponse(txRes) 106 | 107 | return { 108 | blockHash: 'n/a', 109 | blockNumber: '', 110 | hash: txRes.digest, 111 | from: utils.hexZeroPad(txRes.transaction?.data.sender || '0x', 32), 112 | to: moveCalls?.[0]?.package, 113 | value: '0', 114 | input: JSON.stringify(moveCalls), 115 | timestamp: Math.floor(Number(txRes.timestampMs) / 1000).toString(), 116 | status: txRes.errors?.length ? '0x0' : '0x1', 117 | } 118 | } 119 | 120 | #moveCallsFromTxResponse(txRes: SuiTransactionBlockResponse) { 121 | const txData = txRes.transaction?.data.transaction 122 | if (txData?.kind === 'ProgrammableTransaction') { 123 | return txData.transactions.map(tx => { 124 | if ('MoveCall' in tx) { 125 | const { 126 | module, 127 | function: functionName, 128 | arguments: _arguments, 129 | type_arguments: typeArguments, 130 | } = tx.MoveCall 131 | const packageName = utils.hexZeroPad(tx.MoveCall.package || '0x', 32) 132 | return { 133 | package: packageName, 134 | target: `${packageName}::${module}::${functionName}`, 135 | arguments: _arguments.map(arg => { 136 | if (arg !== 'GasCoin' && 'Input' in arg) { 137 | const input = txData.inputs[arg.Input] 138 | if (input.type === 'pure') { 139 | if (input.valueType === 'vector') { 140 | return utils.hexlify(input.value) 141 | } else { 142 | return input.value 143 | } 144 | } else if (input.type === 'object') { 145 | return input.objectId 146 | } 147 | } 148 | }), 149 | typeArguments 150 | } 151 | } 152 | }).filter(Boolean) 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/sui/SuiWallet.ts: -------------------------------------------------------------------------------- 1 | import { utils } from 'ethers' 2 | import { type SuiTransactionBlockResponseOptions } from '@mysten/sui/client' 3 | import { Ed25519Keypair as SuiKeypair } from '@mysten/sui/keypairs/ed25519' 4 | import { Transaction as SuiTransaction } from '@mysten/sui/transactions' 5 | 6 | import SuiAdaptor from './SuiAdaptor' 7 | 8 | export default class SuiWallet extends SuiAdaptor { 9 | readonly keypair: SuiKeypair 10 | 11 | constructor(adaptor: SuiAdaptor, keypair?: SuiKeypair) { 12 | super(adaptor.client) 13 | this.keypair = keypair 14 | } 15 | 16 | get address() { 17 | return this.keypair.getPublicKey().toSuiAddress() 18 | } 19 | 20 | async transfer({ to, value }, options?: SuiTransactionBlockResponseOptions) { 21 | const transaction = new SuiTransaction() 22 | const [coin] = transaction.splitCoins(transaction.gas, [value]) 23 | transaction.transferObjects([coin], to) 24 | const result = await this.client.signAndExecuteTransaction({ signer: this.keypair, transaction, options }) 25 | return { 26 | hash: result.digest, 27 | wait: () => this._wrapSuiTx(result) 28 | } 29 | } 30 | 31 | async signMessage (msg: string) { 32 | const { signature } = await this.keypair.signPersonalMessage(utils.toUtf8Bytes(msg)) 33 | return signature 34 | } 35 | 36 | async sendTransaction(transaction: SuiTransaction, options?: SuiTransactionBlockResponseOptions) { 37 | if (!(transaction instanceof SuiTransaction)) { 38 | return this.transfer(transaction, options) 39 | } 40 | const result = await this.client.signAndExecuteTransaction({ signer: this.keypair, transaction, options }) 41 | return { 42 | hash: result.digest, 43 | wait: () => this._wrapSuiTx(result) 44 | } 45 | } 46 | 47 | _moveCallsFromTx (transaction: SuiTransaction) { 48 | return transaction.blockData.transactions.map(tx => { 49 | if (tx.kind === 'MoveCall') { 50 | return { 51 | ...tx, 52 | arguments: tx.arguments.map(arg => { 53 | if (arg.kind === 'Input') { 54 | if (typeof arg.value === 'number' || typeof arg.value === 'string') { 55 | return arg.value 56 | } else if (Array.isArray(arg.value)) { 57 | return utils.hexlify(arg.value) 58 | } 59 | } 60 | }) 61 | } 62 | } 63 | }).filter(Boolean) 64 | } 65 | 66 | async deploy(module: string, metadata: string) { 67 | throw new Error('Not implemented') 68 | } 69 | } 70 | 71 | export class SuiExtWallet extends SuiWallet { 72 | readonly ext: any 73 | 74 | constructor(adaptor: SuiAdaptor, ext) { 75 | super(adaptor) 76 | this.ext = ext 77 | } 78 | 79 | get address() { 80 | return this.ext.signer.accounts?.[0]?.address as string 81 | } 82 | 83 | async sendTransaction(transaction: SuiTransaction, options?: SuiTransactionBlockResponseOptions) { 84 | const feat = this.ext.signer.features['sui:signAndExecuteTransactionBlock'] 85 | const result = await feat.signAndExecuteTransactionBlock({ 86 | transactionBlock: 87 | transaction, options, 88 | chain: 'sui:mainnet', 89 | account: this.ext.signer.accounts[0] 90 | }) 91 | 92 | return { 93 | hash: result.digest, 94 | wait: () => this._wrapSuiTx(result) 95 | } 96 | } 97 | 98 | async deploy(): Promise { 99 | throw new Error('Cannot deploy with extention wallet') 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/ton/TonAdaptor.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'ethers' 2 | import { Address } from '@ton/core' 3 | import { TonClient } from '@ton/ton' 4 | import { HttpClient, Api as TonApi, Trace } from 'tonapi-sdk-js' 5 | 6 | import { timer } from '../../utils' 7 | import type { IAdaptor, WrappedTransaction } from '../types' 8 | 9 | export default class TonAdaptor implements IAdaptor { 10 | #client: TonClient | any 11 | #tonApi: TonApi 12 | 13 | constructor(client: TonClient) { 14 | this.#client = client 15 | this.#tonApi = new TonApi(new HttpClient({ 16 | baseUrl: (client).metadata.url_tonconsole, 17 | baseApiParams: { 18 | headers: { 19 | Authorization: `Bearer ${process.env.TON_CONSOLE_API}`, 20 | 'Content-type': 'application/json' 21 | } 22 | } 23 | })) 24 | } 25 | 26 | get client() { 27 | return this.#client 28 | } 29 | 30 | get nodeUrl() { 31 | return this.client.parameters.endpoint 32 | } 33 | 34 | async detectNetwork(): Promise { 35 | return (await this.client.getMasterchainInfo()).latestSeqno != 0 36 | } 37 | 38 | async getBlockNumber() { 39 | return (await this.client.getMasterchainInfo()).latestSeqno 40 | } 41 | 42 | async getGasPrice() { 43 | return BigNumber.from(0) // TODO 44 | } 45 | 46 | async getBalance(addr: string) { 47 | const tonAddr = Address.parse(addr) 48 | return BigNumber.from(await this.client.getBalance(tonAddr)) 49 | } 50 | 51 | async getCode(addr) { 52 | return '' // TODO 53 | } 54 | 55 | async getLogs(filter) { 56 | } 57 | 58 | on() { } 59 | removeAllListeners() { } 60 | 61 | async #getTransaction(hash: string) { 62 | return await this.#tonApi.traces.getTrace(hash) 63 | } 64 | 65 | async send(method, params) { 66 | if (method === 'eth_getTransactionByHash') { 67 | return this._wrapTonTx(await this.#getTransaction(params[0])) 68 | } else if (method === 'eth_getTransactionReceipt') { 69 | return this._wrapTonTx(await this.#getTransaction(params[0])) 70 | } 71 | 72 | if (method === 'eth_getBlockByNumber') { 73 | if (params[0] === 'latest') { 74 | // return _wrapTronBlock(await this.client.trx.getCurrentBlock()) 75 | } else { 76 | console.warn(`TonAdaptor: 'eth_getBlockByNumber' unsupported`) 77 | return 78 | } 79 | } else if (method === 'eth_getBlockByHash') { 80 | // return _wrapTronBlock(await this.client.trx.getBlockByHash(params[0])) 81 | } 82 | throw new Error(`TonAdaptor: '${method}' unsupported`) 83 | } 84 | 85 | async waitForTransaction(hash: string, confirmations?: number, timeout?: number): Promise { 86 | return new Promise((resolve, reject) => { 87 | const tryGetTransaction = async () => { 88 | let info: Trace 89 | try { 90 | info = await this.#getTransaction(hash) 91 | } catch {} 92 | if (Object.keys(info || {}).length) { 93 | clearInterval(h) 94 | resolve(this._wrapTonTx(info)) 95 | } 96 | } 97 | const h = setInterval(tryGetTransaction, 3000) 98 | tryGetTransaction() 99 | 100 | if (timeout) { 101 | timer(timeout * 1000).then(() => { 102 | clearInterval(h) 103 | reject(new Error('Time out')) 104 | }) 105 | } 106 | }) 107 | } 108 | 109 | protected _wrapTonTx(tx: Trace) { 110 | const { transaction } = tx 111 | const { decoded_op_name, decoded_body } = transaction.in_msg 112 | 113 | let messageList 114 | if (decoded_op_name === 'wallet_signed_v4') { 115 | messageList = decoded_body.payload.map(a => a.message) 116 | } else if (decoded_op_name === 'wallet_signed_external_v5_r1') { 117 | messageList = decoded_body.actions.map(a => a.msg) 118 | } 119 | 120 | const parsed = messageList?.map(msg => { 121 | switch (msg.sum_type) { 122 | case 'MessageInternal': { 123 | const data = msg.message_internal.body.value 124 | if (data.sum_type === 'JettonTransfer') { 125 | return { 126 | value: data.value.amount, 127 | to: Address.parseRaw(data.value.destination).toString({ bounceable: false }), 128 | } 129 | } 130 | return 131 | } 132 | default: 133 | console.log(msg) 134 | return 135 | } 136 | }) 137 | 138 | return { 139 | blockHash: 'n/a', 140 | blockNumber: '', 141 | block: Number(transaction.block.split(',')[2].replace(')', '')), // (0,e000000000000000,46178664) 142 | hash: transaction.hash, 143 | from: Address.parseRaw(transaction.account.address).toString({ bounceable: false }), 144 | to: parsed.map(v => v.to), 145 | input: parsed.map(v => v.value), 146 | timestamp: transaction.utime.toString(), 147 | status: transaction.success ? '0x1' : '0x0', 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/ton/TonWallet.ts: -------------------------------------------------------------------------------- 1 | // import { ethers } from 'ethers' 2 | import { KeyPair } from '@ton/crypto' 3 | import { beginCell, storeMessage } from '@ton/core' 4 | import { Address, Cell, internal, external, OpenedContract, Sender, WalletContractV4 } from '@ton/ton' 5 | 6 | import TonAdaptor from './TonAdaptor' 7 | 8 | export default class TonWallet extends TonAdaptor { 9 | readonly #publicKey: Buffer 10 | readonly #secretKey: Buffer 11 | readonly #wallet: WalletContractV4 12 | readonly #walletContract: OpenedContract 13 | readonly #walletSender: Sender 14 | readonly #address: Address 15 | 16 | constructor(adaptor: TonAdaptor, keypair?: KeyPair) { 17 | super(adaptor.client) 18 | if (keypair) { 19 | this.#publicKey = keypair.publicKey 20 | this.#secretKey = keypair.secretKey 21 | this.#wallet = WalletContractV4.create({ workchain: 0, publicKey: keypair.publicKey }) 22 | this.#walletContract = adaptor.client.open(this.#wallet) 23 | this.#walletSender = this.#walletContract.sender(keypair.secretKey) 24 | this.#address = this.#wallet.address 25 | } 26 | } 27 | 28 | get pubkey(): Buffer { 29 | return this.#publicKey 30 | } 31 | 32 | get address(): string { 33 | return this.#address.toString({ bounceable: false }) 34 | } 35 | 36 | async transfer(to: string, value: bigint) { 37 | return this.sendTransaction({ to, value }) 38 | } 39 | 40 | async sendTransaction(data: { to: string, value: bigint, body?: Cell, swapId?: string }) { 41 | const seqno = await this.#walletContract.getSeqno() 42 | const transfer = this.#wallet.createTransfer({ 43 | seqno, 44 | secretKey: this.#secretKey, 45 | messages: [internal(data)] 46 | }) 47 | const msg = external({ to: this.#address, init: null, body: transfer }) 48 | const cell = beginCell().store(storeMessage(msg)).endCell() 49 | const hash = cell.hash().toString('hex') 50 | await this.client.sendFile(cell.toBoc()) 51 | return { 52 | hash, 53 | wait: (_: number) => this.waitForTransaction(hash), 54 | } 55 | } 56 | } 57 | 58 | export class TonExtWallet extends TonWallet { 59 | readonly ext: any 60 | 61 | constructor(adaptor: TonAdaptor, ext) { 62 | super(adaptor) 63 | this.ext = ext 64 | } 65 | 66 | get address() { 67 | return this.ext?.currentAccount?.address 68 | } 69 | 70 | async sendTransaction(data: { to: string, value: bigint, body?: Cell, swapId?: string }) { 71 | const submitTs = Math.floor(Date.now() / 1e3) 72 | const hash = await this.ext?.sendTransaction({ 73 | validUntil: submitTs + 120, 74 | messages: [ 75 | { 76 | address: data.to.toString(), 77 | amount: data.value.toString(), 78 | payload: data.body?.toBoc().toString('base64'), 79 | } 80 | ] 81 | }) 82 | return { 83 | hash, 84 | wait: () => this.waitForTransaction(hash), 85 | } 86 | } 87 | 88 | async deploy(): Promise { 89 | throw new Error('Cannot deploy with extention wallet') 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/ton/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Address, Builder, Cell } from "@ton/core"; 2 | 3 | export type TokenTransfer = { 4 | $$type: 'TokenTransfer'; 5 | query_id: bigint; 6 | amount: bigint; 7 | destination: Address; 8 | response_destination: Address | null; 9 | custom_payload: Cell | null; 10 | forward_ton_amount: bigint; 11 | forward_payload: Cell; 12 | } 13 | 14 | export function storeTokenTransfer(src: TokenTransfer) { 15 | return (builder: Builder) => { 16 | let b_0 = builder; 17 | b_0.storeUint(260734629, 32); 18 | b_0.storeUint(src.query_id, 64); 19 | b_0.storeCoins(src.amount); 20 | b_0.storeAddress(src.destination); 21 | b_0.storeAddress(src.response_destination); 22 | if (src.custom_payload !== null && src.custom_payload !== undefined) { b_0.storeBit(true).storeRef(src.custom_payload); } else { b_0.storeBit(false); } 23 | b_0.storeCoins(src.forward_ton_amount); 24 | b_0.storeBuilder(src.forward_payload.asBuilder()); 25 | }; 26 | } 27 | 28 | export type ProxyTokenTransfer = { 29 | $$type: 'ProxyTokenTransfer'; 30 | wallet_address: Address; 31 | token_transfer: TokenTransfer; 32 | } 33 | 34 | export function storeProxyTokenTransfer(src: ProxyTokenTransfer) { 35 | return (builder: Builder) => { 36 | let b_0 = builder; 37 | b_0.storeUint(3761706239, 32); 38 | b_0.storeAddress(src.wallet_address); 39 | let b_1 = new Builder(); 40 | b_1.store(storeTokenTransfer(src.token_transfer)); 41 | b_0.storeRef(b_1.endCell()); 42 | }; 43 | } 44 | 45 | export type ProxyTokenTransferWithSwapid = { 46 | $$type: 'ProxyTokenTransferWithSwapid'; 47 | swapid: bigint; 48 | wallet_address: Address; 49 | token_transfer: TokenTransfer; 50 | } 51 | 52 | export function storeProxyTokenTransferWithSwapid(src: ProxyTokenTransferWithSwapid) { 53 | return (builder: Builder) => { 54 | let b_0 = builder; 55 | b_0.storeUint(2737718375, 32); 56 | b_0.storeUint(src.swapid, 256); 57 | b_0.storeAddress(src.wallet_address); 58 | let b_1 = new Builder(); 59 | b_1.store(storeTokenTransfer(src.token_transfer)); 60 | b_0.storeRef(b_1.endCell()); 61 | }; 62 | } 63 | 64 | export type ModifySupportToken = { 65 | $$type: 'ModifySupportToken'; 66 | available: boolean; 67 | token_index: bigint; 68 | token_master_address: Address; 69 | meson_wallet_address: Address; 70 | } 71 | 72 | export function storeModifySupportToken(src: ModifySupportToken) { 73 | return (builder: Builder) => { 74 | let b_0 = builder; 75 | b_0.storeUint(613687068, 32); 76 | b_0.storeBit(src.available); 77 | b_0.storeUint(src.token_index, 8); 78 | b_0.storeAddress(src.token_master_address); 79 | b_0.storeAddress(src.meson_wallet_address); 80 | }; 81 | } -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/tron/TronAdaptor.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, providers } from 'ethers' 2 | import TronWeb from 'tronweb' 3 | 4 | import { timer } from '../../utils' 5 | import type { IAdaptor, WrappedTransaction } from '../types' 6 | 7 | export default class TronAdaptor implements IAdaptor { 8 | #client: any 9 | 10 | constructor(client) { 11 | this.#client = client 12 | } 13 | 14 | get client() { 15 | return this.#client 16 | } 17 | 18 | protected set client(c) { 19 | this.#client = c 20 | } 21 | 22 | get nodeUrl() { 23 | return this.client.fullNode.host 24 | } 25 | 26 | get rpcUrl() { 27 | let url = this.nodeUrl 28 | if (url.includes('quiknode')) { 29 | return url + '/jsonrpc' 30 | } else if (url.includes('ankr')) { 31 | return url.replace('premium-http/tron', 'tron_jsonrpc') 32 | } 33 | } 34 | 35 | async detectNetwork(): Promise { 36 | return this.client.fullNode.isConnected() 37 | } 38 | 39 | async getBlockNumber() { 40 | const latestBlock = await this.client.trx.getCurrentBlock() 41 | return latestBlock?.block_header?.raw_data?.number 42 | } 43 | 44 | async getGasPrice() { 45 | return BigNumber.from(420_000_000_000_000) 46 | } 47 | 48 | async getTransactionCount(addr: string) { 49 | } 50 | 51 | async getBalance(addr) { 52 | return BigNumber.from(await this.client.trx.getBalance(addr)) 53 | } 54 | 55 | async getCode(addr: string): Promise { 56 | const account = await this.client.trx.getAccount(addr) 57 | return account.type === 'Contract' ? '0x1' : '0x' 58 | } 59 | 60 | async getLogs(filter) { 61 | const provider = new providers.JsonRpcProvider(this.rpcUrl) 62 | return await provider.getLogs({ 63 | ...filter, 64 | address: TronWeb.address.toHex(filter.address).replace(/^41/, '0x') 65 | }) 66 | } 67 | 68 | on () {} 69 | removeAllListeners () {} 70 | 71 | async send(method, params) { 72 | if (method === 'eth_getBlockByNumber') { 73 | if (params[0] === 'latest') { 74 | return _wrapTronBlock(await this.client.trx.getCurrentBlock()) 75 | } else { 76 | console.warn(`TronAdaptor: 'eth_getBlockByNumber' unsupported`) 77 | return 78 | } 79 | } else if (method === 'eth_getBlockByHash') { 80 | return _wrapTronBlock(await this.client.trx.getBlockByHash(params[0])) 81 | } else if (method === 'eth_getTransactionByHash') { 82 | return _wrapTronTx(await this.client.trx.getTransaction(params[0])) 83 | } else if (method === 'eth_getTransactionReceipt') { 84 | return _wrapTronReceipt(await this.client.trx.getUnconfirmedTransactionInfo(params[0])) 85 | } 86 | throw new Error(`TronAdaptor: '${method}' unsupported`) 87 | } 88 | 89 | async waitForTransaction(hash: string, confirmations?: number, timeout?: number) { 90 | return new Promise((resolve, reject) => { 91 | const tryGetTransaction = async () => { 92 | let info 93 | try { 94 | if (confirmations) { 95 | info = await this.client.trx.getTransactionInfo(hash) 96 | } else { 97 | info = await this.client.trx.getUnconfirmedTransactionInfo(hash) 98 | } 99 | } catch {} 100 | if (Object.keys(info || {}).length) { 101 | clearInterval(h) 102 | resolve(_wrapTronReceipt(info)) 103 | } 104 | } 105 | const h = setInterval(tryGetTransaction, 3000) 106 | tryGetTransaction() 107 | 108 | if (timeout) { 109 | timer(timeout * 1000).then(() => { 110 | clearInterval(h) 111 | reject(new Error('Time out')) 112 | }) 113 | } 114 | }) 115 | } 116 | } 117 | 118 | function _wrapTronBlock(raw) { 119 | return { 120 | hash: raw.blockID, 121 | parentHash: raw.block_header.raw_data.parentHash, 122 | number: raw.block_header.raw_data.number, 123 | timestamp: Math.floor(raw.block_header.raw_data.timestamp / 1000).toString(), 124 | transactions: raw.transactions?.map(_wrapTronTx) || [] 125 | } 126 | } 127 | 128 | function _wrapTronEvent(raw) { 129 | const { block, timestamp, contract, name, transaction } = raw 130 | return { 131 | blockNumber: block, 132 | address: contract, 133 | name, 134 | transactionHash: transaction, 135 | } 136 | } 137 | 138 | function _wrapTronTx(raw) { 139 | const { 140 | ref_block_hash: blockHash, 141 | timestamp, 142 | } = raw.raw_data || {} 143 | const { 144 | owner_address: from, // hex 145 | contract_address: to, // hex 146 | data, 147 | } = raw.raw_data?.contract[0]?.parameter?.value || {} 148 | 149 | return { 150 | blockHash: '', 151 | blockNumber: '', 152 | hash: raw.txID, 153 | from: TronWeb.address.fromHex(from), 154 | to: TronWeb.address.fromHex(to), 155 | value: '0', 156 | input: `0x${data}`, 157 | timestamp: Math.floor(timestamp / 1000).toString(), 158 | ...raw, 159 | } 160 | } 161 | 162 | function _wrapTronReceipt(raw) { 163 | const { energy_usage, energy_usage_total, net_usage } = raw.receipt 164 | return { 165 | blockHash: '', 166 | status: raw.receipt?.result === 'SUCCESS' ? '1' : '0', 167 | blockNumber: raw.blockNumber, 168 | timestamp: Math.floor(raw.blockTimeStamp / 1000).toString(), 169 | fee: raw.fee || 0, 170 | logs: raw.log?.map(log => ({ 171 | address: TronWeb.address.fromHex(`0x${log.address}`), 172 | topics: log.topics.map(topic => `0x${topic}`), 173 | data: `0x${log.data || ''}` 174 | })), 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/tron/TronContract.ts: -------------------------------------------------------------------------------- 1 | import TronWeb from 'tronweb' 2 | import { utils } from 'ethers' 3 | 4 | import TronAdaptor from './TronAdaptor' 5 | import TronWallet from './TronWallet' 6 | 7 | export default class TronContract { 8 | readonly #adaptor: TronAdaptor 9 | readonly #contract 10 | 11 | constructor(address: string, abi, adaptor: TronAdaptor) { 12 | this.#adaptor = adaptor 13 | this.#contract = this.#adaptor.client.contract(abi, address) 14 | 15 | Object.entries(this.#contract.methodInstances) 16 | .forEach(([name, { abi }]: [string, { abi: any }]) => { 17 | if (abi?.type === 'function') { 18 | if (['view', 'pure'].includes(abi.stateMutability)) { 19 | Object.defineProperty(this, name, { 20 | enumerable: true, 21 | value: (...args) => this._read(name, abi, args), 22 | writable: false, 23 | }) 24 | } else { 25 | Object.defineProperty(this, name, { 26 | enumerable: true, 27 | value: (...args) => this._write(name, abi, args), 28 | writable: false, 29 | }) 30 | } 31 | } 32 | }) 33 | } 34 | 35 | get address() { 36 | return TronWeb.address.fromHex(this.#contract.address) 37 | } 38 | 39 | get provider() { 40 | return this.#adaptor 41 | } 42 | 43 | get signer() { 44 | if (this.#adaptor instanceof TronWallet) { 45 | return this.#adaptor 46 | } 47 | throw new Error(`TronWeb instance doesn't have a signer.`) 48 | } 49 | 50 | get interface() { 51 | return new utils.Interface(this.#contract.abi) 52 | } 53 | 54 | get filters() { 55 | throw new Error('TronContract.filters not implemented') 56 | } 57 | 58 | connect(providerOrTronWeb: TronAdaptor | TronWeb) { 59 | return new TronContract(this.address, this.#contract.abi, providerOrTronWeb) 60 | } 61 | 62 | queryFilter() {} 63 | on() {} 64 | removeAllListeners() {} 65 | 66 | async _read(name: string, abi, args: any[]) { 67 | let overrides 68 | if (args.length > abi.inputs.length) { 69 | overrides = args.pop() 70 | } 71 | return await this.#contract[name](...args).call(overrides) 72 | } 73 | 74 | async _write(name: string, abi, args: any[]) { 75 | let overrides 76 | if (args.length > abi.inputs.length) { 77 | overrides = args.pop() 78 | } 79 | return await (this.#adaptor as TronWallet).sendTransaction({ 80 | contract: this.#contract, 81 | method: name, 82 | args, 83 | }, overrides) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/tron/TronWallet.ts: -------------------------------------------------------------------------------- 1 | import TronAdaptor from './TronAdaptor' 2 | 3 | export default class TronWallet extends TronAdaptor { 4 | constructor(adaptor: TronAdaptor) { 5 | super(adaptor.client) 6 | } 7 | 8 | get tronWeb () { 9 | return this.client 10 | } 11 | 12 | get address() { 13 | return this.tronWeb.defaultAddress.base58 14 | } 15 | 16 | async transfer({ to, value }) { 17 | const tx = await this.tronWeb.transactionBuilder.sendTrx(to, value.toString()) 18 | const signed = await this.tronWeb.trx.sign(tx) 19 | const receipt = await this.tronWeb.trx.sendRawTransaction(signed) 20 | return { 21 | hash: receipt.txID, 22 | wait: (confirmations: number) => this.waitForTransaction(receipt.txID, confirmations) 23 | } 24 | } 25 | 26 | async sendTransaction(payload, overrides) { 27 | if (!payload.contract) { 28 | return await this.transfer(payload) 29 | } 30 | const { contract, method, args } = payload 31 | const hash = await contract[method](...args).send(overrides) 32 | return { 33 | hash, 34 | wait: (confirmations: number) => this.waitForTransaction(hash, confirmations) 35 | } 36 | } 37 | 38 | async deploy() {} 39 | 40 | async getTransaction(hash: string): Promise { 41 | while (true) { 42 | try { 43 | return await this.send('eth_getTransactionByHash', [hash]) 44 | } catch { 45 | await new Promise(resolve => setTimeout(resolve, 1000)) 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/tron/index.ts: -------------------------------------------------------------------------------- 1 | import TronWeb from 'tronweb' 2 | 3 | import TronAdaptor from './TronAdaptor' 4 | import TronWallet from './TronWallet' 5 | import TronContract from './TronContract' 6 | import { FailoverTronAdaptor } from '../FailoverAdaptors' 7 | 8 | export function getWallet(privateKey: string, adaptor: TronAdaptor, Wallet = TronWallet): TronWallet { 9 | if (privateKey.startsWith('0x')) { 10 | privateKey = privateKey.substring(2) 11 | } 12 | let injectedAdaptor 13 | if (adaptor['adaptors']) { 14 | injectedAdaptor = new FailoverTronAdaptor(adaptor['adaptors'].map(adp => _injectPrivateKey(privateKey, adp))) 15 | } else { 16 | injectedAdaptor = _injectPrivateKey(privateKey, adaptor) 17 | } 18 | return new Wallet(injectedAdaptor) 19 | } 20 | 21 | function _injectPrivateKey(privateKey: string, adaptor: TronAdaptor): TronAdaptor { 22 | return new TronAdaptor(new TronWeb({ fullHost: adaptor.client.fullNode.host, privateKey })) 23 | } 24 | 25 | export function getWalletFromExtension(adaptor: TronAdaptor): TronWallet { 26 | return new TronWallet(adaptor) 27 | } 28 | 29 | export function getContract(address: string, abi, adaptor: TronAdaptor) { 30 | return new TronContract(address, abi, adaptor) 31 | } 32 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/types.ts: -------------------------------------------------------------------------------- 1 | import { type BigNumber } from 'ethers' 2 | 3 | export type WrappedTransaction = { 4 | blockHash: string 5 | status?: number | string 6 | } 7 | 8 | export interface IAdaptor { 9 | get client() 10 | set client(c: any) 11 | get nodeUrl(): string 12 | detectNetwork(): Promise 13 | getBlockNumber(): Promise 14 | getGasPrice(): Promise 15 | getBalance(addr: string): Promise 16 | getCode(addr: string): Promise 17 | // getTransactionCount(addr: string): Promise 18 | getLogs(filter: any): Promise 19 | // on(): any 20 | // removeAllListeners(): any 21 | send(method: string, params: any[]): Promise 22 | waitForTransaction(hash: string, confirmations?: number, timeout?: number): Promise 23 | } 24 | 25 | export type AdaptorConstructor = new (...args: any) => IAdaptor 26 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/zksync/ZksyncAdaptor.ts: -------------------------------------------------------------------------------- 1 | import { Provider as ZkProvider } from 'zksync-web3' 2 | 3 | import { IAdaptor } from '../types' 4 | import extendProvider from '../ethers/extendProvider' 5 | 6 | export default class ZksyncAdaptor extends extendProvider(ZkProvider) implements IAdaptor { 7 | constructor(client: ZkProvider) { 8 | super(client as any) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/adaptors/zksync/index.ts: -------------------------------------------------------------------------------- 1 | import { Wallet as ZkWallet, Contract as ZkContract } from 'zksync-web3' 2 | import ZksyncAdaptor from './ZksyncAdaptor' 3 | 4 | export function getWallet(privateKey: string, adaptor: ZksyncAdaptor, Wallet = ZkWallet): ZkWallet { 5 | return new Wallet(privateKey, adaptor) 6 | } 7 | 8 | export function getContract(address: string, abi, wallet: ZksyncAdaptor | ZkWallet) { 9 | return new ZkContract(address, abi, wallet) 10 | } 11 | -------------------------------------------------------------------------------- /packages/sdk/src/index.ts: -------------------------------------------------------------------------------- 1 | export { MesonClient, PartialSwapData, PostedSwapStatus, LockedSwapStatus } from './MesonClient' 2 | export { Block, Transaction, Receipt } from './Rpc' 3 | export { Swap, SwapData } from './Swap' 4 | export { SwapSigner, EthersWalletSwapSigner, RemoteSwapSigner, NonEcdsaRemoteSwapSigner } from './SwapSigner' 5 | export { SwapWithSigner } from './SwapWithSigner' 6 | export { SignedSwapRequest, SignedSwapRequestData, SignedSwapRelease, SignedSwapReleaseData } from './SignedSwap' 7 | export * as adaptors from './adaptors' 8 | export { IAdaptor } from './adaptors/types' 9 | 10 | export * as stark from 'starknet' -------------------------------------------------------------------------------- /packages/sdk/src/utils.ts: -------------------------------------------------------------------------------- 1 | import {BigNumber, type BigNumberish, utils} from 'ethers'; 2 | import TronWeb from 'tronweb' 3 | 4 | export function timer(ms: number): Promise { 5 | return new Promise((resolve) => { 6 | const h = setTimeout(() => { 7 | clearTimeout(h) 8 | resolve() 9 | }, ms) 10 | }) 11 | } 12 | 13 | export function timeout(promise: Promise, ms: number): Promise { 14 | return Promise.race([ 15 | promise, 16 | timer(ms).then(() => { 17 | throw new Error('Time out') 18 | }) 19 | ]) 20 | } 21 | 22 | export function getSwapId(encoded: string, initiator: string) { 23 | const packed = utils.solidityPack(['bytes32', 'address'], [encoded, initiator]) 24 | return utils.keccak256(packed) 25 | } 26 | 27 | 28 | export function fromSwapValue(value: BigNumberish) { 29 | return utils.formatUnits(value || '0', 6).replace(/\.0*$/, '') 30 | } 31 | 32 | export function toSwapValue(amount: string) { 33 | return utils.parseUnits(amount || '0', 6) 34 | } 35 | 36 | export function subValue(v1 = '0', v2 = '0') { 37 | return BigNumber.from(v1).sub(v2) 38 | } 39 | 40 | export function initiatorFromAddress(address: string) { 41 | return `0x${TronWeb.address.toHex(address).substring(2)}` 42 | } 43 | 44 | export {tokenType, tokenCategory} from '@mesonfi/config'; 45 | -------------------------------------------------------------------------------- /packages/sdk/tests/SignedSwap.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | 3 | import { SignedSwapRequest, SignedSwapRelease } from '../src' 4 | import { signedSwapRequestData, signedSwapReleaseData } from './shared' 5 | 6 | describe('SignedSwapRequest', () => { 7 | const signedRequest = new SignedSwapRequest(signedSwapRequestData) 8 | 9 | describe('#constructor', () => { 10 | it('rejects if missing encoded', () => { 11 | const cloned = { ...signedSwapRequestData, encoded: '' } 12 | expect(() => new SignedSwapRequest(cloned)).to.throw('Missing encoded') 13 | }) 14 | it('rejects if missing initiator', () => { 15 | const cloned = { ...signedSwapRequestData, initiator: '' } 16 | expect(() => new SignedSwapRequest(cloned)).to.throw('Missing initiator') 17 | }) 18 | it('rejects if missing signature', () => { 19 | const cloned = { ...signedSwapRequestData, signature: undefined } 20 | expect(() => new SignedSwapRequest(cloned)).to.throw('Missing signature') 21 | }) 22 | it('creates a SignedSwapRequest', () => { 23 | const signedRequest = new SignedSwapRequest(signedSwapRequestData) 24 | expect(signedRequest.encoded).to.equal(signedSwapRequestData.encoded) 25 | expect(signedRequest.initiator).to.equal(signedSwapRequestData.initiator.toLowerCase()) 26 | expect(signedRequest.signature).to.equal(signedSwapRequestData.signature) 27 | }) 28 | }) 29 | 30 | describe('#getDigest', () => { 31 | it('generates digest for testnet & mainnet', () => { 32 | const signedRequest2 = new SignedSwapRequest({ ...signedSwapRequestData, testnet: false }) 33 | expect(signedRequest.getDigest()).to.equal(signedSwapRequestData.digest) 34 | expect(signedRequest2.getDigest()).to.equal(signedSwapRequestData.mainnetDigest) 35 | }) 36 | }) 37 | 38 | describe('#checkSignature', () => { 39 | it('validates the signature for testnet', () => { 40 | expect(signedRequest.checkSignature()).to.be.undefined 41 | }) 42 | it('rejects the same signature for mainnet', () => { 43 | const signedRequest = new SignedSwapRequest({ ...signedSwapRequestData, testnet: false }) 44 | expect(() => signedRequest.checkSignature()).to.throw('Invalid signature') 45 | }) 46 | it('accepcts the mainnet signature for mainnet', () => { 47 | const cloned = { ...signedSwapRequestData, signature: signedSwapRequestData.mainnetSignature, testnet: false } 48 | const signedRequest = new SignedSwapRequest(cloned) 49 | expect(signedRequest.checkSignature()).to.be.undefined 50 | }) 51 | }) 52 | 53 | describe('#toObject', () => { 54 | it('exports the signedRequest as an object', () => { 55 | const signedRequestObject = signedRequest.toObject() 56 | expect(signedRequestObject.encoded).to.equal(signedRequest.encoded) 57 | expect(signedRequestObject.initiator).to.equal(signedRequest.initiator) 58 | expect(signedRequestObject.signature).to.deep.equal(signedRequest.signature) 59 | }) 60 | }) 61 | }) 62 | 63 | describe('SignedSwapRelease', () => { 64 | const signedRelease = new SignedSwapRelease(signedSwapReleaseData) 65 | 66 | describe('#constructor', () => { 67 | it('rejects if missing recipient', () => { 68 | const cloned = { ...signedSwapReleaseData, recipient: undefined } 69 | expect(() => new SignedSwapRelease(cloned)).to.throw('Missing recipient') 70 | }) 71 | it('creates a SignedSwapRelease', () => { 72 | const signedRelease = new SignedSwapRelease(signedSwapReleaseData) 73 | expect(signedRelease.encoded).to.equal(signedSwapReleaseData.encoded) 74 | expect(signedRelease.initiator).to.equal(signedSwapReleaseData.initiator.toLowerCase()) 75 | expect(signedRelease.signature).to.equal(signedSwapReleaseData.signature) 76 | expect(signedRelease.recipient).to.equal(signedSwapReleaseData.recipient) 77 | }) 78 | }) 79 | 80 | describe('#getDigest', () => { 81 | it('generates digest for testnet & mainnet', () => { 82 | expect(signedRelease.getDigest()).to.equal(signedSwapReleaseData.digest) 83 | const signedRelease2 = new SignedSwapRelease({ ...signedSwapReleaseData, testnet: false }) 84 | expect(signedRelease2.getDigest()).to.equal(signedSwapReleaseData.mainnetDigest) 85 | }) 86 | }) 87 | 88 | describe('#checkSignature', () => { 89 | it('validates the signature for testnet', () => { 90 | expect(signedRelease.checkSignature()).to.be.undefined 91 | }) 92 | it('rejects the same signature for mainnet', () => { 93 | const signedRelease = new SignedSwapRequest({ ...signedSwapRequestData, testnet: false }) 94 | expect(() => signedRelease.checkSignature()).to.throw('Invalid signature') 95 | }) 96 | it('accepcts the mainnet signature for mainnet', () => { 97 | const cloned = { ...signedSwapReleaseData, signature: signedSwapReleaseData.mainnetSignature, testnet: false } 98 | const signedRelease = new SignedSwapRelease(cloned) 99 | expect(signedRelease.checkSignature()).to.be.undefined 100 | }) 101 | }) 102 | 103 | describe('#toObject', () => { 104 | it('exports the signedRelease as an object', () => { 105 | const signedReleaseObject = signedRelease.toObject() 106 | expect(signedReleaseObject.encoded).to.equal(signedRelease.encoded) 107 | expect(signedReleaseObject.initiator).to.equal(signedRelease.initiator) 108 | expect(signedReleaseObject.signature).to.deep.equal(signedRelease.signature) 109 | expect(signedReleaseObject.recipient).to.equal(signedRelease.recipient) 110 | }) 111 | }) 112 | }) -------------------------------------------------------------------------------- /packages/sdk/tests/Swap.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { BigNumber } from 'ethers' 3 | 4 | import { Swap } from '../src' 5 | import { getSwap } from './shared' 6 | 7 | describe('Swap', () => { 8 | describe('#constructor', () => { 9 | it('rejects invalid amount', () => { 10 | expect(() => new Swap(getSwap({ amount: 'x' }))).to.throw('Invalid amount') 11 | }) 12 | it('rejects non-positive amount', () => { 13 | expect(() => new Swap(getSwap({ amount: 0 }))).to.throw('Amount must be positive') 14 | expect(() => new Swap(getSwap({ amount: -1 }))).to.throw('Amount must be positive') 15 | }) 16 | it('rejects invalid fee', () => { 17 | expect(() => new Swap(getSwap({ fee: 'x' }))).to.throw('Invalid fee') 18 | }) 19 | it('rejects negative fee', () => { 20 | expect(() => new Swap(getSwap({ fee: -1 }))).to.throw('Fee must be non-negative') 21 | }) 22 | it('rejects if missing expireTs', () => { 23 | expect(() => new Swap(getSwap({ expireTs: 0 }))).to.throw('Missing expireTs') 24 | }) 25 | it('rejects if missing inChain', () => { 26 | expect(() => new Swap(getSwap({ inChain: '' }))).to.throw('Missing inChain') 27 | }) 28 | it('rejects if inToken is not a number', () => { 29 | expect(() => new Swap(getSwap({ inToken: '' }))).to.throw('Invalid inToken') 30 | }) 31 | it('rejects if missing outChain', () => { 32 | expect(() => new Swap(getSwap({ outChain: '' }))).to.throw('Missing outChain') 33 | }) 34 | it('rejects if outToken is not a number', () => { 35 | expect(() => new Swap(getSwap({ outToken: '' }))).to.throw('Invalid outToken') 36 | }) 37 | 38 | it('creates a swap if all parameters are correct', () => { 39 | const swapData = getSwap() 40 | const swap = new Swap(swapData) 41 | expect(swap.amount).to.equal(swapData.amount) 42 | expect(swap.salt).to.be.a('string') 43 | expect(swap.fee).to.equal(swapData.fee) 44 | expect(swap.expireTs).to.equal(swapData.expireTs) 45 | expect(swap.inChain).to.equal(swapData.inChain) 46 | expect(swap.inToken).to.equal(swapData.inToken) 47 | expect(swap.outChain).to.equal(swapData.outChain) 48 | expect(swap.outToken).to.equal(swapData.outToken) 49 | }) 50 | }) 51 | 52 | describe('#Swap.decode', () => { 53 | it('rejects invalid encoded', () => { 54 | expect(() => Swap.decode('')).to.throw('encoded swap should be a hex string of length 66') 55 | }) 56 | 57 | const swapData = getSwap() 58 | const swap = new Swap(swapData) 59 | it('decodes a valid hex string', () => { 60 | const decodedSwap = Swap.decode(swap.encoded) 61 | expect(decodedSwap.version).to.equal(swap.version) 62 | expect(decodedSwap.amount).to.equal(swap.amount) 63 | expect(decodedSwap.salt).to.equal(swap.salt) 64 | expect(decodedSwap.fee).to.equal(swap.fee) 65 | expect(decodedSwap.expireTs).to.equal(swap.expireTs) 66 | expect(decodedSwap.inChain).to.equal(swap.inChain) 67 | expect(decodedSwap.inToken).to.equal(swap.inToken) 68 | expect(decodedSwap.outChain).to.equal(swap.outChain) 69 | expect(decodedSwap.outToken).to.equal(swap.outToken) 70 | }) 71 | 72 | it('decodes a valid BigNumber', () => { 73 | const decodedSwap = Swap.decode(BigNumber.from(swap.encoded)) 74 | expect(decodedSwap.amount).to.equal(swap.amount) 75 | expect(decodedSwap.salt).to.equal(swap.salt) 76 | expect(decodedSwap.fee).to.equal(swap.fee) 77 | expect(decodedSwap.expireTs).to.equal(swap.expireTs) 78 | expect(decodedSwap.inChain).to.equal(swap.inChain) 79 | expect(decodedSwap.inToken).to.equal(swap.inToken) 80 | expect(decodedSwap.outChain).to.equal(swap.outChain) 81 | expect(decodedSwap.outToken).to.equal(swap.outToken) 82 | }) 83 | }) 84 | 85 | describe('#toObject', () => { 86 | const swapData = getSwap() 87 | const swap = new Swap(swapData) 88 | it('exports the swap as an object', () => { 89 | const swapObject = swap.toObject() 90 | expect(swapObject.encoded).to.equal(swap.encoded) 91 | expect(swapObject.amount).to.equal(swap.amount) 92 | expect(swapObject.salt).to.equal(swap.salt) 93 | expect(swapObject.fee).to.equal(swap.fee) 94 | expect(swapObject.expireTs).to.equal(swap.expireTs) 95 | expect(swapObject.inChain).to.equal(swap.inChain) 96 | expect(swapObject.inToken).to.equal(swap.inToken) 97 | expect(swapObject.outChain).to.equal(swap.outChain) 98 | expect(swapObject.outToken).to.equal(swap.outToken) 99 | }) 100 | }) 101 | }) -------------------------------------------------------------------------------- /packages/sdk/tests/SwapSigner.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { MockProvider } from 'ethereum-waffle'; 3 | import { utils } from 'ethers' 4 | 5 | import { SwapSigner, EthersWalletSwapSigner, RemoteSwapSigner } from '../src' 6 | import { signedSwapRequestData, signedSwapReleaseData } from './shared' 7 | 8 | describe('SwapSigner', () => { 9 | const swapSigner = new SwapSigner() 10 | const encoded = signedSwapReleaseData.encoded 11 | const recipient = signedSwapReleaseData.recipient 12 | 13 | describe('#getAddress', () => { 14 | it('rejects by "Not implemented"', () => { 15 | expect(() => swapSigner.getAddress()).to.throw('Not implemented') 16 | }) 17 | }) 18 | 19 | describe('#signSwapRequest', () => { 20 | it('rejects by "Not implemented"', async () => { 21 | await expect(swapSigner.signSwapRequest(encoded)).to.be.rejectedWith('Not implemented') 22 | }) 23 | }) 24 | 25 | describe('#signSwapRelease', () => { 26 | it('rejects by "Not implemented"', async () => { 27 | await expect(swapSigner.signSwapRelease(encoded, recipient)).to.be.rejectedWith('Not implemented') 28 | }) 29 | }) 30 | 31 | describe('#SwapSigner.hashRequest', () => { 32 | it('hashes a swap request for testnet', () => { 33 | expect(SwapSigner.hashRequest(encoded, true)).to.equal(signedSwapRequestData.digest) 34 | }) 35 | 36 | it('hashes a swap request for mainnet', () => { 37 | expect(SwapSigner.hashRequest(encoded, false)).to.equal(signedSwapRequestData.mainnetDigest) 38 | }) 39 | }) 40 | 41 | describe('#SwapSigner.hashRelease', () => { 42 | it('hashes a swap release for testnet', () => { 43 | expect(SwapSigner.hashRelease(encoded, recipient, true)).to.equal(signedSwapReleaseData.digest) 44 | }) 45 | 46 | it('hashes a swap release for mainnet', () => { 47 | expect(SwapSigner.hashRelease(encoded, recipient, false)).to.equal(signedSwapReleaseData.mainnetDigest) 48 | }) 49 | }) 50 | }) 51 | 52 | 53 | describe('EthersWalletSwapSigner', () => { 54 | const initiator = new MockProvider().getWallets()[1] 55 | const swapSigner = new EthersWalletSwapSigner(initiator) 56 | const encoded = signedSwapReleaseData.encoded 57 | const recipient = signedSwapReleaseData.recipient 58 | 59 | beforeEach('create EthersWalletSwapSigner', async () => { 60 | }) 61 | 62 | describe('#getAddress', () => { 63 | it('returns the signer address', async () => { 64 | expect(swapSigner.getAddress()).to.equal(initiator.address) 65 | }) 66 | }) 67 | 68 | describe('#signSwapRequest', () => { 69 | it('signs a swap request for testnet', async () => { 70 | expect(await swapSigner.signSwapRequest(encoded, true)) 71 | .to.deep.equal(signedSwapRequestData.signature) 72 | }) 73 | it('signs a swap request for mainnet', async () => { 74 | expect(await swapSigner.signSwapRequest(encoded, false)) 75 | .to.deep.equal(signedSwapRequestData.mainnetSignature) 76 | }) 77 | }) 78 | 79 | describe('#signSwapRelease', () => { 80 | it('signs a swap release for testnet', async () => { 81 | expect(await swapSigner.signSwapRelease(encoded, recipient, true)) 82 | .to.deep.equal(signedSwapReleaseData.signature) 83 | }) 84 | it('signs a swap release for mainnet', async () => { 85 | expect(await swapSigner.signSwapRelease(encoded, recipient, false)) 86 | .to.deep.equal(signedSwapReleaseData.mainnetSignature) 87 | }) 88 | }) 89 | }) 90 | 91 | 92 | describe('RemoteSwapSigner', () => { 93 | const initiator = new MockProvider().getWallets()[1] 94 | const remoteSigner = { 95 | getAddress: () => initiator.address, 96 | signMessage: async msg => { 97 | const header = '\x19Ethereum Signed Message:\n32' 98 | const digest = utils.solidityKeccak256(['string', 'bytes32'], [header, msg]) 99 | const { r, s, v } = initiator._signingKey().signDigest(digest) 100 | return utils.solidityPack(['bytes32', 'bytes32', 'uint8'], [r, s, v]) 101 | }, 102 | signTypedData: async data => { 103 | const typeHash = utils.solidityKeccak256( 104 | data.map(() => 'string'), 105 | data.map(({ type, name }) => `${type} ${name}`) 106 | ) 107 | const types = data.map(({ type }) => type) 108 | const values = data.map(({ value }) => value) 109 | const digest = utils.solidityKeccak256(['bytes32', 'bytes32'], [typeHash, utils.solidityKeccak256(types, values)]) 110 | const { r, s, v } = initiator._signingKey().signDigest(digest) 111 | return utils.solidityPack(['bytes32', 'bytes32', 'uint8'], [r, s, v]) 112 | } 113 | } 114 | const swapSigner = new RemoteSwapSigner(remoteSigner) 115 | const encoded = signedSwapReleaseData.encoded 116 | const recipient = signedSwapReleaseData.recipient 117 | 118 | describe('#getAddress', () => { 119 | it('returns the signer address', async () => { 120 | expect(swapSigner.getAddress()).to.equal(initiator.address) 121 | }) 122 | }) 123 | 124 | describe('#signSwapRequest', () => { 125 | it('signs a swap request for testnet', async () => { 126 | expect(await swapSigner.signSwapRequest(encoded, true)) 127 | .to.deep.equal(signedSwapRequestData.signature) 128 | }) 129 | it('signs a swap request for mainnet', async () => { 130 | expect(await swapSigner.signSwapRequest(encoded, false)) 131 | .to.deep.equal(signedSwapRequestData.mainnetSignature) 132 | }) 133 | }) 134 | 135 | describe('#signSwapRelease', () => { 136 | it('signs a swap release for testnet', async () => { 137 | expect(await swapSigner.signSwapRelease(encoded, recipient, true)) 138 | .to.deep.equal(signedSwapReleaseData.signature) 139 | }) 140 | it('signs a swap release for mainnet', async () => { 141 | expect(await swapSigner.signSwapRelease(encoded, recipient, false)) 142 | .to.deep.equal(signedSwapReleaseData.mainnetSignature) 143 | }) 144 | }) 145 | }) 146 | -------------------------------------------------------------------------------- /packages/sdk/tests/SwapWithSigner.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { MockProvider } from 'ethereum-waffle' 3 | import { Wallet } from 'ethers' 4 | 5 | import { SwapWithSigner, EthersWalletSwapSigner } from '../src' 6 | import { signedSwapRequestData, signedSwapReleaseData } from './shared' 7 | 8 | describe('SwapWithSigner', () => { 9 | let swapWithSigner: SwapWithSigner 10 | let initiator: Wallet 11 | 12 | beforeEach('create SwapWithSigner', () => { 13 | const wallets = new MockProvider().getWallets() 14 | initiator = wallets[1] 15 | const swapSigner = new EthersWalletSwapSigner(initiator) 16 | const swap = SwapWithSigner.decode(signedSwapRequestData.encoded) 17 | swapWithSigner = new SwapWithSigner(swap, swapSigner) 18 | }) 19 | 20 | describe('#signForRequest', () => { 21 | it('signs a swap request for testnet', async () => { 22 | const signedSwapRequest = await swapWithSigner.signForRequest(true) 23 | expect(signedSwapRequest.initiator).to.equal(initiator.address) 24 | expect(signedSwapRequest.signature).to.deep.equal(signedSwapRequestData.signature) 25 | }) 26 | it('signs a swap request for mainnet', async () => { 27 | const signedSwapRequest = await swapWithSigner.signForRequest(false) 28 | expect(signedSwapRequest.initiator).to.equal(initiator.address) 29 | expect(signedSwapRequest.signature).to.deep.equal(signedSwapRequestData.mainnetSignature) 30 | }) 31 | }) 32 | 33 | describe('#signForRelease', () => { 34 | it('signs a swap release for testnet', async () => { 35 | const signedSwapRelease = await swapWithSigner.signForRelease(signedSwapReleaseData.recipient, true) 36 | expect(signedSwapRelease.initiator).to.equal(initiator.address) 37 | expect(signedSwapRelease.recipient).to.equal(signedSwapReleaseData.recipient) 38 | expect(signedSwapRelease.signature).to.deep.equal(signedSwapReleaseData.signature) 39 | }) 40 | it('signs a swap release for mainnet', async () => { 41 | const signedSwapRelease = await swapWithSigner.signForRelease(signedSwapReleaseData.recipient, false) 42 | expect(signedSwapRelease.initiator).to.equal(initiator.address) 43 | expect(signedSwapRelease.recipient).to.equal(signedSwapReleaseData.recipient) 44 | expect(signedSwapRelease.signature).to.deep.equal(signedSwapReleaseData.mainnetSignature) 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/sdk/tests/shared/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish, utils } from 'ethers' 2 | import { PartialSwapData, SwapData, SignedSwapRequestData, SignedSwapReleaseData } from '../..' 3 | 4 | export function getPartialSwap({ 5 | amount = utils.parseUnits('1000', 6) as BigNumberish, 6 | fee = utils.parseUnits('1', 6) as BigNumberish, 7 | inToken = 1, 8 | outToken = 1, 9 | } = {}): PartialSwapData { 10 | return { 11 | amount, 12 | fee, 13 | inToken, 14 | outToken, 15 | recipient: '0x2ef8a51f8ff129dbb874a0efb021702f59c1b211', 16 | salt: '0x80' 17 | } 18 | } 19 | 20 | export function getSwap({ 21 | amount = utils.parseUnits('1000', 6) as BigNumberish, 22 | fee = utils.parseUnits('1', 6) as BigNumberish, 23 | expireTs = Math.floor(Date.now() / 1000) + 5400, 24 | inChain = '0x0001', 25 | inToken = 2, 26 | outChain = '0x0002', 27 | outToken = 3, 28 | } = {}): SwapData { 29 | return { 30 | amount, 31 | fee, 32 | expireTs, 33 | inChain, 34 | inToken, 35 | outChain, 36 | outToken, 37 | } 38 | } 39 | 40 | interface ExtendedSignedSwapRequestData extends SignedSwapRequestData { 41 | digest: string 42 | mainnetDigest: string 43 | mainnetSignature: string 44 | } 45 | 46 | export const signedSwapRequestData = { 47 | testnet: true, 48 | encoded: '0x01003b9aca000000000000008140328700000f424000621d7ef8003c01003c01', 49 | initiator: '0x63FC2aD3d021a4D7e64323529a55a9442C444dA0', 50 | digest: '0xae2ef152547628db8ec4d935ea85828b120aca6ece438207f8dcaa842e69ff0f', 51 | signature: '0x3cd0c5a3abe5214b5001e44d73d02b6fa7068f46256090c1043ebcd23483c2d0ad4038d42b0b247fefa54c123675a5a4984431c9033261f96f0084d42a5d52c7', 52 | mainnetDigest: '0x778f68046136b3a42d3f715762b1e114926da740d4a3b9ada63d0af2964d6302', 53 | mainnetSignature: '0x39e66dc0fcacad5f09cc92a92ec6e279c8445e68c9c417d1b67a7a268465bc737bb7b56caedd14c12a10b2f71dabd50e46941de3c26b7039c8e1be5d3d4282e7' 54 | } as ExtendedSignedSwapRequestData 55 | 56 | interface ExtendedSignedSwapReleaseData extends SignedSwapReleaseData { 57 | digest: string 58 | mainnetDigest: string 59 | mainnetSignature: string 60 | } 61 | 62 | export const signedSwapReleaseData = { 63 | testnet: true, 64 | encoded: '0x01003b9aca000000000000008140328700000f424000621d7ef8003c01003c01', 65 | initiator: '0x63FC2aD3d021a4D7e64323529a55a9442C444dA0', 66 | recipient: '0xD1D84F0e28D6fedF03c73151f98dF95139700aa7', 67 | digest: '0xf0331cc7c17b25ed6bfd9bd1ea3bc35d0afbc9dd6148d5c5ad585ad24fa3944c', 68 | signature: '0x7b865163e2e513a187f8a6d9a73f7ae4a5026d510da2c48a486888ba54e4a483bb7e6ed417a721247b27dba3968e8b08179eb940ebb8e4865b574149e0cf7097', 69 | mainnetDigest: '0x46e800b7c736d9fe41e74f6719ff6b1c230e04b3b47f58896714b6f14e371193', 70 | mainnetSignature: '0xf8eabaa8bd8f26c5d0338a0b1fcc9e577bb33c77ede12c130ca39a506129a36fdffde39331c79c9c37fa0497ae53bb4ba3e8ec3a8e59cc8f601d6a7dfbeccf6e' 71 | } as ExtendedSignedSwapReleaseData -------------------------------------------------------------------------------- /packages/sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "commonjs", 5 | "strict": false, 6 | "esModuleInterop": true, 7 | "outDir": "lib", 8 | "declaration": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "typeRoots": ["./node_modules/@types"] 12 | }, 13 | "include": ["./src"], 14 | "ts-node": { 15 | "transpileOnly": true, 16 | "transpiler": "ts-node/transpilers/swc-experimental" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/config/MesonConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.16; 3 | 4 | /// @notice Parameters of the Meson contract 5 | /// for CONFIG_BLOCKCHAIN_NAME 6 | contract MesonConfig { 7 | uint8 constant MESON_PROTOCOL_VERSION = CONFIG_PROTOCOL_VERSION; 8 | 9 | // Ref https://github.com/satoshilabs/slips/blob/master/slip-0044.md 10 | uint16 constant SHORT_COIN_TYPE = CONFIG_COIN_TYPE; 11 | 12 | uint256 constant MAX_SWAP_AMOUNT = 1e11; // 100,000.000000 = 100k 13 | uint256 constant SERVICE_FEE_RATE = 5; // service fee = 5 / 10000 = 0.05% 14 | uint256 constant SERVICE_FEE_MINIMUM = 500_000; // min $0.5 15 | uint256 constant SERVICE_FEE_MINIMUM_ETH = 500; // min 0.0005 ETH (or SOL) 16 | uint256 constant SERVICE_FEE_MINIMUM_BNB = 5000; // min 0.005 BNB 17 | uint256 constant SERVICE_FEE_MINIMUM_BTC = 10; // min 0.00001 BTC 18 | 19 | uint256 constant MIN_BOND_TIME_PERIOD = 1 hours; 20 | uint256 constant MAX_BOND_TIME_PERIOD = 2 hours; 21 | uint256 constant LOCK_TIME_PERIOD = CONFIG_LOCK_TIME; 22 | 23 | bytes28 constant ETH_SIGN_HEADER = bytes28("\x19Ethereum Signed Message:\n32"); 24 | bytes28 constant ETH_SIGN_HEADER_52 = bytes28("\x19Ethereum Signed Message:\n52"); 25 | bytes25 constant TRON_SIGN_HEADER = bytes25("\x19TRON Signed Message:\n32\n"); 26 | bytes25 constant TRON_SIGN_HEADER_33 = bytes25("\x19TRON Signed Message:\n33\n"); 27 | bytes25 constant TRON_SIGN_HEADER_53 = bytes25("\x19TRON Signed Message:\n53\n"); 28 | 29 | bytes32 constant REQUEST_TYPE_HASH = keccak256("bytes32 Sign to request a swap on MesonCONFIG_TESTNET_MODE"); 30 | bytes32 constant RELEASE_TYPE_HASH = keccak256("bytes32 Sign to release a swap on MesonCONFIG_TESTNET_MODEaddress Recipient"); 31 | 32 | bytes32 constant RELEASE_TO_TRON_TYPE_HASH = keccak256("bytes32 Sign to release a swap on MesonCONFIG_TESTNET_MODEaddress Recipient (tron address in hex format)"); 33 | } 34 | -------------------------------------------------------------------------------- /scripts/config/set-chain-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const mainnetsPath = path.join(__dirname, '../../packages/config/networks/mainnets.json') 5 | const testnetsPath = path.join(__dirname, '../../packages/config/networks/testnets.json') 6 | 7 | const testnets = require(testnetsPath); 8 | const presets = require(mainnetsPath); 9 | 10 | module.exports = async function setChainConfig(networkId, testnetMode) { 11 | if (!networkId) { 12 | throw new Error(`No networkId specified`) 13 | } 14 | 15 | const template = await fs.promises.readFile( 16 | path.join(__dirname, 'MesonConfig.sol'), 17 | 'utf8' 18 | ) 19 | 20 | let network 21 | if (networkId === 'local') { 22 | network = { name: 'Local', shortSlip44: '0x0001' } 23 | } else if (testnetMode) { 24 | network = testnets.find(item => item.id.startsWith(networkId)) 25 | } else { 26 | network = presets.find(item => item.id.startsWith(networkId)) 27 | } 28 | if (!network) { 29 | throw new Error(`Invalid network: ${networkId}`) 30 | } 31 | 32 | console.log(`Switch chain config to: ${networkId} ${testnetMode ? 'testnet' : 'mainnet'}`) 33 | 34 | let config = template 35 | .replace('CONFIG_PROTOCOL_VERSION', 1) 36 | .replace('CONFIG_BLOCKCHAIN_NAME', `${network.name}`) 37 | .replace('CONFIG_COIN_TYPE', network.shortSlip44) 38 | 39 | if (network.shortSlip44 === '0x003c') { 40 | config = config.replace('CONFIG_LOCK_TIME', '40 minutes') 41 | } else { 42 | config = config.replace('CONFIG_LOCK_TIME', '20 minutes') 43 | } 44 | 45 | config = config.replace(/CONFIG_TESTNET_MODE/g, testnetMode ? ' (Testnet)' : '') 46 | 47 | await fs.promises.writeFile( 48 | path.join(__dirname, '../../contracts/utils/MesonConfig.sol'), 49 | config 50 | ) 51 | 52 | return network 53 | } 54 | -------------------------------------------------------------------------------- /scripts/copy-abis.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const pairs = [[ 4 | 'artifacts/contracts/Meson.sol/Meson.json', 5 | 'packages/config/abis/MesonAbi.json' 6 | ], [ 7 | 'artifacts/contracts/test/MockToken.sol/MockToken.json', 8 | 'packages/config/abis/ERC20Abi.json' 9 | ]]; 10 | 11 | pairs.forEach(([from, to]) => { 12 | 13 | const content = fs.readFileSync(from, 'utf-8'); 14 | const json = JSON.parse(content); 15 | 16 | const result = JSON.stringify({contractName: json.contractName, abi: json.abi}, null, 2); 17 | 18 | fs.writeFileSync(to, result, 'utf-8'); 19 | }); 20 | -------------------------------------------------------------------------------- /scripts/data/.gitignore: -------------------------------------------------------------------------------- 1 | *.json -------------------------------------------------------------------------------- /scripts/deploy-forward.js: -------------------------------------------------------------------------------- 1 | const { adaptors } = require('@mesonfi/sdk') 2 | const { getAdaptor } = require('./lib/getAdaptor') 3 | const { deployContract } = require('./lib/deploy') 4 | 5 | require('dotenv').config() 6 | 7 | const { PRIVATE_KEY } = process.env 8 | 9 | module.exports = async function deployForwardContract(network) { 10 | const adaptor = getAdaptor(network) 11 | await hre.run('compile') 12 | 13 | const wallet = adaptors.getWallet(PRIVATE_KEY, adaptor) 14 | console.log('Deploying ForwardTokenContract...') 15 | await deployContract('ForwardTokenContract', wallet) 16 | } 17 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const { adaptors } = require('@mesonfi/sdk') 3 | const { getAdaptor } = require('./lib/getAdaptor') 4 | const { deployMeson, deployContract } = require('./lib/deploy') 5 | const { deposit } = require('./lib/pool') 6 | const updatePresets = require('./lib/updatePresets') 7 | 8 | require('dotenv').config() 9 | 10 | const { 11 | PRIVATE_KEY, 12 | PREMIUM_MANAGER, 13 | DEPOSIT_ON_DEPLOY, 14 | } = process.env 15 | 16 | module.exports = async function deploy(network, upgradable, testnetMode) { 17 | const adaptor = getAdaptor(network) 18 | await hre.run('compile') 19 | 20 | if (upgradable && network.id.startsWith('skale')) { 21 | upgradable = 2 // deploy Proxy2ToMeson 22 | } 23 | 24 | const wallet = adaptors.getWallet(PRIVATE_KEY, adaptor) 25 | const tokens = network.tokens 26 | 27 | if (testnetMode) { // only for testnets 28 | for (const token of tokens) { 29 | if (token.addr) { 30 | continue 31 | } 32 | 33 | console.log(`Deploying ${token.name}...`) 34 | const totalSupply = ethers.utils.parseUnits('100000000', token.decimals) 35 | const args = [token.name, token.symbol, totalSupply.toString(), token.decimals] 36 | const tokenContract = await deployContract('MockToken', wallet, args) 37 | token.addr = tokenContract.address 38 | } 39 | } 40 | 41 | const meson = await deployMeson(wallet, upgradable, PREMIUM_MANAGER, tokens) 42 | network.mesonAddress = meson.address 43 | 44 | const shortCoinType = await meson.getShortCoinType() 45 | if (shortCoinType !== network.shortSlip44) { 46 | throw new Error('Coin type does not match') 47 | } 48 | 49 | if (testnetMode && DEPOSIT_ON_DEPLOY) { // only for testnets 50 | for (const token of tokens) { 51 | await deposit(token.tokenIndex, DEPOSIT_ON_DEPLOY, { network, wallet }) 52 | } 53 | } 54 | 55 | network.tokens = tokens 56 | updatePresets(network) 57 | } 58 | -------------------------------------------------------------------------------- /scripts/estimate-gas.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const { 3 | MesonClient, 4 | EthersWalletSwapSigner, 5 | SignedSwapRequest, 6 | SignedSwapRelease, 7 | } = require('@mesonfi/sdk') 8 | 9 | const { deployContract, deployMeson } = require('./lib/deploy') 10 | const { wallet: unconnectedWallet } = require('../test/shared/wallet') 11 | const { getPartialSwap } = require('../test/shared/meson') 12 | 13 | const testnetMode = true 14 | 15 | module.exports = async function main(upgradable) { 16 | const wallet = unconnectedWallet.connect(ethers.provider) 17 | const swapSigner = new EthersWalletSwapSigner(wallet) 18 | 19 | const totalSupply = ethers.utils.parseUnits('10000', 6) 20 | const tokenContract = await deployContract('MockToken', wallet, ['Mock Token', 'MT', totalSupply, 6]) 21 | 22 | const tokenIndex = 1 23 | const tokens = [{ addr: tokenContract.address, tokenIndex }] 24 | const mesonContract = await deployMeson(wallet, !!upgradable, wallet.address, tokens) 25 | const mesonClient = await MesonClient.Create(mesonContract, swapSigner) 26 | 27 | // approve 28 | const approveTx = await tokenContract.approve(mesonContract.address, totalSupply) 29 | getUsedGas('approve', approveTx.hash) 30 | await approveTx.wait(1) 31 | 32 | // deposits 33 | const amount = ethers.utils.parseUnits('1000', 6) 34 | const depositTx1 = await mesonClient.depositAndRegister(tokenIndex, amount, '1') 35 | getUsedGas('first deposit', depositTx1.hash) 36 | await depositTx1.wait(1) 37 | 38 | const depositTx2 = await mesonClient.deposit(tokenIndex, amount) 39 | getUsedGas('another deposit', depositTx2.hash) 40 | await depositTx2.wait(1) 41 | 42 | // requestSwap (no gas) 43 | const swapData = getPartialSwap() 44 | const outChain = await mesonContract.getShortCoinType() 45 | const swap = mesonClient.requestSwap(swapData, outChain) 46 | const request = await swap.signForRequest(testnetMode) 47 | const signedRequest = new SignedSwapRequest(request) 48 | signedRequest.checkSignature(testnetMode) 49 | 50 | const swapData2 = getPartialSwap({ amount: ethers.utils.parseUnits('500', 6) }) 51 | const swap2 = mesonClient.requestSwap(swapData2, outChain) 52 | const request2 = await swap2.signForRequest(testnetMode) 53 | const signedRequest2 = new SignedSwapRequest(request2) 54 | signedRequest2.checkSignature(testnetMode) 55 | 56 | // postSwap 57 | const postSwapTx1 = await mesonClient.postSwap(signedRequest) 58 | getUsedGas('first postSwap', postSwapTx1.hash) 59 | await postSwapTx1.wait(1) 60 | 61 | const postSwapTx2 = await mesonClient.postSwap(signedRequest2) 62 | getUsedGas('another postSwap', postSwapTx2.hash) 63 | await postSwapTx2.wait(1) 64 | 65 | // lock 66 | const lockTx = await mesonClient.lock(signedRequest) 67 | getUsedGas('lock', lockTx.hash) 68 | await lockTx.wait(1) 69 | 70 | // export release signature 71 | const release = await swap.signForRelease(swapData.recipient, testnetMode) 72 | const signedRelease = new SignedSwapRelease(release) 73 | signedRelease.checkSignature(testnetMode) 74 | 75 | // release 76 | const releaseTx = await mesonClient.release(signedRelease) 77 | getUsedGas('release', releaseTx.hash) 78 | 79 | // executeSwap 80 | const executeTx = await mesonClient.executeSwap(signedRelease, true) 81 | await getUsedGas('execute', executeTx.hash) 82 | } 83 | 84 | async function getUsedGas(name, hash) { 85 | return ethers.provider.getTransactionReceipt(hash).then((receipt) => { 86 | console.log(name, ':', receipt.cumulativeGasUsed.toString()) 87 | }) 88 | } 89 | -------------------------------------------------------------------------------- /scripts/lib/CustomGasFeeProviderWrapper.js: -------------------------------------------------------------------------------- 1 | const { EthersProviderWrapper } = require('@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper') 2 | 3 | module.exports = class CustomGasFeeProviderWrapper extends EthersProviderWrapper { 4 | async getFeeData() { 5 | const feeData = await this._checkEip1559() || { gasPrice: await this.getGasPrice() } 6 | return feeData 7 | } 8 | 9 | _eip1559FeeData = undefined 10 | async _checkEip1559 () { 11 | if (typeof this._eip1559FeeData !== 'undefined') { 12 | return this._eip1559FeeData 13 | } 14 | 15 | const block = await this.getBlockWithTransactions('latest') 16 | const transactions = [...block.transactions] 17 | let offset = 0 18 | while (transactions.length < 20) { 19 | offset++ 20 | transactions.push(...(await this.getBlockWithTransactions(block.number - offset)).transactions) 21 | if (offset > 5) { 22 | break 23 | } 24 | } 25 | const eip1559Transactions = transactions.filter(tx => tx.maxFeePerGas && tx.maxPriorityFeePerGas) 26 | 27 | setTimeout(() => this._eip1559FeeData = undefined, 60_000) 28 | 29 | if (!transactions.length || eip1559Transactions.length / transactions.length < 0.5) { 30 | this._eip1559FeeData = null 31 | return 32 | } 33 | 34 | const sorted = eip1559Transactions.sort((tx1, tx2) => tx1.maxFeePerGas.lt(tx2.maxFeePerGas) ? -1 : 1) 35 | const index = Math.floor(sorted.length * 0.5) 36 | const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = sorted[index] 37 | this._eip1559FeeData = { gasPrice, maxFeePerGas, maxPriorityFeePerGas } 38 | 39 | return this._eip1559FeeData 40 | } 41 | 42 | async estimateGas(transaction) { 43 | return await super.estimateGas(transaction) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scripts/lib/deploy.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const { Wallet: ZkWallet } = require('zksync-web3') 3 | const { Deployer } = require('@matterlabs/hardhat-zksync-deploy') 4 | 5 | const { adaptors } = require('@mesonfi/sdk') 6 | const { MesonAbi } = require('@mesonfi/config') 7 | 8 | exports.deployMeson = async function deployMeson(wallet, upgradable, premiumManager, tokens) { 9 | let mesonContract 10 | if (upgradable === 2) { 11 | console.log('Deploying Proxy2ToMeson & UpgradableMeson...') 12 | const impl = await deployContract('UpgradableMeson', wallet, []) 13 | const proxy = await deployContract('Proxy2ToMeson', wallet, [impl.address, premiumManager]) 14 | mesonContract = adaptors.getContract(proxy.address, MesonAbi.abi, wallet) 15 | } else if (upgradable) { 16 | console.log('Deploying ProxyToMeson & UpgradableMeson...') 17 | const proxy = await deployContract('ProxyToMeson', wallet, [premiumManager]) 18 | mesonContract = adaptors.getContract(proxy.address, MesonAbi.abi, wallet) 19 | } else { 20 | console.log('Deploying Meson...') 21 | mesonContract = await deployContract('Meson', wallet, [premiumManager]) 22 | } 23 | 24 | await new Promise(resolve => setTimeout(resolve, 2000)) 25 | 26 | console.log('Adding supported tokens', tokens) 27 | const tx = await mesonContract.addMultipleSupportedTokens(tokens.map(t => t.addr), tokens.map(t => t.tokenIndex)) 28 | await tx.wait(1) 29 | return mesonContract 30 | } 31 | 32 | async function deployContract(name, wallet, args = []) { 33 | if (wallet instanceof ZkWallet) { 34 | return await deployZkContract(name, wallet, args) 35 | } else if (wallet instanceof ethers.Wallet) { 36 | return await deployEtherContract(name, wallet, args) 37 | } else { 38 | return await deployTronContract(name, wallet, args) 39 | } 40 | } 41 | exports.deployContract = deployContract 42 | 43 | async function deployZkContract(name, wallet, args) { 44 | const deployer = new Deployer(hre, wallet) 45 | const artifact = await deployer.loadArtifact(name) 46 | const instance = await deployer.deploy(artifact, args) 47 | console.log(`${name} deployed to:`, instance.address) 48 | return instance 49 | } 50 | 51 | async function deployEtherContract(name, wallet, args) { 52 | const factory = await ethers.getContractFactory(name, wallet) 53 | const instance = await factory.deploy(...args) 54 | await instance.deployed() 55 | console.log(`${name} deployed to:`, instance.address) 56 | return instance 57 | } 58 | 59 | async function deployTronContract(name, wallet, args) { 60 | const factory = await ethers.getContractFactory(name) 61 | const abi = JSON.parse(factory.interface.format('json')) 62 | const constructor = abi.find(({ type }) => type === 'constructor') 63 | if (constructor) { 64 | constructor.stateMutability = constructor.payable ? 'payable' : 'nonpayable' 65 | } 66 | const deployed = await wallet.tronWeb.contract().new({ 67 | abi, 68 | bytecode: factory.bytecode, 69 | feeLimit: 5000_000000, 70 | callValue: 0, 71 | parameters: args 72 | }) 73 | const instance = adaptors.getContract(deployed.address, abi, wallet) 74 | console.log(`${name} deployed to:`, instance.address) 75 | return instance 76 | } 77 | -------------------------------------------------------------------------------- /scripts/lib/getAdaptor.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const presets = require('@mesonfi/presets').default 3 | 4 | const CustomGasFeeProviderWrapper = require('./CustomGasFeeProviderWrapper') 5 | 6 | exports.getAdaptor = function getAdaptor (network) { 7 | if (!presets.getNetwork(network.id)) { 8 | presets.useTestnet(true) 9 | } 10 | 11 | if (network.addressFormat === 'ethers' && !network.id.startsWith('zksync') && !network.id.startsWith('zklink')) { 12 | const provider = new ethers.providers.JsonRpcProvider(network.url) 13 | ethers.provider = new CustomGasFeeProviderWrapper(provider) 14 | return ethers.provider 15 | } else { 16 | return presets.createAdaptor(network.id, network.url) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/lib/pool.js: -------------------------------------------------------------------------------- 1 | const { MesonClient, adaptors } = require('@mesonfi/sdk') 2 | const { MesonAbi } = require('@mesonfi/config') 3 | 4 | function getTokenIndex(network, symbol) { 5 | if (!symbol) { 6 | return -1 7 | } if (symbol.toLowerCase() === 'uct') { 8 | return 255 9 | } 10 | 11 | const token = network.tokens.find(t => !t.disabled && t.symbol.toLowerCase().includes(symbol.toLowerCase())) 12 | return token.tokenIndex 13 | } 14 | 15 | exports.addSupportedTokens = async function addSupportedTokens(tokens, { network, wallet }) { 16 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 17 | 18 | console.log('Adding supported tokens', tokens) 19 | let tx 20 | if (tokens.length > 1) { 21 | tx = await mesonInstance.addMultipleSupportedTokens(tokens.map(t => t.addr), tokens.map(t => t.tokenIndex)) 22 | } else { 23 | tx = await mesonInstance.addSupportToken(tokens[0].addr, tokens[0].tokenIndex) 24 | } 25 | await tx.wait(1) 26 | return tx 27 | } 28 | 29 | exports.removeSupportToken = async function removeSupportToken(tokenIndex, { network, wallet }) { 30 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 31 | 32 | console.log('Removing supported token', tokenIndex) 33 | const tx = await mesonInstance.removeSupportToken(tokenIndex) 34 | await tx.wait(1) 35 | return tx 36 | } 37 | 38 | exports.deposit = async function deposit(symbol, amount, { network, wallet }) { 39 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 40 | const mesonClient = await MesonClient.Create(mesonInstance) 41 | const tokenIndex = getTokenIndex(network, symbol) 42 | 43 | const allowance = await mesonClient.getAllowance(wallet.address, tokenIndex) 44 | console.log(`Allowance: ${allowance.display}`) 45 | const value = MesonClient.toSwapValue(amount) 46 | if (allowance.value.lt(value)) { 47 | console.log(`Approving...`) 48 | const toApprove = MesonClient.toSwapValue('1000000000') // 1B 49 | const tx = await mesonClient.approveToken(tokenIndex, mesonInstance.address, toApprove) 50 | await tx.wait(1) 51 | } 52 | 53 | console.log(`Depositing ${amount} ${symbol}...`) 54 | const poolIndex = await mesonInstance.poolOfAuthorizedAddr(wallet.address) 55 | 56 | let tx 57 | if (poolIndex == 0) { 58 | tx = await mesonClient.depositAndRegister(tokenIndex, value, 1) 59 | } else { 60 | tx = await mesonClient.deposit(tokenIndex, value) 61 | } 62 | await tx.wait(1) 63 | return tx 64 | } 65 | 66 | exports.withdraw = async function withdraw(symbol, amount, { network, wallet }) { 67 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 68 | const tokenIndex = getTokenIndex(network, symbol) 69 | 70 | console.log(`Withdrawing ${amount} ${symbol}...`) 71 | const poolIndex = await mesonInstance.poolOfAuthorizedAddr(wallet.address) 72 | const poolTokenIndex = tokenIndex * 2**40 + poolIndex 73 | 74 | // const gasLimit = await mesonInstance.estimateGas.withdraw(MesonClient.toSwapValue(amount), poolTokenIndex) 75 | const tx = await mesonInstance.withdraw(MesonClient.toSwapValue(amount), poolTokenIndex) 76 | await tx.wait(1) 77 | return tx 78 | } 79 | 80 | exports.withdrawServiceFee = async function withdrawServiceFee(symbol, amount, { network, wallet }) { 81 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 82 | const tokenIndex = getTokenIndex(network, symbol) 83 | 84 | console.log(`Withdrawing Service Fee ${amount} ${symbol}...`) 85 | 86 | const tx = await mesonInstance.withdrawServiceFee(tokenIndex, MesonClient.toSwapValue(amount), 1) 87 | await tx.wait(1) 88 | return tx 89 | } 90 | 91 | exports.send = async function send(symbol, amount, recipient, { network, wallet }) { 92 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 93 | const mesonClient = await MesonClient.Create(mesonInstance) 94 | const tokenIndex = getTokenIndex(network, symbol) 95 | 96 | console.log(`Sending ${amount} ${symbol} to ${recipient}...`) 97 | const tx = await mesonClient.transferToken(tokenIndex, recipient, MesonClient.toSwapValue(amount)) 98 | await tx.wait(1) 99 | return tx 100 | } 101 | 102 | exports.authorize = async function authorize(addr, { network, wallet }) { 103 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 104 | 105 | const poolIndex = await mesonInstance.poolOfAuthorizedAddr(wallet.address) 106 | 107 | console.log(`Authorizing ${addr} to pool ${poolIndex}...`) 108 | const tx = await mesonInstance.addAuthorizedAddr(addr) 109 | await tx.wait(1) 110 | return tx 111 | } 112 | 113 | exports.transferOwner = async function transferOwner(addr, { network, wallet }) { 114 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 115 | 116 | const poolIndex = await mesonInstance.poolOfAuthorizedAddr(wallet.address) 117 | 118 | console.log(`Transfer owner of pool ${poolIndex} to ${addr}...`) 119 | const tx = await mesonInstance.transferPoolOwner(addr) 120 | await tx.wait(1) 121 | return tx 122 | } 123 | -------------------------------------------------------------------------------- /scripts/lib/updatePresets.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const mainnetsPath = path.join(__dirname, '../../packages/config/networks/mainnets.json') 5 | const testnetsPath = path.join(__dirname, '../../packages/config/networks/testnets.json') 6 | 7 | module.exports = function updatePresets (network) { 8 | const mainnets = require(mainnetsPath) 9 | let index = mainnets.findIndex(item => item.id === network.id) 10 | if (index > -1) { 11 | // mainnet 12 | mainnets.splice(index, 1, network) 13 | fs.writeFileSync(mainnetsPath, JSON.stringify(mainnets, null, 2)) 14 | return 15 | } 16 | 17 | // testnet 18 | const testnets = require(testnetsPath) 19 | index = testnets.findIndex(item => item.id === network.id) 20 | if (index === -1) { 21 | throw new Error(`Invalid network: ${networkId}`) 22 | } 23 | testnets.splice(index, 1, network) 24 | fs.writeFileSync(testnetsPath, JSON.stringify(testnets, null, 2)) 25 | } 26 | -------------------------------------------------------------------------------- /scripts/pool.js: -------------------------------------------------------------------------------- 1 | const { adaptors } = require('@mesonfi/sdk') 2 | const { MesonAbi } = require('@mesonfi/config') 3 | const { getAdaptor } = require('./lib/getAdaptor') 4 | const { addSupportedTokens, deposit, withdraw, send, authorize, transferOwner, withdrawServiceFee } = require('./lib/pool') 5 | 6 | require('dotenv').config() 7 | 8 | const { LP_PRIVATE_KEY } = process.env 9 | 10 | const amount = '10' 11 | const symbol = 'USDC' 12 | const addr = '' 13 | 14 | module.exports = async function pool(network) { 15 | const adaptor = getAdaptor(network) 16 | 17 | const wallet = adaptors.getWallet(LP_PRIVATE_KEY, adaptor) 18 | console.log(`⬜️ LP address: ${wallet.address}`) 19 | 20 | const mesonInstance = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 21 | console.log(`🟩 Status: ${JSON.stringify(await mesonInstance.provider.detectNetwork())}`) 22 | console.log(`🟩 Block height: ${await mesonInstance.provider.getBlockNumber()}`) 23 | console.log(`🟩 LP balance: ${await mesonInstance.provider.getBalance(wallet.address)}`) 24 | 25 | // const tx = await deposit(symbol, amount, { network, wallet }) 26 | // const tx = await withdraw(symbol, amount, { network, wallet }) 27 | // const tx = await send(symbol, amount, addr, { network, wallet }) 28 | // const tx = await addSupportedTokens(tokens, { network, wallet }) 29 | // const tx = await authorize(addr, { network, wallet }) 30 | // const tx = await transferOwner(addr, { network, wallet }) 31 | // const tx = await withdrawServiceFee(symbol, amount, { network, wallet }) 32 | // console.log(tx) 33 | } 34 | -------------------------------------------------------------------------------- /scripts/uct.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('hardhat') 2 | const TronWeb = require('tronweb') 3 | const { adaptors } = require('@mesonfi/sdk') 4 | const { MesonAbi } = require('@mesonfi/config') 5 | 6 | const { getAdaptor } = require('./lib/getAdaptor') 7 | const { deployContract } = require('./lib/deploy') 8 | const updatePresets = require('./lib/updatePresets') 9 | const PoDUpgradeable = require('../artifacts/contracts/Token/PoDUpgradeable.sol/PoDUpgradeable.json') 10 | const UCTUpgradeable = require('../artifacts/contracts/Token/UCTUpgradeable.sol/UCTUpgradeable.json') 11 | 12 | require('dotenv').config() 13 | 14 | const { 15 | PRIVATE_KEY, 16 | MINTER, 17 | MINTER_PK 18 | } = process.env 19 | 20 | module.exports = async function uct(network) { 21 | await deploy(network) 22 | // await deployPoD(network) 23 | // await addUCT(network) 24 | 25 | // await upgrade(network) 26 | 27 | // await mint(network, [], '100') 28 | // await mintPoD(network, '', '100') 29 | 30 | // const list = require('./data/list.json') 31 | // await mint2(network, list.map(i => i.to), list.map(i => i.amount)) 32 | } 33 | 34 | async function deploy(network) { 35 | const adaptor = getAdaptor(network) 36 | const wallet = adaptors.getWallet(PRIVATE_KEY, adaptor) 37 | 38 | console.log('Deploying UCTUpgradeable...') 39 | const impl = await deployContract('UCTUpgradeable', wallet) 40 | console.log('Deploying Proxy...') 41 | const data = impl.interface.encodeFunctionData('initialize', [MINTER, TronWeb.address.toHex(network.mesonAddress).replace(/^(41)/, '0x')]) 42 | const proxy = await deployContract('ERC1967Proxy', wallet, [impl.address, data]) 43 | 44 | network.uctAddress = proxy.address 45 | updatePresets(network) 46 | } 47 | 48 | async function deployPoD(network) { 49 | const adaptor = getAdaptor(network) 50 | const wallet = adaptors.getWallet(PRIVATE_KEY, adaptor) 51 | 52 | console.log('Deploying PoDUpgradeable...') 53 | const impl = await deployContract('PoDUpgradeable', wallet) 54 | console.log('Deploying Proxy...') 55 | const data = impl.interface.encodeFunctionData('initialize', [MINTER, TronWeb.address.toHex(network.mesonAddress).replace(/^(41)/, '0x')]) 56 | const proxy = await deployContract('ERC1967Proxy', wallet, [impl.address, data]) 57 | 58 | network.tokens = [...network.tokens, { 59 | name: 'Proof of Deposit (meson.fi)', 60 | symbol: 'PoD', 61 | decimals: 6, 62 | addr: proxy.address, 63 | tokenIndex: 32 64 | }] 65 | updatePresets(network) 66 | } 67 | 68 | async function addUCT(network) { 69 | const adaptor = getAdaptor(network) 70 | const wallet = adaptors.getWallet(PRIVATE_KEY, adaptor) 71 | 72 | console.log('ADD UCT to Meson...') 73 | const meson = adaptors.getContract(network.mesonAddress, MesonAbi.abi, wallet) 74 | await meson.addSupportToken(network.uctAddress, 255) 75 | } 76 | 77 | async function upgrade(network) { 78 | const adaptor = getAdaptor(network) 79 | const wallet = adaptors.getWallet(PRIVATE_KEY, adaptor) 80 | 81 | console.log('Deploying UCTUpgradeable...') 82 | const impl = await deployContract('UCTUpgradeable', wallet) 83 | const abi = JSON.parse(impl.interface.format('json')) 84 | const proxy = adaptors.getContract(network.uctAddress, abi, wallet) 85 | await proxy.upgradeTo(impl.address) 86 | console.log('UCTUpgradeable upgraded') 87 | } 88 | 89 | async function mint(network, targets, amount) { 90 | const adaptor = getAdaptor(network) 91 | const wallet = adaptors.getWallet(MINTER_PK, adaptor) 92 | 93 | const uct = adaptors.getContract(network.uctAddress, UCTUpgradeable.abi, wallet) 94 | const tx = await uct.batchMint(targets, ethers.utils.parseUnits(amount, 4)) 95 | await tx.wait() 96 | } 97 | 98 | async function mint2(network, targets, amounts) { 99 | const adaptor = getAdaptor(network) 100 | const wallet = adaptors.getWallet(MINTER_PK, adaptor) 101 | 102 | const uct = adaptors.getContract(network.uctAddress, UCTUpgradeable.abi, wallet) 103 | const tx = await uct.batchMint2(targets, amounts) 104 | await tx.wait() 105 | } 106 | 107 | async function mintPoD(network, account, amount) { 108 | const adaptor = getAdaptor(network) 109 | const wallet = adaptors.getWallet(MINTER_PK, adaptor) 110 | 111 | const token = network.tokens.find(t => t.tokenIndex === 32) 112 | console.log(token) 113 | 114 | const uct = adaptors.getContract(token.addr, PoDUpgradeable.abi, wallet) 115 | const tx = await uct.mint(account, ethers.utils.parseUnits(amount, 6)) 116 | await tx.wait() 117 | } 118 | -------------------------------------------------------------------------------- /scripts/upgrade.js: -------------------------------------------------------------------------------- 1 | const { adaptors } = require('@mesonfi/sdk') 2 | const { getAdaptor } = require('./lib/getAdaptor') 3 | const { deployContract } = require('./lib/deploy') 4 | 5 | require('dotenv').config() 6 | 7 | const { PRIVATE_KEY } = process.env 8 | 9 | module.exports = async function upgrade(network) { 10 | const adaptor = getAdaptor(network) 11 | await hre.run('compile') 12 | 13 | const wallet = adaptors.getWallet(PRIVATE_KEY, adaptor) 14 | console.log('Deploying UpgradableMeson...') 15 | const impl = await deployContract('UpgradableMeson', wallet) 16 | const abi = JSON.parse(impl.interface.format('json')) 17 | const proxy = adaptors.getContract(network.mesonAddress, abi, wallet) 18 | await proxy.upgradeTo(impl.address) 19 | console.log('Meson upgraded') 20 | } 21 | -------------------------------------------------------------------------------- /templates/contract.hbs: -------------------------------------------------------------------------------- 1 | # {{{natspec.title}}} 2 | {{{natspec.userdoc}}} 3 | {{{natspec.devdoc}}} 4 | 5 | {{#if ownFunctions}} 6 | ## Functions 7 | {{/if}} 8 | {{#ownFunctions}} 9 | ### {{name}} 10 | ```solidity 11 | function {{name}}( 12 | {{#natspec.params}} 13 | {{#lookup ../args.types @index}}{{/lookup}} {{param}}{{#if @last}}{{else}},{{/if}} 14 | {{/natspec.params}} 15 | ) {{visibility}}{{#if outputs}} returns ({{outputs}}){{/if}} 16 | ``` 17 | {{#if natspec.userdoc}}{{natspec.userdoc}}{{/if}} 18 | {{#if natspec.devdoc}}{{natspec.devdoc}}{{/if}} 19 | {{#if natspec.params}} 20 | #### Parameters: 21 | | Name | Type | Description | 22 | | :--- | :--- | :------------------------------------------------------------------- | 23 | {{#natspec.params}} 24 | |`{{param}}` | {{#lookup ../args.types @index}}{{/lookup}} | {{ description }}{{/natspec.params}}{{/if}} 25 | {{#if natspec.returns}} 26 | #### Return Values: 27 | | Name | Type | Description | 28 | | :----------------------------- | :------------ | :--------------------------------------------------------------------------- | 29 | {{#natspec.returns}} 30 | |`{{param}}`| {{#lookup ../args.types @index}}{{/lookup}} | {{{description}}}{{/natspec.returns}}{{/if}} 31 | 32 | {{/ownFunctions}} 33 | {{#if ownEvents}} 34 | ## Events 35 | {{/if}} 36 | {{#ownEvents}} 37 | ### {{name}} 38 | ```solidity 39 | event {{name}}( 40 | {{#natspec.params}} 41 | {{#lookup ../args.types @index}}{{/lookup}} {{param}}{{#if @last}}{{else}},{{/if}} 42 | {{/natspec.params}} 43 | ) 44 | ``` 45 | {{#if natspec.userdoc}}{{natspec.userdoc}}{{/if}} 46 | {{#if natspec.devdoc}}{{natspec.devdoc}}{{/if}} 47 | {{#if natspec.params}} 48 | #### Parameters: 49 | | Name | Type | Description | 50 | | :----------------------------- | :------------ | :--------------------------------------------- | 51 | {{#natspec.params}} 52 | |`{{param}}`| {{#lookup ../args.types @index}}{{/lookup}} | {{{description}}}{{/natspec.params}}{{/if}} 53 | {{/ownEvents}} -------------------------------------------------------------------------------- /test/shared/expect.ts: -------------------------------------------------------------------------------- 1 | import { expect, use } from 'chai' 2 | import chaiAsPromised from 'chai-as-promised' 3 | import { solidity } from 'ethereum-waffle' 4 | import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot' 5 | 6 | use(chaiAsPromised) 7 | use(solidity) 8 | use(jestSnapshotPlugin()) 9 | 10 | export { expect } -------------------------------------------------------------------------------- /test/shared/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { MockToken, MesonPoolsTest, MesonSwapTest } from '@mesonfi/config' 3 | 4 | export const TOKEN_BALANCE = ethers.utils.parseUnits('3000', 6) 5 | export const TOKEN_TOTAL_SUPPLY = ethers.utils.parseUnits('10000', 6) 6 | 7 | export async function fixtures (accounts: string[] | undefined) { 8 | const signer = (await ethers.getSigners())[0] 9 | 10 | const tokenFactory = await ethers.getContractFactory('MockToken') 11 | const token1 = await tokenFactory.deploy('Mock Token 1', 'MT1', TOKEN_TOTAL_SUPPLY, 6) as MockToken 12 | const token2 = await tokenFactory.deploy('Mock Token 2', 'MT2', TOKEN_TOTAL_SUPPLY, 18) as MockToken 13 | 14 | if (accounts) { 15 | for (const account of accounts) { 16 | await signer.sendTransaction({ 17 | to: account, 18 | value: ethers.utils.parseEther('10') 19 | }) 20 | await token1.transfer(account, TOKEN_BALANCE) 21 | await token2.transfer(account, TOKEN_BALANCE) 22 | } 23 | } 24 | 25 | const poolsFactory = await ethers.getContractFactory('MesonPoolsTest') 26 | const pools = await poolsFactory.deploy(token1.address, accounts && accounts[1] || ethers.constants.AddressZero) as MesonPoolsTest 27 | 28 | const swapFactory = await ethers.getContractFactory('MesonSwapTest') 29 | const swap = await swapFactory.deploy(token1.address) as MesonSwapTest 30 | 31 | return { pools, swap, token1, token2 } 32 | } 33 | -------------------------------------------------------------------------------- /test/shared/meson.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | import { BigNumberish } from 'ethers' 3 | import { PartialSwapData, SwapData } from '@mesonfi/sdk' 4 | 5 | export const TestAddress = '0x7F342A0D04B951e8600dA1eAdD46afe614DaC20B' 6 | export const TestAddress2 = '0x2eF8a51F8fF129DBb874A0efB021702F59C1b211' 7 | export const AddressOne = '0x0000000000000000000000000000000000000001' 8 | 9 | export function getPartialSwap({ 10 | amount = ethers.utils.parseUnits('1000', 6) as BigNumberish, 11 | fee = ethers.utils.parseUnits('1', 6) as BigNumberish, 12 | inToken = 1, 13 | outToken = 1, 14 | } = {}): PartialSwapData { 15 | return { 16 | amount, 17 | fee, 18 | inToken, 19 | outToken, 20 | recipient: '0x2ef8a51f8ff129dbb874a0efb021702f59c1b211', 21 | salt: '0x80' 22 | } 23 | } 24 | 25 | export function getSwap({ 26 | amount = ethers.utils.parseUnits('1000', 6) as BigNumberish, 27 | fee = ethers.utils.parseUnits('1', 6) as BigNumberish, 28 | expireTs = Math.floor(Date.now() / 1000) + 5400, 29 | inChain = '0x0001', 30 | inToken = 2, 31 | outChain = '0x0002', 32 | outToken = 3, 33 | } = {}): SwapData { 34 | return { 35 | amount, 36 | fee, 37 | expireTs, 38 | inChain, 39 | inToken, 40 | outChain, 41 | outToken, 42 | recipient: '0x2ef8a51f8ff129dbb874a0efb021702f59c1b211', 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/shared/wallet.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat' 2 | 3 | // default mnemonic for hardhat network 4 | const mnemonic = 'test test test test test test test test test test test junk' 5 | export const wallet = ethers.Wallet.fromMnemonic(mnemonic) 6 | 7 | const privateKey1 = '0xb0467aa26cc0b8f5de684b447287958fbeea2877adf194c284cf98d8d0fad2dd' 8 | export const initiator = new ethers.Wallet(privateKey1, ethers.provider) 9 | 10 | const privateKey2 = '0x3618b13685a4faf31003929844655e3c08c864128c45ce356f97de22612ede51' 11 | export const poolOwner = new ethers.Wallet(privateKey2, ethers.provider) 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true, 9 | "typeRoots": ["./typechain", "./node_modules/@types"], 10 | }, 11 | "include": ["./scripts", "./test", "./typechain"], 12 | "files": ["./hardhat.config.ts"], 13 | "ts-node": { 14 | "transpileOnly": true, 15 | "transpiler": "ts-node/transpilers/swc-experimental" 16 | } 17 | } 18 | --------------------------------------------------------------------------------