diff --git a/basic/13-decentralized-exchange/EtherDelta/README-CN.md b/basic/13-decentralized-exchange/EtherDelta/README-CN.md index a1ff2cf3d..5d6d82061 100644 --- a/basic/13-decentralized-exchange/EtherDelta/README-CN.md +++ b/basic/13-decentralized-exchange/EtherDelta/README-CN.md @@ -57,8 +57,8 @@ EtherDelta 实现了简单的 token 合约,类似 ERC20 标准合约,用于 #### 交易流程 -1. 卖家挂单 `order()`,以挂单信息转换为 hash,作为键,存入 `orders` 合约变量 -2. 买家吃单 `trade()`,传入指定的挂单 hash +1. 卖家(maker)挂单 `order()`,以挂单信息转换为 hash,作为键,存入 `orders` 合约变量 +2. 买家(taker)吃单 `trade()`,传入指定的挂单 hash, 并且需要拿到maker的订单签名信息。且支持部分成交。 #### constructor @@ -82,11 +82,21 @@ mapping(address => mapping(bytes32 => bool)) public orders; // 挂单列表 (tru mapping(address => mapping(bytes32 => uint256)) public orderFills; // 每一笔挂单完成的数量 (amount of order that has been filled) ``` -## todo list +#### 手续费逻辑 + + 手续费都是收取maker的 tokenGet(即要买入的token) +``` + //0 = regular user (pays take fee and make fee) + //1 = market maker silver (pays take fee, no make fee, gets rebate) + //2 = market maker gold (pays take fee, no make fee, gets entire counterparty's take fee as rebate) +``` +普通用户,需要付take fee和maker fee +白银用户,付taker费用,并且有手续费返现 +黄金用户,付taker费用,但是可以获取对手方taker费用返现 ### 测试流程补全 -目前只实现了核心功能的测试,还有部分测试流程未升级,老测试文件参见 `./backup/test.old.js` +目前只实现了核心功能的测试,还有部分测试流程未升级,老测试文件参见 `../backup/etherdelta/test.old.js` **老版测试文件部分测试有误,建议以当前测试文件为准** ## 参考链接 diff --git a/basic/13-decentralized-exchange/EtherDelta/README.md b/basic/13-decentralized-exchange/EtherDelta/README.md index 6b588ec32..2614316d2 100644 --- a/basic/13-decentralized-exchange/EtherDelta/README.md +++ b/basic/13-decentralized-exchange/EtherDelta/README.md @@ -86,7 +86,7 @@ mapping(address => mapping(bytes32 => uint256)) public orderFills; // amount of ### Test Process Description -At present, only the core functions have been tested, and part of the test process has not been upgraded. See `./backup/test.old.js` for the old test file +At present, only the core functions have been tested, and part of the test process has not been upgraded. See `../backup/etherdelta/test.old.js` for the old test file **Some tests in the original test file are incorrect. You are advised to use the current test file** ## Refer to the link diff --git a/basic/13-decentralized-exchange/EtherDelta/contracts/EtherDelta.sol b/basic/13-decentralized-exchange/EtherDelta/contracts/EtherDelta.sol index 1ccc3614b..fdc1d3e11 100644 --- a/basic/13-decentralized-exchange/EtherDelta/contracts/EtherDelta.sol +++ b/basic/13-decentralized-exchange/EtherDelta/contracts/EtherDelta.sol @@ -232,12 +232,8 @@ contract EtherDelta { address give ); event Deposit(address token, address user, uint256 amount, uint256 balance); - event Withdraw( - address token, - address user, - uint256 amount, - uint256 balance - ); + + event Withdraw(address token, address user, uint256 amount, uint256 balance); modifier adminOnly { require(msg.sender == admin, "No permission"); @@ -449,6 +445,10 @@ contract EtherDelta { uint256 feeMakeXfer = SafeMath.mul(amount, feeMake) / (1 ether); uint256 feeTakeXfer = SafeMath.mul(amount, feeTake) / (1 ether); uint256 feeRebateXfer = 0; + + //0 = regular user (pays take fee and make fee) + //1 = market maker silver (pays take fee, no make fee, gets rebate) + //2 = market maker gold (pays take fee, no make fee, gets entire counterparty's take fee as rebate) if (accountLevelsAddr != address(0)) { uint256 accountLevel = AccountLevels(accountLevelsAddr) .accountLevel(user); @@ -456,18 +456,22 @@ contract EtherDelta { feeRebateXfer = SafeMath.mul(amount, feeRebate) / (1 ether); if (accountLevel == 2) feeRebateXfer = feeTakeXfer; } + // 对taker收费 tokens[tokenGet][msg.sender] = SafeMath.sub( tokens[tokenGet][msg.sender], SafeMath.add(amount, feeTakeXfer) ); + //对maker收费 tokens[tokenGet][user] = SafeMath.add( tokens[tokenGet][user], SafeMath.sub(SafeMath.add(amount, feeRebateXfer), feeMakeXfer) ); + //feeAccount 账户收费 tokens[tokenGet][feeAccount] = SafeMath.add( tokens[tokenGet][feeAccount], SafeMath.sub(SafeMath.add(feeMakeXfer, feeTakeXfer), feeRebateXfer) ); + // 对maker买入token收费, tokens[tokenGive][user] = SafeMath.sub( tokens[tokenGive][user], SafeMath.mul(amountGive, amount) / amountGet diff --git a/basic/13-decentralized-exchange/README-CN.md b/basic/13-decentralized-exchange/README-CN.md index beb913b65..b7e863f3f 100644 --- a/basic/13-decentralized-exchange/README-CN.md +++ b/basic/13-decentralized-exchange/README-CN.md @@ -1,11 +1,12 @@ 中文 / [English](https://github.com/Dapp-Learning-DAO/Dapp-Learning/blob/main/basic/13-decentralized-exchange/README.md) ## 简介 -本样例主要介绍 etherdelta 合约逻辑, 演示对 etherdelta 合约的基本使用. -有去中心化交易所, 有 etherdelta 订单薄模式和uniswap的AMM做市商模式。 -待集成 0x & bancor & kyber +去中心化交易所, 有 etherdelta 订单薄模式和uniswap的AMM做市商模式。 +其他类型有 0x & bancor & 1inch +了解etherdelta交易所是了解其他订单薄模式交易所的基础。而了解uniswap v1是了解AMM的基础。 ### EtherDelta +本样例主要介绍 etherdelta 合约逻辑, 演示对 etherdelta 合约的基本使用. [详情参见 EtherDelta解析和测试 :point_right:](./EtherDelta/README.md) @@ -15,6 +16,9 @@ [详情参见 uniswap-v1-like :point_right:](./uniswap-v1-like/README.md) + +### backup +为了方便演示以及兼容hardhat我们做了微调。 backup 目录是原版代码。 ## 参考链接 https://github.com/etherdelta/smart_contract/blob/master/etherdelta.sol diff --git a/basic/13-decentralized-exchange/EtherDelta/backup/deploy.old.js b/basic/13-decentralized-exchange/backup/etherdelta/deploy.old.js similarity index 100% rename from basic/13-decentralized-exchange/EtherDelta/backup/deploy.old.js rename to basic/13-decentralized-exchange/backup/etherdelta/deploy.old.js diff --git a/basic/13-decentralized-exchange/EtherDelta/backup/etherdelta.old.sol b/basic/13-decentralized-exchange/backup/etherdelta/etherdelta.old.sol similarity index 100% rename from basic/13-decentralized-exchange/EtherDelta/backup/etherdelta.old.sol rename to basic/13-decentralized-exchange/backup/etherdelta/etherdelta.old.sol diff --git a/basic/13-decentralized-exchange/EtherDelta/backup/test.old.js b/basic/13-decentralized-exchange/backup/etherdelta/test.old.js similarity index 100% rename from basic/13-decentralized-exchange/EtherDelta/backup/test.old.js rename to basic/13-decentralized-exchange/backup/etherdelta/test.old.js diff --git a/basic/13-decentralized-exchange/EtherDelta/backup/utility.js b/basic/13-decentralized-exchange/backup/etherdelta/utility.js similarity index 100% rename from basic/13-decentralized-exchange/EtherDelta/backup/utility.js rename to basic/13-decentralized-exchange/backup/etherdelta/utility.js diff --git a/basic/13-decentralized-exchange/uniswap-v1-like/contracts/Exchange.sol b/basic/13-decentralized-exchange/uniswap-v1-like/contracts/Exchange.sol index 0ed1f3102..fedfe3e1d 100644 --- a/basic/13-decentralized-exchange/uniswap-v1-like/contracts/Exchange.sol +++ b/basic/13-decentralized-exchange/uniswap-v1-like/contracts/Exchange.sol @@ -120,20 +120,7 @@ contract Exchange is ERC20 { return getAmount(_tokenSold, tokenReserve, address(this).balance); } - // 使用eth购买token - // 拆分为 ethToToken 和 ethToTokenSwap - // function ethToTokenSwap(uint256 _minTokens) public payable { - // uint256 tokenReserve = getReserve(); - // uint256 tokensBought = getAmount( - // msg.value, - // address(this).balance - msg.value, - // tokenReserve - // ); - - // require(tokensBought >= _minTokens, "insufficient output amount"); - - // IERC20(tokenAddress).transfer(msg.sender, tokensBought); - // } + function ethToToken(uint256 _minTokens, address recipient) private { uint256 tokenReserve = getReserve(); diff --git a/defi/0x-protocal/README.md b/defi/0x-protocal/README.md index 8b9bfd2a1..32a8fd8ad 100644 --- a/defi/0x-protocal/README.md +++ b/defi/0x-protocal/README.md @@ -47,7 +47,7 @@ https://docs.0x.org/0x-api-swap/guides/accessing-rfq-liquidity-on-0x-api ### How to Build NFT Exchange in Your DApp -//todo +https://0x.org/docs/0x-swap-api/guides/how-to-build-a-token-swap-dapp-with-0x-api ### 限价单 有两种类型的单子: limit order & RFQ order @@ -60,4 +60,5 @@ https://docs.0x.org/0x-api-orderbook/introduction - github: - 0x文档: - video: https://www.web3.university/tracks/road-to-web3/build-a-blockchain-betting-game +- How to Get 0x and Matcha Data: https://0x.org/docs/developer-resources/how-to-get-0x-and-matcha-data diff --git a/defi/0x-protocal/demo/README.md b/defi/0x-protocal/demo/README.md index 39c17df48..e5f5f23e9 100644 --- a/defi/0x-protocal/demo/README.md +++ b/defi/0x-protocal/demo/README.md @@ -11,13 +11,14 @@ npx hardhat run scripts/deploy.js ``` ## direct-swap https://github.com/0xProject/0x-api-starter-guide-code/blob/master/src/direct-swap.js#L105-L113 -//todo +https://0x.org/docs/0x-swap-api/guides/swap-tokens-with-0x-swap-api ## contract-swap //todo ## limit order swap https://github.com/0xProject/0x-starter-project/tree/master/src + //todo ## nft marketplace diff --git a/defi/0x-protocal/demo/hardhat.config.js b/defi/0x-protocal/demo/hardhat.config.js index 86913e705..ad11ef7b2 100644 --- a/defi/0x-protocal/demo/hardhat.config.js +++ b/defi/0x-protocal/demo/hardhat.config.js @@ -1,6 +1,57 @@ require("@nomicfoundation/hardhat-toolbox"); +require('dotenv').config(); -/** @type import('hardhat/config').HardhatUserConfig */ +// This is a sample Hardhat task. To learn how to create your own go to +// https://hardhat.org/guides/create-task.html +task('accounts', 'Prints the list of accounts', async () => { + const accounts = await ethers.getSigners(); + + for (const account of accounts) { + console.log(account.address); + } +}); + +function mnemonic() { + return process.env.PRIVATE_KEY; +} + +/** + * @type import('hardhat/config').HardhatUserConfig + */ module.exports = { - solidity: "0.8.17", + solidity: '0.8.17', + networks: { + localhost: { + url: 'http://localhost:8545', + //gasPrice: 125000000000, // you can adjust gasPrice locally to see how much it will cost on production + /* + notice no mnemonic here? it will just use account 0 of the hardhat node to deploy + (you can put in a mnemonic here to set the deployer locally) + */ + }, + goerli: { + url: 'https://goerli.infura.io/v3/' + process.env.INFURA_ID, //<---- CONFIG YOUR INFURA ID IN .ENV! (or it won't work) + accounts: [mnemonic()], + }, + mainnet: { + url: 'https://eth-mainnet.g.alchemy.com/v2/' + process.env.ALCHEMY, //<---- CONFIG YOUR INFURA ID IN .ENV! (or it won't work) + accounts: [mnemonic()], + }, + ropsten: { + url: 'https://ropsten.infura.io/v3/' + process.env.INFURA_ID, //<---- CONFIG YOUR INFURA ID IN .ENV! (or it won't work) + accounts: [mnemonic()], + }, + arbitest: { + url: 'https://arbitrum-rinkeby.infura.io/v3/' + process.env.INFURA_ID, //<---- CONFIG YOUR INFURA ID IN .ENV! (or it won't work) + accounts: [mnemonic()], + }, + matic: { + url: 'https://polygon-mainnet.infura.io/v3/' + process.env.PROJECT_ID, //<---- CONFIG YOUR INFURA ID IN .ENV! (or it won't work) + accounts: [mnemonic()] + }, + op: { + url: 'https://opt-mainnet.g.alchemy.com/v2/' + process.env.ALCHEMY, + accounts: [mnemonic()], + }, + }, }; diff --git a/defi/0x-protocal/demo/package.json b/defi/0x-protocal/demo/package.json index 7895c7df2..6c268796c 100644 --- a/defi/0x-protocal/demo/package.json +++ b/defi/0x-protocal/demo/package.json @@ -2,7 +2,18 @@ "name": "hardhat-project", "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^2.0.0", + "@types/node": "^20.11.16", + "dotenv": "^16.0.3", "hardhat": "^2.12.4", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "qs": "6.11.2", + "typescript": "^5.3.3", + "yesno": "0.4.0" + }, + "dependencies": { + "@1inch/limit-order-protocol-utils": "^4.0.13-alpha", + "@0x/protocol-utils":"11.24.2", + "@0x/contract-addresses":"8.13.0", + "web3": "4.2.2" } } diff --git a/defi/0x-protocal/demo/scripts/0xdirect-swap.js b/defi/0x-protocal/demo/scripts/0xdirect-swap.js new file mode 100644 index 000000000..6adef25b0 --- /dev/null +++ b/defi/0x-protocal/demo/scripts/0xdirect-swap.js @@ -0,0 +1,65 @@ +const hre = require("hardhat"); +const fetch = require('node-fetch'); +const qs = require('qs'); +require('dotenv').config(); + +//https://github.com/0xProject/0x-api-starter-guide-code/blob/master/src/direct-swap.js#L105-L113 +async function main() { + + //const API_QUOTE_URL = 'https://api.0x.org/swap/v1/quote'; + const API_QUOTE_URL = 'https://optimism.api.0x.org/swap/v1/quote'; + console.info(`Fetching swap quote from 0x-API to sell WETH for DAI...`); + + const exchangeOP="0xdef1abe32c034e558cdd535791643c58a13acc10"; + const apiKey = process.env.ZEROXAPIKEY; + + const usdcTokenAddress = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'; // Replace with actual USDC token address + const opTokenAddress = '0x4200000000000000000000000000000000000042'; // Replace with actual OP token address + const wethTokenAddress = '0x4200000000000000000000000000000000000006'; // Replace with actual OP token address + + const params = { + // Not all token symbols are supported. The address of the token can be used instead. + sellToken:usdcTokenAddress , + buyToken: opTokenAddress, + // Note that the DAI token uses 18 decimal places, so `sellAmount` is `100 * 10^18`. + sellAmount: '5000000', + } + const quoteUrl = `${API_QUOTE_URL}?${qs}`; + + // const headers = { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` } } + + // const response = await fetch( + // `https://api.0x.org/swap/v1/price?${qs.stringify(params)}`, {headers: {'0x-api-key': apiKey}} + // ); + + const response = await fetch( + `https://optimism.api.0x.org/swap/v1/quote?${qs.stringify(params)}`, {headers: {'0x-api-key': apiKey}} +); + + const quote = await response.json(); + console.log(quote) + + console.info(`Received a quote ${quote}`); + console.info(`Received a quote with price ${quote.price}`); + + + const [signer] = await ethers.getSigners(); + + console.log("send transaction ----") + // +// await signer.sendTransaction({ +// gasLimit: quote.gas, +// gasPrice: quote.gasPrice, +// to: quote.to, +// data: quote.data, +// value: quote.value, +// chainId: quote.chainId, +// }); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/defi/0x-protocal/demo/scripts/0xlimitswap.js b/defi/0x-protocal/demo/scripts/0xlimitswap.js new file mode 100644 index 000000000..e47d0fea4 --- /dev/null +++ b/defi/0x-protocal/demo/scripts/0xlimitswap.js @@ -0,0 +1,87 @@ +const hre = require("hardhat"); +const fetch = require('node-fetch'); +const qs = require('qs'); +require('dotenv').config(); + +const utils = require("@0x/protocol-utils"); +const contractAddresses = require("@0x/contract-addresses"); + + +//https://github.com/0xProject/0x-api-starter-guide-code/blob/master/src/direct-swap.js#L105-L113 +async function main() { + + //const API_QUOTE_URL = 'https://api.0x.org/swap/v1/quote'; + const API_QUOTE_URL = 'https://optimism.api.0x.org/swap/v1/quote'; + console.info(`Fetching swap quote from 0x-API to sell WETH for DAI...`); + + const exchangeOP="0xdef1abe32c034e558cdd535791643c58a13acc10"; + const apiKey = process.env.ZEROXAPIKEY; + + const usdcTokenAddress = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'; // Replace with actual USDC token address + const opTokenAddress = '0x4200000000000000000000000000000000000042'; // Replace with actual OP token address + const wethTokenAddress = '0x4200000000000000000000000000000000000006'; // Replace with actual OP token address + const walletAddress= process.env.ADDRESS; + + + + const getFutureExpiryInSeconds = () => +Math.floor(Date.now() / 1000 + 300).toString(); // 5 min expiry + +let CHAIN_ID = '10'; +const addresses = contractAddresses.getContractAddressesForChainOrThrow( + CHAIN_ID +); + + const order = new utils.LimitOrder({ + makerToken: usdcTokenAddress, + takerToken: opTokenAddress, + makerAmount: "500000000", // NOTE: This is 1 WEI, 1 ETH would be 1000000000000000000 + takerAmount: "2000000000000000000", // NOTE this is 0.001 ZRX. 1 ZRX would be 1000000000000000000 + maker: walletAddress, + + expiry: getFutureExpiryInSeconds(), + salt: Date.now().toString(), + chainId: CHAIN_ID, + verifyingContract: addresses.exchangeProxy + }); + const signature = await order.getSignatureWithKey( + process.env.PRIVATE_KEY, + utils.SignatureType.EIP712 // Optional + ); + +// const response = await fetch( +// `https://optimism.api.0x.org/swap/v1/quote?${qs.stringify(params)}`, {headers: {'0x-api-key': apiKey}} +// ); + +console.log(`Signature: ${JSON.stringify(signature, undefined, 2)}`); + +const signedOrder = { ...order, signature }; +console.log(`Signed Order: ${JSON.stringify(signedOrder, undefined, 2)}`); + + +const resp = await fetch("https://optimism.api.0x.org/orderbook/v1/order", { + method: "POST", + body: JSON.stringify(signedOrder), + headers: { + '0x-api-key': apiKey + } +}); +//todo 401 + +if (resp.status === 200) { + console.log("Successfully posted order to SRA"); +} else { + const body = await resp.json(); + console.log( + `ERROR(status code ${resp.status}): ${JSON.stringify(body, undefined, 2)}` + ); +} +} + + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/defi/0x-protocal/demo/scripts/1inch-offchain-oracle.js b/defi/0x-protocal/demo/scripts/1inch-offchain-oracle.js new file mode 100644 index 000000000..f13580da3 --- /dev/null +++ b/defi/0x-protocal/demo/scripts/1inch-offchain-oracle.js @@ -0,0 +1,93 @@ +const {Web3} = require('web3'); +const { BigNumber } = require('ethers'); + +const apiKey = process.env.APIKEY; +//const web3 = new Web3(`https://mainnet.infura.io/v3/${yourInfuraKey}`); +let yourInfuraKey = process.env.ALCHEMY; +const web3 = new Web3(`https://eth-mainnet.g.alchemy.com/v2/${yourInfuraKey}`); + +// eslint-disable-next-line max-len +const OffChainOracleAbi = '[{"inputs":[{"internalType":"contract MultiWrapper","name":"_multiWrapper","type":"address"},{"internalType":"contract IOracle[]","name":"existingOracles","type":"address[]"},{"internalType":"enum OffchainOracle.OracleType[]","name":"oracleTypes","type":"uint8[]"},{"internalType":"contract IERC20[]","name":"existingConnectors","type":"address[]"},{"internalType":"contract IERC20","name":"wBase","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IERC20","name":"connector","type":"address"}],"name":"ConnectorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IERC20","name":"connector","type":"address"}],"name":"ConnectorRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract MultiWrapper","name":"multiWrapper","type":"address"}],"name":"MultiWrapperUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IOracle","name":"oracle","type":"address"},{"indexed":false,"internalType":"enum OffchainOracle.OracleType","name":"oracleType","type":"uint8"}],"name":"OracleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IOracle","name":"oracle","type":"address"},{"indexed":false,"internalType":"enum OffchainOracle.OracleType","name":"oracleType","type":"uint8"}],"name":"OracleRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"contract IERC20","name":"connector","type":"address"}],"name":"addConnector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IOracle","name":"oracle","type":"address"},{"internalType":"enum OffchainOracle.OracleType","name":"oracleKind","type":"uint8"}],"name":"addOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"connectors","outputs":[{"internalType":"contract IERC20[]","name":"allConnectors","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"srcToken","type":"address"},{"internalType":"contract IERC20","name":"dstToken","type":"address"},{"internalType":"bool","name":"useWrappers","type":"bool"}],"name":"getRate","outputs":[{"internalType":"uint256","name":"weightedRate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"srcToken","type":"address"},{"internalType":"bool","name":"useSrcWrappers","type":"bool"}],"name":"getRateToEth","outputs":[{"internalType":"uint256","name":"weightedRate","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"multiWrapper","outputs":[{"internalType":"contract MultiWrapper","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracles","outputs":[{"internalType":"contract IOracle[]","name":"allOracles","type":"address[]"},{"internalType":"enum OffchainOracle.OracleType[]","name":"oracleTypes","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"connector","type":"address"}],"name":"removeConnector","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IOracle","name":"oracle","type":"address"},{"internalType":"enum OffchainOracle.OracleType","name":"oracleKind","type":"uint8"}],"name":"removeOracle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract MultiWrapper","name":"_multiWrapper","type":"address"}],"name":"setMultiWrapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]'; +const offChainOracleAddress = '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb'; +const offChainOracleContract = new web3.eth.Contract(JSON.parse(OffChainOracleAbi), offChainOracleAddress); + +const token = { + address: '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT + decimals: 6, +}; + +console.log("---------------"); +offChainOracleContract.methods.getRateToEth( + token.address, // source token + true, // use source wrappers +).call() + .then((rate) => { + const numerator = BigNumber.from(10).pow(token.decimals); + const denominator = BigNumber.from(10).pow(18); // eth decimals + const price = BigNumber.from(rate).mul(numerator).div(denominator); + console.log(price.toString()); // 472685293218315 + }) + .catch(console.log); + + //------------------- + + // eslint-disable-next-line max-len + const MultiCallAbi = '[{"inputs":[{"components":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct MultiCall.Call[]","name":"calls","type":"tuple[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"},{"internalType":"bool[]","name":"success","type":"bool[]"}],"stateMutability":"view","type":"function"}]'; + // eslint-disable-next-line max-len + const multiCallContract = new web3.eth.Contract(JSON.parse(MultiCallAbi), '0xda3c19c6fe954576707fa24695efb830d9cca1ca'); + + const tokens = [ + { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI + decimals: 18, + }, + { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC + decimals: 6, + }, + { + address: '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT + decimals: 6, + }, { + address: '0x111111111117dc0aa78b770fa6a738034120c302', // 1INCH + decimals: 18, + }, + ]; + + const callData = tokens.map((token) => ({ + to: offChainOracleAddress, + data: offChainOracleContract.methods.getRateToEth( + token.address, + true, // use wrapper + ).encodeABI(), + })); + + multiCallContract.methods.multicall(callData).call() + .then(({ + results, + success, + }) => { + const prices = {}; + for (let i = 0; i < results.length; i++) { + if (!success[i]) { + continue; + } + + const decodedRate = web3.eth.abi.decodeParameter('uint256', results[i]).toString(); + const numerator = BigNumber.from(10).pow(tokens[i].decimals); + const denominator = BigNumber.from(10).pow(18); // eth decimals + const price = BigNumber.from(decodedRate).mul(numerator).div(denominator); + prices[tokens[i].address] = price.toString(); + } + console.log(prices); + /* + { + '0x6b175474e89094c44da98b954eedeac495271d0f': '527560209915550', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': '507746821617073', + '0xdac17f958d2ee523a2206206994597c13d831ec7': '529527134930000', + '0x111111111117dc0aa78b770fa6a738034120c302': '1048752594621361' + } + */ + }) + .catch(console.log); + diff --git a/defi/0x-protocal/demo/scripts/1inch.js b/defi/0x-protocal/demo/scripts/1inch.js new file mode 100644 index 000000000..39c119f06 --- /dev/null +++ b/defi/0x-protocal/demo/scripts/1inch.js @@ -0,0 +1,158 @@ +const { ethers } = require('hardhat'); +const { Web3 } = require("web3"); +require('dotenv').config(); +const fetch = require('node-fetch'); + +const apiKey = process.env.APIKEY; +const chainId = 10; // Chain ID for Optimism +const optimismRpcUrl = 'https://mainnet.optimism.io'; + +const broadcastApiUrl = 'https://api.1inch.dev/tx-gateway/v1.1/' + chainId + '/broadcast'; + +const apiBaseUrl = `https://api.1inch.dev/swap/v5.2/${chainId}`; +let yourInfuraKey = process.env.ALCHEMY; +const web3 = new Web3(`https://opt-mainnet.g.alchemy.com/v2/${yourInfuraKey}`); + + + +async function checkAllowance(tokenAddress, walletAddress) { + const url = `${apiBaseUrl}/approve/allowance?tokenAddress=${tokenAddress}&walletAddress=${walletAddress}`; + const response = await fetch(url, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` } }); + const data = await response.json(); + return data.allowance; + } + +// async function approveToken(tokenAddress, spenderAddress) { +// const url = `${apiBaseUrl}/approve/transaction?tokenAddress=${tokenAddress}&spenderAddress=${spenderAddress}`; +// const response = await fetch(url, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` } }); +// const transaction = await response.json(); + +// const wallet = new ethers.Wallet(process.env.PRIVATE_KEY); +// const signedTransaction = await wallet.signTransaction(transaction); +// await ethers.provider.sendTransaction(signedTransaction); +// } + + + +// Sign and post a transaction, return its hash +async function signAndSendTransaction( transaction) { + console.log("sign ----") + const { rawTransaction } = await web3.eth.accounts.signTransaction(transaction, process.env.PRIVATE_KEY); + console.log("rawTransaction: ", rawTransaction) + return await broadCastRawTransaction(rawTransaction); + } + + +async function broadCastRawTransaction(rawTransaction) { + const response = await fetch(broadcastApiUrl, { + method: 'post', + body: JSON.stringify({ rawTransaction }), + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, + }); + + const data = await response.json(); + console.log("broadCast", data) + return data.transactionHash; + } + + +function apiRequestUrl(methodName, queryParams) { + let s = apiBaseUrl + methodName + '?' + (new URLSearchParams(queryParams)).toString(); + + return s + } + + async function buildTxForApproveTradeWithRouter(tokenAddress, amount) { + const url = apiRequestUrl( + '/approve/transaction', + amount ? { tokenAddress, amount } : { tokenAddress } + ); + + const response = await fetch(url, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` } }); + + + const transaction = await response.json(); + + console.log("transaction: ", transaction) + + const gasLimit = await ethers.provider.estimateGas({ + ...transaction + }); + + console.log("gasLimit: ", gasLimit) + + return { + ...transaction, + gas: gasLimit, + }; + + + } + + +// async function buildSwapTransaction(srcToken, dstToken, amount) { +// const url = `${apiBaseUrl}/swap?fromTokenAddress=${srcToken}&toTokenAddress=${dstToken}&amount=${amount}&fromAddress=${wallet.address}`; +// const response = await fetch(url, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` } }); +// const transaction = await response.json(); +// return transaction; +// } + + +async function buildTxForSwap(swapParams) { + const url = apiRequestUrl("/swap", swapParams); + const headers = { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` } } + // Fetch the swap transaction details from the API + const response = await fetch(url, headers); + const data = await response.json(); + return data.tx; + + + } + + function sleep (time) { + return new Promise((resolve) => setTimeout(resolve, time)); + } + + +async function main() { + const usdcTokenAddress = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'; // Replace with actual USDC token address + const opTokenAddress = '0x4200000000000000000000000000000000000042'; // Replace with actual OP token address + const oneinchrouter = '0x1111111254eeb25477b68fb85ed929f73a960582'; + const wallet = new ethers.Wallet(process.env.PRIVATE_KEY); + const [signer] = await ethers.getSigners(); +// const usdcAllowance = await checkAllowance(usdcTokenAddress, wallet.address); +// console.log("usdcAllowance", usdcAllowance) + +// if (usdcAllowance < 1e18) { + // Approve 1inch to spend USDC if allowance is insufficient + // const transactionForSign = await buildTxForApproveTradeWithRouter(usdcTokenAddress, 10000000); + // const approveTxHash = await signAndSendTransaction( transactionForSign); + // console.log("approveTxHash", approveTxHash); + //} + +// // Swap USDC to OP +const swapParams = { + src: usdcTokenAddress, // Token address of 1INCH + dst: opTokenAddress, // Token address of DAI + amount: "5000000", // Amount of 1INCH to swap (in wei) + from: wallet.address, + slippage: 1, // Maximum acceptable slippage percentage for the swap (e.g., 1 for 1%) + disableEstimate: false, // Set to true to disable estimation of swap details + allowPartialFill: false // Set to true to allow partial filling of the swap order + }; + const swapTransaction = await buildTxForSwap(swapParams); // 0.1 USDC + + console.log("swapTransaction ", swapTransaction) + + // await wallet.signTransaction(swapTransaction) //!!! need fix the gas to gaslimit +// const transactionHash = await ethers.provider.sendTransaction(swapTransaction); +// console.log(`Swap transaction sent. Transaction Hash: ${transactionHash}`); + +// await sleep(2000) + + const swapTxHash = await signAndSendTransaction(swapTransaction); + console.log("Swap tx hash: ", swapTxHash); + console.log(`Swap transaction sent. Transaction Hash: ${transactionHash}`); +} + +main().catch((error) => console.error(error)); diff --git a/defi/0x-protocal/demo/scripts/direct-swap.js b/defi/0x-protocal/demo/scripts/direct-swap.js deleted file mode 100644 index 311855d2b..000000000 --- a/defi/0x-protocal/demo/scripts/direct-swap.js +++ /dev/null @@ -1,34 +0,0 @@ -const hre = require("hardhat"); -const fetch = require('node-fetch'); -const qs = require('qs'); - - -//https://github.com/0xProject/0x-api-starter-guide-code/blob/master/src/direct-swap.js#L105-L113 -async function main() { - - const API_QUOTE_URL = 'https://api.0x.org/swap/v1/quote'; - console.info(`Fetching swap quote from 0x-API to sell WETH for DAI...`); - const params = { - // Not all token symbols are supported. The address of the token can be used instead. - sellToken: 'DAI', - buyToken: 'WETH', - // Note that the DAI token uses 18 decimal places, so `sellAmount` is `100 * 10^18`. - sellAmount: '100000000000000000000', - } - const quoteUrl = `${API_QUOTE_URL}?${qs}`; - const response = await fetch( - `https://api.0x.org/swap/v1/quote?${qs.stringify(params)}` - ); - - const quote = await response.json(); - - console.info(`Received a quote ${quote}`); - console.info(`Received a quote with price ${quote.price}`); -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/defi/1inch/readme.md b/defi/1inch/readme.md new file mode 100644 index 000000000..1e6899579 --- /dev/null +++ b/defi/1inch/readme.md @@ -0,0 +1,2 @@ + +1inch Network是一个DEX聚合器解决方案,在多个流动性来源中寻找更便宜的汇率。最初的协议结合了Pathfinder算法,在不同市场之间寻找最佳路径。 \ No newline at end of file