From ed139974ad544688e52f45e745e5fcf2a3b93687 Mon Sep 17 00:00:00 2001 From: XFT <anonymous@xft.eth> Date: Sat, 25 Mar 2023 18:04:33 -0400 Subject: [PATCH] . --- .env | 14 + .gitignore | 2 + Dockerfile | 7 + README.md | 63 ++- config.json | 20 + controllers/configController.js | 68 +++ controllers/relayController.js | 95 ++++ deps/Chainlink.json | 512 ++++++++++++++++++ deps/Shifter.json | 894 ++++++++++++++++++++++++++++++++ main.js | 41 ++ package.json | 20 + 11 files changed, 1735 insertions(+), 1 deletion(-) create mode 100644 .env create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 config.json create mode 100644 controllers/configController.js create mode 100644 controllers/relayController.js create mode 100644 deps/Chainlink.json create mode 100644 deps/Shifter.json create mode 100644 main.js create mode 100644 package.json diff --git a/.env b/.env new file mode 100644 index 0000000..60c52b0 --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +KEY= +RPC= +PORT= +GAS_LIMIT= +REFUND= +PRIORITY_FEE= +CHAIN_ID= +FEE= +SHIFTERS= +ORACLE= +EOA_ONLY= +LIVE_FEE= +HOST= +LIMIT= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25c8fdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..09aebaa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:16.15.0-alpine +WORKDIR /app +COPY package.json ./ +RUN npm install --production +COPY . . +EXPOSE 8080 +CMD node main \ No newline at end of file diff --git a/README.md b/README.md index 06f9c13..1e2aef6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ -# offshift-relayer-public +# Offshift Relayer Server +This relayer server is used to submit withdrawal transactions for the Offshift platform. The included configuration is compatible with Offshift's live deployment of Offshift Anon. You only need to provide your withdrawal account private key and RPC (and optionally, your fee). + +Your chosen fee is denominated in aUSD Wei, deducted from the withdrawal transaction and added to your relayer's account. You will most likely want to keep `LIVE_FEE` enabled as that will charge the actual gas price on top of your set `FEE` value. + +Once your relayer is live, it can be used with either the Offshift frontend or the CLI tool. + +**The relayer has been tested using node v19+** + +# Using the Docker container + +To run your own relayer using a Docker image specified in the `Dockerfile`, follow these steps: + +1. Install Docker on your machine. You can download Docker from the official website: https://www.docker.com/get-started + +2. Clone the repository or download the `Dockerfile` and application files. + +3. Navigate to the directory where the `Dockerfile` and application files are located. + +4. Build the Docker image using the command: +``` +docker build -t relayer . +``` +This command builds a Docker image with the tag `relayer` based on the `Dockerfile` in the current directory. + +You can provide any of the following environment variables as arguments before running your container: + +* `KEY` is the private key of the relayer account (required) + +* `RPC` provide your own RPC url (required) + +* `PORT` select an outgoing port for your relayer + +* `FEE` sets the fee for using your relayer (in aUSD Wei) + +* `LIVE_FEE` will add the current cost of gas to your fee when set to `true` + +5. Run the Docker container using the following command replacing 'key' and 'fee' with your relayer private key and your requested fee in wei: +``` +docker run -p 8080:8080 \ +-e KEY=key \ +-e FEE=fee \ +-e PORT=8080 \ +relayer +``` +This command runs the `relayer` Docker image in a container, maps port 8080 on the host machine to port 8080 in the container, using your set key and fee. + +6. You can verify that the container is working properly by opening a web browser and navigating to `http://localhost:8080/config`. + +If everything is working correctly, you should see the the fee set to your provided value, and the address associated with the private key you created the instance with. + +# Run without Docker + +To run your own instance without using the Docker image, follow these steps: + +1. Clone the repository and open a terminal window at `/relayer`. + +2. Make sure that you have node installed and are using node version 18 or higher. + +3. Run `npm install` to install all of the dependencies. + +4. You can then modify the `.env` file and run with `npm run start` \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..ab60f63 --- /dev/null +++ b/config.json @@ -0,0 +1,20 @@ +{ + "key": "<PRIVATE KEY>", + "rpc": "<RPC URL>", + "port": 80, + "gasLimit": 400000, + "refund": "0", + "priorityFee": "2", + "chainId": "1", + "fee": "0", + "shifters": [ + "0x207793a5a08e1da37b44c874a5006D32cF1d52C8", + "0x3704e2Af0e5a828549F9fF9da93eda10A7CCf402", + "0xd5e9156e77C88f1cC7A49291256adc643A163747" + ], + "oracle": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", + "eoaOnly": true, + "liveFee": true, + "host": "0.0.0.0", + "limit": 10 +} \ No newline at end of file diff --git a/controllers/configController.js b/controllers/configController.js new file mode 100644 index 0000000..7e5c6ad --- /dev/null +++ b/controllers/configController.js @@ -0,0 +1,68 @@ +const config = require('../config.json'); +require('dotenv').config() + +const validKeys = [ + "key", + "rpc", + "port", + "gasLimit", + "refund", + "priorityFee", + "chainId", + "fee", + "shifters", + "oracle", + "eoaOnly", + "liveFee", + "host", + "limit" +]; + +const camelToAllCaps = (str) => { + return str.replace(/[A-Z]/g, (match) => `_${match}`).toUpperCase(); +} + +validKeys.forEach(key => { + let envKey = camelToAllCaps(key) + if(process.env[envKey]){ + if(key == "liveFee" || key == "eoaOnly" || key == "port" || key == "gasLimit" || key == "limit") + config[key] = JSON.parse(process.env[envKey]); + else + config[key] = process.env[envKey]; + } +}) + +global.config = config; +global.config.initialFee = config.fee; + +const configController = { + rateLimiter: async (req, res, next) => { + const currentTime = Date.now(); + if (!req.app.locals.rateLimiter) req.app.locals.rateLimiter = {} + if (!req.app.locals.rateLimiter[req.ip]) { + req.app.locals.rateLimiter[req.ip] = { + requests: [currentTime], + lastChecked: currentTime, + }; + } + const rateLimiter = req.app.locals.rateLimiter[req.ip]; + rateLimiter.requests.push(currentTime); + if (currentTime - rateLimiter.lastChecked > 60000) { + rateLimiter.requests = rateLimiter.requests.filter( + (time) => currentTime - time < 60000 + ); + rateLimiter.lastChecked = currentTime; + } + if (rateLimiter.requests.length > config.limit) res.json({ error: "Rate limit exceeded" }); + else next(); + }, + getConfig: async (relayController, res) => res.json({ + refund: config.refund, + chainId: config.chainId, + fee: (BigInt(config.initialFee) + (config.liveFee ? BigInt(relayController.liveFee()) : BigInt("0"))).toString(), + address: relayController.address, + shifters: config.shifters + }) +} + +module.exports = { configController } \ No newline at end of file diff --git a/controllers/relayController.js b/controllers/relayController.js new file mode 100644 index 0000000..8b02dff --- /dev/null +++ b/controllers/relayController.js @@ -0,0 +1,95 @@ +const Web3 = require('web3') +const web3 = new Web3(config.rpc); +const key = config.key; +let shifter; +const signer = web3.eth.accounts.privateKeyToAccount(config.key); + +const refund = web3.utils.toWei(config.refund) + +const validShifters = config.shifters.map(i => i.toLowerCase()); +const shifterABI = require('../deps/Shifter.json').abi; +const shifters = validShifters.map(async (i) => await new web3.eth.Contract(shifterABI, i)); +const oracle = new web3.eth.Contract(require('../deps/Chainlink.json').abi, config.oracle); + +const relayController = { + load: async () => { + await web3.eth.accounts.wallet.add(signer); + + relayController.address = signer.address; + + console.log(`-> Loaded Account: ${relayController.address}`) + }, + relay: async (req, res) => { + if (validShifters.includes(req.body.shifter.toLowerCase())) { + const args = [req.body.proof, ...Object.keys(req.body.args).map(e => req.body.args[e])]; + const tx = await relayController.craft(args, req.body.shifter); + if (tx.error) res.json({ error: tx.error }); + else res.json(tx); + } else res.json({ error: "Invalid shifter" }); + }, + craft: async (args, shifter) => { + shifter = new web3.eth.Contract(shifterABI, shifter); + + const requiredFee = (BigInt(config.initialFee) + (config.liveFee ? BigInt(relayController.liveFee()) : BigInt("0"))) * 90n / 100n; + const paidFee = BigInt(args[5]); + + if (paidFee < requiredFee) return ({ error: "Fee too low." }); + + config.eoaOnly && (await relayController.isEOA(args[3])) || ({ error: "Sender is not an EOA" }) + + let limit; + + try { + limit = await shifter.methods.withdraw(...args).estimateGas({ + from: signer.address, + to: shifter._address, + value: refund + }); + } catch (e) { + console.log(e); + return ({ error: "Gas estimation failed" }); + } + + limit = limit + 50000; + + const balance = await web3.eth.getBalance(signer.address); + if (balance < limit) ({ error: "Insufficient funds" }); + + const tx = { + to: shifter._address, + value: web3.utils.toWei(refund), + gas: limit.toString(), + data: shifter.methods.withdraw(...args).encodeABI() + }; + + signedTx = await web3.eth.accounts.signTransaction(tx, key) + + web3.eth.sendSignedTransaction(signedTx.rawTransaction); + + console.log(`Transaction sent - ${JSON.stringify(signedTx)}`); + + return signedTx; + }, + isEOA: async (address) => { + const code = await web3.eth.getCode(address); + return code === '0x'; + }, + updateLiveFee: async () => { + + let usdPerEth = web3.utils.toBN(await oracle.methods.latestAnswer().call()) + .mul(web3.utils.toBN(1e10)); + + let gasPrice = web3.utils.toBN(await web3.eth.getGasPrice()); + + let totalGasPrice = gasPrice.mul(web3.utils.toBN(config.gasLimit)); + + let requiredFee = usdPerEth + .mul(web3.utils.toBN(totalGasPrice)) + .div(web3.utils.toBN(1e18)); + + config.fee = BigInt(requiredFee).toString(); + }, + liveFee: () => BigInt(config.fee).toString() +} + +module.exports = { relayController } \ No newline at end of file diff --git a/deps/Chainlink.json b/deps/Chainlink.json new file mode 100644 index 0000000..3f2b857 --- /dev/null +++ b/deps/Chainlink.json @@ -0,0 +1,512 @@ +{ + "contractName": "ChainlinkOracle", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_aggregator", + "type": "address" + }, + { + "internalType": "address", + "name": "_accessController", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "int256", + "name": "current", + "type": "int256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "roundId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + } + ], + "name": "AnswerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "roundId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "startedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + } + ], + "name": "NewRound", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "accessController", + "outputs": [ + { + "internalType": "contract AccessControllerInterface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "aggregator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_aggregator", + "type": "address" + } + ], + "name": "confirmAggregator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "description", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_roundId", + "type": "uint256" + } + ], + "name": "getAnswer", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint80", + "name": "_roundId", + "type": "uint80" + } + ], + "name": "getRoundData", + "outputs": [ + { + "internalType": "uint80", + "name": "roundId", + "type": "uint80" + }, + { + "internalType": "int256", + "name": "answer", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "uint80", + "name": "answeredInRound", + "type": "uint80" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_roundId", + "type": "uint256" + } + ], + "name": "getTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestAnswer", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestRound", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestRoundData", + "outputs": [ + { + "internalType": "uint80", + "name": "roundId", + "type": "uint80" + }, + { + "internalType": "int256", + "name": "answer", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "uint80", + "name": "answeredInRound", + "type": "uint80" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "name": "phaseAggregators", + "outputs": [ + { + "internalType": "contract AggregatorV2V3Interface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "phaseId", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_aggregator", + "type": "address" + } + ], + "name": "proposeAggregator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "proposedAggregator", + "outputs": [ + { + "internalType": "contract AggregatorV2V3Interface", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint80", + "name": "_roundId", + "type": "uint80" + } + ], + "name": "proposedGetRoundData", + "outputs": [ + { + "internalType": "uint80", + "name": "roundId", + "type": "uint80" + }, + { + "internalType": "int256", + "name": "answer", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "uint80", + "name": "answeredInRound", + "type": "uint80" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposedLatestRoundData", + "outputs": [ + { + "internalType": "uint80", + "name": "roundId", + "type": "uint80" + }, + { + "internalType": "int256", + "name": "answer", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "startedAt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "updatedAt", + "type": "uint256" + }, + { + "internalType": "uint80", + "name": "answeredInRound", + "type": "uint80" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_accessController", + "type": "address" + } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/deps/Shifter.json b/deps/Shifter.json new file mode 100644 index 0000000..623f1ac --- /dev/null +++ b/deps/Shifter.json @@ -0,0 +1,894 @@ +{ + "contractName": "XFTanon", + "abi": [ + { + "inputs": [ + { + "internalType": "contract IVerifier", + "name": "_verifier", + "type": "address" + }, + { + "internalType": "contract IHasher", + "name": "_hasher", + "type": "address" + }, + { + "internalType": "contract IStorage", + "name": "_storage", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_denomination", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_ethDenomination", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_merkleTreeHeight", + "type": "uint32" + }, + { + "internalType": "contract ElasticIERC20", + "name": "_xft", + "type": "address" + }, + { + "internalType": "contract ElasticIERC20", + "name": "_token", + "type": "address" + }, + { + "internalType": "contract IOracle", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "_xftPool", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenPool", + "type": "address" + }, + { + "internalType": "address", + "name": "_weth9", + "type": "address" + }, + { + "internalType": "address", + "name": "_chainlinkFeed", + "type": "address" + }, + { + "internalType": "uint32", + "name": "_interval", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "_flexFeeThreshold", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "leafIndex", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "Deposit", + "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" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "nullifierHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "relayer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "inputs": [], + "name": "FIELD_SIZE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ROOT_HISTORY_SIZE", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZERO_VALUE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allCommitments", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "chainlinkFeed", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "commitmentList", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "commitments", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentRootIndex", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "denomination", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_commitment", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_encryptedNote", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_passwordHash", + "type": "bytes32" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "ethDenomination", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "filledSubtrees", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "flexFeeThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IHasher", + "name": "_hasher", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_left", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_right", + "type": "bytes32" + } + ], + "name": "hashLeftRight", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "hasher", + "outputs": [ + { + "internalType": "contract IHasher", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "interval", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_root", + "type": "bytes32" + } + ], + "name": "isKnownRoot", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_nullifierHash", + "type": "bytes32" + } + ], + "name": "isSpent", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_nullifierHashes", + "type": "bytes32[]" + } + ], + "name": "isSpentArray", + "outputs": [ + { + "internalType": "bool[]", + "name": "spent", + "type": "bool[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "levels", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextIndex", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "noteStorage", + "outputs": [ + { + "internalType": "contract IStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "nullifierHashes", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract IOracle", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "roots", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract ElasticIERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "verifier", + "outputs": [ + { + "internalType": "contract IVerifier", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "weth9", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_proof", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_root", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_nullifierHash", + "type": "bytes32" + }, + { + "internalType": "address payable", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "address payable", + "name": "_relayer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_refund", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "xft", + "outputs": [ + { + "internalType": "contract ElasticIERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "xftPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "i", + "type": "uint256" + } + ], + "name": "zeros", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_interval", + "type": "uint32" + } + ], + "name": "setInterval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "setFlexFeeThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + } + ], + "name": "simpleShift", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getDenomination", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "getCost", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ] + } \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..1f2f618 --- /dev/null +++ b/main.js @@ -0,0 +1,41 @@ +const express = require("express"); +const bodyParser = require('body-parser'); +var cors = require('cors') +const app = express(); +app.use(cors()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); + +console.log(`Starting OffShift Relay `) +console.log(`-> Loading Config Controller`); +const { configController } = require('./controllers/configController.js'); + +console.log(config); +console.log(`-> Loading Relay Controller`); +const { relayController } = require('./controllers/relayController.js'); + +app.use(configController.rateLimiter); + +app.get('/config', (req, res) => configController.getConfig(relayController, res)); +app.post('/relay', relayController.relay); + +app.listen(config.port, config.host, async () => { + await relayController.load(); + if (config.liveFee) { + await relayController.updateLiveFee(); + console.log(`-> Live Fee Enabled. Current fee (aUSD in wei): ${config.fee}`); + setInterval(async () => await relayController.updateLiveFee(), 60000); + } + console.log(`-> Config:\n--> ${JSON.stringify({ + refund: config.refund, + chainId: config.chainId, + fee: parseInt(config.fee).toString(), + address: relayController.address, + shifters: config.shifters + })}`); + console.log(`-> Relay started at http://${config.host}:${config.port}`); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.log(`${new Date} ${reason}`); +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..92c5e4e --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "relayer", + "version": "1.0.0", + "description": "Relayer system for OffshiftAnon.", + "main": "main.js", + "scripts": { + "start": "node main", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "body-parser": "^1.20.0", + "cors": "^2.8.5", + "dotenv": "^16.0.3", + "express": "^4.18.1", + "fs": "^0.0.1-security", + "web3": "^1.7.5" + } +} -- GitLab