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