diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..251aebd347ffe3714ed21e874bd3d4d81f011785
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+notes.sqlite
+package-lock.json
+txs.db
+.env
\ No newline at end of file
diff --git a/Nargo.toml b/Nargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..d2c6b3a80c1e709aa7ef9ba9f081fe29e2c0d17a
--- /dev/null
+++ b/Nargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "main"
+type = "bin"
+authors = [""]
+compiler_version = "0.10.3"
+
+[dependencies]
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index a680c82cf247c3b7a5a73194a06b850f190f761c..0000000000000000000000000000000000000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# Momiji Sepolia 1
-
diff --git a/controllers/services.mjs b/controllers/services.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..a11e184c7ca3edc921607fe5fae5bd7a7aae9f75
--- /dev/null
+++ b/controllers/services.mjs
@@ -0,0 +1,54 @@
+import inquirer from 'inquirer';
+import { deposit, transact, balance } from '../script/zk.js'
+import { ethers } from 'ethers';
+
+let trees;
+let data;
+
+export const serviceController = {
+    error: "",
+    balance: balance,
+    deposit: async () => {
+
+        let amountPrompt = await inquirer.prompt({
+            type: "input",
+            name: "amount",
+            message: "Enter the amount of XFT to deposit: "
+        })
+
+        if (isNaN(amountPrompt.amount)) {
+            let errorMsg = "No amount specified";
+            serviceController.error = errorMsg;
+            console.log(errorMsg);
+            return;
+        }
+
+        const arg = { amount: ethers.parseEther(amountPrompt.amount) };
+        [trees, data] = await deposit(arg);
+    },
+    withdraw: async () => {
+
+        let amountPrompt = await inquirer.prompt({
+            type: "input",
+            name: "amount",
+            message: "Enter the amount of XFT to withdraw: "
+        })
+
+        if (isNaN(amountPrompt.amount)) {
+            let errorMsg = "No amount specified";
+            serviceController.error = errorMsg;
+            console.log(errorMsg);
+            return;
+        }
+        const args = { amount: ethers.parseEther(amountPrompt.amount), trees: trees, data: data };
+
+        await transact(args);
+    }
+}
+
+export const commandController = {
+    'd': serviceController.deposit,
+    'w': serviceController.withdraw,
+    'r': () => true,
+    'exit': process.exit
+}
\ No newline at end of file
diff --git a/deps/TechnicalPreview.json b/deps/TechnicalPreview.json
new file mode 100644
index 0000000000000000000000000000000000000000..6f4e33f1fdf6a70551b90b5fad6a95df8921a663
--- /dev/null
+++ b/deps/TechnicalPreview.json
@@ -0,0 +1,435 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "TechnicalPreview",
+  "sourceName": "contracts/TechnicalPreview.sol",
+  "abi": [
+    {
+      "inputs": [
+        {
+          "internalType": "contract IVerifier",
+          "name": "_verifier",
+          "type": "address"
+        },
+        {
+          "internalType": "address",
+          "name": "_token",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "nonpayable",
+      "type": "constructor"
+    },
+    {
+      "inputs": [],
+      "name": "MAX_FIELD_SIZE",
+      "outputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "name": "commitments",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "components": [
+            {
+              "internalType": "bytes32[16]",
+              "name": "tx_in",
+              "type": "bytes32[16]"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "current_root",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "amount_public_in",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "amount_public_out",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32[16]",
+              "name": "commitment_out",
+              "type": "bytes32[16]"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "recipient",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "oracle",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "old_root",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "new_root",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32[16]",
+              "name": "nullifier_hashes",
+              "type": "bytes32[16]"
+            }
+          ],
+          "internalType": "struct BatchPublicInputs",
+          "name": "input",
+          "type": "tuple"
+        }
+      ],
+      "name": "flattenBatchPublicInputs",
+      "outputs": [
+        {
+          "internalType": "bytes32[]",
+          "name": "",
+          "type": "bytes32[]"
+        }
+      ],
+      "stateMutability": "pure",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "_commitment",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getCommitment",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "_utxo",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getRootFromUtxo",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32[]",
+          "name": "_nullifierHashes",
+          "type": "bytes32[]"
+        }
+      ],
+      "name": "getSpentNullifiers",
+      "outputs": [
+        {
+          "internalType": "bool[]",
+          "name": "spent",
+          "type": "bool[]"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "_root",
+          "type": "bytes32"
+        }
+      ],
+      "name": "getUtxoFromRoot",
+      "outputs": [
+        {
+          "internalType": "bytes32[]",
+          "name": "",
+          "type": "bytes32[]"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "getValidRoots",
+      "outputs": [
+        {
+          "internalType": "bytes32[]",
+          "name": "",
+          "type": "bytes32[]"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "_nullifierHash",
+          "type": "bytes32"
+        }
+      ],
+      "name": "isSpent",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "merkleRoot",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "name": "nullifierHashes",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "_proof",
+          "type": "bytes"
+        },
+        {
+          "components": [
+            {
+              "internalType": "bytes32[16]",
+              "name": "tx_in",
+              "type": "bytes32[16]"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "current_root",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "amount_public_in",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "amount_public_out",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32[16]",
+              "name": "commitment_out",
+              "type": "bytes32[16]"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "recipient",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "oracle",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "old_root",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32",
+              "name": "new_root",
+              "type": "bytes32"
+            },
+            {
+              "internalType": "bytes32[16]",
+              "name": "nullifier_hashes",
+              "type": "bytes32[16]"
+            }
+          ],
+          "internalType": "struct BatchPublicInputs",
+          "name": "_batch",
+          "type": "tuple"
+        }
+      ],
+      "name": "publish",
+      "outputs": [],
+      "stateMutability": "payable",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        },
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "name": "utxo",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "name": "utxoPrevRoots",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "",
+          "type": "uint256"
+        }
+      ],
+      "name": "validRoots",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [],
+      "name": "verifier",
+      "outputs": [
+        {
+          "internalType": "contract IVerifier",
+          "name": "",
+          "type": "address"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "_proof",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes32[]",
+          "name": "_publicInputs",
+          "type": "bytes32[]"
+        }
+      ],
+      "name": "verifyProof",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/deps/UltraVerifier.json b/deps/UltraVerifier.json
new file mode 100644
index 0000000000000000000000000000000000000000..c83c8a00d1c3fc48f18b05e529ad476a21cc1ce0
--- /dev/null
+++ b/deps/UltraVerifier.json
@@ -0,0 +1,85 @@
+{
+  "_format": "hh-sol-artifact-1",
+  "contractName": "UltraVerifier",
+  "sourceName": "contracts/plonk_vk.sol",
+  "abi": [
+    {
+      "inputs": [],
+      "name": "EC_SCALAR_MUL_FAILURE",
+      "type": "error"
+    },
+    {
+      "inputs": [],
+      "name": "MOD_EXP_FAILURE",
+      "type": "error"
+    },
+    {
+      "inputs": [],
+      "name": "PROOF_FAILURE",
+      "type": "error"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "uint256",
+          "name": "expected",
+          "type": "uint256"
+        },
+        {
+          "internalType": "uint256",
+          "name": "actual",
+          "type": "uint256"
+        }
+      ],
+      "name": "PUBLIC_INPUT_COUNT_INVALID",
+      "type": "error"
+    },
+    {
+      "inputs": [],
+      "name": "PUBLIC_INPUT_GE_P",
+      "type": "error"
+    },
+    {
+      "inputs": [],
+      "name": "PUBLIC_INPUT_INVALID_BN128_G1_POINT",
+      "type": "error"
+    },
+    {
+      "inputs": [],
+      "name": "getVerificationKeyHash",
+      "outputs": [
+        {
+          "internalType": "bytes32",
+          "name": "",
+          "type": "bytes32"
+        }
+      ],
+      "stateMutability": "pure",
+      "type": "function"
+    },
+    {
+      "inputs": [
+        {
+          "internalType": "bytes",
+          "name": "_proof",
+          "type": "bytes"
+        },
+        {
+          "internalType": "bytes32[]",
+          "name": "_publicInputs",
+          "type": "bytes32[]"
+        }
+      ],
+      "name": "verify",
+      "outputs": [
+        {
+          "internalType": "bool",
+          "name": "",
+          "type": "bool"
+        }
+      ],
+      "stateMutability": "view",
+      "type": "function"
+    }
+  ]
+}
diff --git a/main.js b/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f61a1caf3b20afda8ad4cc5ad977f4f151ebb5f
--- /dev/null
+++ b/main.js
@@ -0,0 +1,41 @@
+import inquirer from 'inquirer';
+import { serviceController, commandController } from './controllers/services.mjs'
+
+const mainMenu = async () => {
+
+    let answers = await inquirer.prompt([{
+        type: "list",
+        name: "menu",
+        message: `
+                                         
+        Momiji CLI (PureL1Rollup Sepolia Technical Preview #1)
+
+        Balance: ${await serviceController.balance()}
+
+        \x1b[31m${serviceController.error}\x1b[0m
+        
+        Select an option:\n`,
+        choices: [
+            {
+                value: 'd',
+                name: "\tGENERATE ZKXFT",
+                short: "\n\tShifting to zkXFT"
+            },
+            {
+                value: 'w',
+                name: "\tREDEEM ZKXFT FOR XFT",
+                short: "\n\tShifting back to XFT"
+            },
+            {
+                value: 'exit',
+                name: "\tEXIT\n",
+            }
+        ]
+    }])
+
+    serviceController.error = "";
+    await commandController[answers.menu]();
+    mainMenu();
+}
+
+mainMenu();
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..941a351d3fb9db199d6178b03ae12a04f2bf80f6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,30 @@
+{
+  "name": "momiji-cli",
+  "type": "module",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "directories": {
+    "lib": "lib",
+    "test": "test"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "@aztec/bb.js": "0.3.6",
+    "@ethersproject/abi": "^5.6.4",
+    "@ethersproject/providers": "^5.6.8",
+    "@noir-lang/noir-source-resolver": "1.1.1",
+    "bigint-buffer": "^1.1.5",
+    "circomlibjs": "^0.1.7",
+    "dotenv": "^16.3.1",
+    "ethers": "^6.6.7",
+    "inquirer": "^9.2.10",
+    "keccak256": "^1.0.6",
+    "sequelize": "^6.32.0",
+    "sqlite3": "^5.1.6"
+  }
+}
diff --git a/script/utils/MerkleTree.mjs b/script/utils/MerkleTree.mjs
new file mode 100644
index 0000000000000000000000000000000000000000..318b2b19b94949a8c1c7768fcf12defd3b340e47
--- /dev/null
+++ b/script/utils/MerkleTree.mjs
@@ -0,0 +1,180 @@
+// @ts-ignore -- no types
+import { Fr } from '@aztec/bb.js/dest/node/types/index.js';
+// @ts-ignore -- no types
+
+export function pedersenLeftRight(
+  barretenberg,
+  left,
+  right) {
+
+  let leftBuffer = Fr.fromBufferReduce(Buffer.from(left.slice(2), 'hex'));
+  let rightBuffer = Fr.fromBufferReduce(Buffer.from(right.slice(2), 'hex'));
+  let hashRes = barretenberg.pedersenPlookupCompressFields(leftBuffer, rightBuffer);
+  return hashRes.toString('hex')
+}
+
+export class MerkleTree {
+  zeroValue = "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725"; // sha256("Momiji")
+  levels;
+
+  hashLeftRight;
+  storage;
+  zeros;
+  totalLeaves;
+  barretenberg;
+
+  constructor(
+    levels,
+    barretenberg,
+    defaultLeaves = [],
+    hashLeftRight = pedersenLeftRight) {
+    this.levels = levels;
+    this.hashLeftRight = hashLeftRight;
+    this.storage = new Map();
+    this.zeros = [];
+    this.totalLeaves = 0;
+    this.barretenberg = barretenberg;
+
+    // build zeros depends on tree levels
+    let currentZero = this.zeroValue;
+    this.zeros.push(currentZero);
+    for (let i = 0; i < levels; i++) {
+      currentZero = this.hashLeftRight(barretenberg, currentZero, currentZero);
+      this.zeros.push(currentZero);
+    }
+
+    if (defaultLeaves.length > 0) {
+      this.totalLeaves = defaultLeaves.length;
+
+      // store leaves with key value pair
+      let level = 0;
+      defaultLeaves.forEach((leaf, index) => {
+        this.storage.set(MerkleTree.indexToKey(level, index), leaf);
+      });
+
+      // build tree with initial leaves
+      level++;
+      let numberOfNodesInLevel = Math.ceil(this.totalLeaves / 2);
+      for (level; level <= this.levels; level++) {
+        for (let i = 0; i < numberOfNodesInLevel; i++) {
+          const leftKey = MerkleTree.indexToKey(level - 1, 2 * i);
+          const rightKey = MerkleTree.indexToKey(level - 1, 2 * i + 1);
+
+          const left = this.storage.get(leftKey);
+          const right = this.storage.get(rightKey) || this.zeros[level - 1];
+          if (!left) throw new Error("leftKey not found");
+
+          const node = this.hashLeftRight(barretenberg, left, right);
+          this.storage.set(MerkleTree.indexToKey(level, i), node);
+        }
+        numberOfNodesInLevel = Math.ceil(numberOfNodesInLevel / 2);
+      }
+    }
+  }
+
+  static indexToKey(level, index) {
+    return `${level}-${index}`;
+  }
+
+  getIndex(leaf) {
+    for (const [key, value] of this.storage) {
+      if (value === leaf) {
+        return Number(key.split("-")[1]);
+      }
+    }
+    return -1;
+  }
+
+  root() {
+    return this.storage.get(MerkleTree.indexToKey(this.levels, 0)) || this.zeros[this.levels];
+  }
+
+  proof(indexOfLeaf) {
+    let pathElements = [];
+    let pathIndices = [];
+
+    const leaf = this.storage.get(MerkleTree.indexToKey(0, indexOfLeaf));
+    if (!leaf) throw new Error("leaf not found");
+
+    // store sibling into pathElements and target's indices into pathIndices
+    const handleIndex = (level, currentIndex, siblingIndex) => {
+      const siblingValue = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level];
+      pathElements.push(siblingValue);
+      pathIndices.push(currentIndex % 2);
+    };
+
+    this.traverse(indexOfLeaf, handleIndex);
+
+    return {
+      root: this.root(),
+      pathElements,
+      pathIndices,
+      leaf: leaf,
+    };
+  }
+
+  insert(leaf) {
+    const index = this.totalLeaves;
+    this.update(index, leaf, true);
+    this.totalLeaves++;
+  }
+
+  update(index, newLeaf, isInsert = false) {
+    if (!isInsert && index >= this.totalLeaves) {
+      throw Error("Use insert method for new elements.");
+    } else if (isInsert && index < this.totalLeaves) {
+      throw Error("Use update method for existing elements.");
+    }
+
+    let keyValueToStore = [];
+    let currentElement = newLeaf;
+
+    const handleIndex = (level, currentIndex, siblingIndex) => {
+      const siblingElement = this.storage.get(MerkleTree.indexToKey(level, siblingIndex)) || this.zeros[level];
+
+      let left;
+      let right;
+      if (currentIndex % 2 === 0) {
+        left = currentElement;
+        right = siblingElement;
+      } else {
+        left = siblingElement;
+        right = currentElement;
+      }
+
+      keyValueToStore.push({
+        key: MerkleTree.indexToKey(level, currentIndex),
+        value: currentElement,
+      });
+      currentElement = this.hashLeftRight(this.barretenberg, left, right);
+    };
+
+    this.traverse(index, handleIndex);
+
+    // push root to the end
+    keyValueToStore.push({
+      key: MerkleTree.indexToKey(this.levels, 0),
+      value: currentElement,
+    });
+
+    keyValueToStore.forEach(o => {
+      this.storage.set(o.key, o.value);
+    });
+  }
+
+  // traverse from leaf to root with handler for target node and sibling node
+  traverse(indexOfLeaf, handler) {
+    let currentIndex = indexOfLeaf;
+    for (let i = 0; i < this.levels; i++) {
+      let siblingIndex;
+      if (currentIndex % 2 === 0) {
+        siblingIndex = currentIndex + 1;
+      } else {
+        siblingIndex = currentIndex - 1;
+      }
+
+      handler(i, currentIndex, siblingIndex);
+      currentIndex = Math.floor(currentIndex / 2);
+    }
+  }
+}
\ No newline at end of file
diff --git a/script/utils/helpers.js b/script/utils/helpers.js
new file mode 100644
index 0000000000000000000000000000000000000000..844c20bfd17dc464b48b25092d8e14c0044af1a6
--- /dev/null
+++ b/script/utils/helpers.js
@@ -0,0 +1,318 @@
+import { randomBytes } from 'crypto'
+import { readFileSync } from 'fs';
+import { Fr } from '@aztec/bb.js/dest/node/types/index.js';
+import { MerkleTree } from './MerkleTree.mjs';
+import { keccak256 } from "@ethersproject/keccak256/lib/index.js";
+import fs from 'fs'
+const ZERO_VALUE = "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725"; 
+const MAX_VALUE = Fr.MAX_VALUE; 
+export const treeConfig = {
+  utxoDepth: 4,
+  txDepth: 4,
+  stateDepth: 9
+}
+
+export const circuitConfig = {
+  main: "./src/main.nr"
+}
+const treeSum = [treeConfig.utxoDepth, treeConfig.txDepth, treeConfig.stateDepth].reduce((a, b) => a + b)
+function evalWithScopePrefix(js) {
+  let public_inputs = {};
+  js.split('\n').forEach(line => {
+    const trimmedLine = line.trim();
+    if (trimmedLine.length > 0) {
+      eval(`public_inputs.` + trimmedLine);
+    }
+  });
+  return public_inputs;
+}
+export function getInputsObject() {
+  
+  
+  
+  
+  let regex = /(.*)\: (pub )?(\[.*\; (.*)?\],)?.*/;
+  let circuit = fs.readFileSync(circuitConfig.main).toString();
+  let inputs = {}
+  let input_ordering = []
+  
+  let structLine = circuit.split('\n').find(line => line.includes('fn main'));
+  let circuitSplit = circuit.split('\n');
+  
+  let structLines = circuitSplit.slice(circuitSplit.indexOf(structLine) + 1, circuitSplit.indexOf(circuitSplit.find(line => line.includes(') {'))));
+  let line_maps = structLines.map(line => line.match(regex)).filter(x => x !== null);
+  line_maps.forEach(line_map => {
+    inputs[line_map[1].trim()] = [(line_map[2] ? true : false), (line_map[4] ? line_map[4] : 1)];
+    input_ordering.push(line_map[1].trim());
+  });
+  return [inputs, input_ordering];
+}
+export function getInputs() {
+  let inputs = getInputsObject()[0];
+  return inputs;
+}
+export function getInputsOrdered(pub) {
+  let inputsObj = getInputsObject();
+  let inputs = inputsObj[1];
+  let input_ordering = inputs;
+  if (pub) input_ordering = input_ordering.map(input => inputsObj[0][input][0] ? input : null).filter(x => x !== null);
+  return input_ordering;
+}
+export function getPublicInputsOrdered() {
+  return getInputsOrdered(true);
+}
+export function readToml(path) {
+  let public_inputs = getPublicInputsOrdered();
+  let unordered_inputs = evalWithScopePrefix(fs.readFileSync(path).toString());
+  let ordered_inputs = public_inputs.map(input => unordered_inputs[input]);
+  return ordered_inputs;
+}
+export function randomBytesFr(numBytes) {
+  const bytes = randomBytes(numBytes)
+  const bytesFr = Fr.fromBufferReduce(bytes)
+  return bytesFr
+}
+export const format = (data) => {
+  if (typeof data === "string") return `"${data}"`;
+  if (data.length === 0) return "[]";
+  return `[\n    "${data.join('",\n    "')}"\n]`;
+}
+export const dumpToml = (data) => {
+  let public_inputs = getInputsOrdered();
+  let toml = [];
+  public_inputs.forEach((input) => {
+    toml.push(`            ${input} = ${format(data[input])}`);
+  });
+  
+  toml = toml.join('\n');
+  fs.writeFileSync('./Prover.toml', toml);
+}
+export const dumpSolidity = () => {
+  
+  let contract = fs.readFileSync("./contracts/TechnicalPreview.sol").toString();
+  
+  let structLine = contract.split('\n').find(line => line.includes('BatchPublicInputs'));
+  
+  let public_inputs = getInputsOrdered(true);
+  
+  let public_inputs_sizes = public_inputs.map(input => getInputs()[input][1]);
+  
+  
+  let new_struct = `struct BatchPublicInputs {`;
+  public_inputs.forEach((input, index) => {
+    new_struct += `\n   bytes32${public_inputs_sizes[index] !== 1 ? `[${public_inputs_sizes[index]}]` : ''} ${input};`;
+  });
+  
+  contract = contract.replace(structLine, new_struct);
+  
+  
+  let flattenLine = contract.split('\n').find(line => line.includes('function flattenBatchPublicInputs(BatchPublicInputs memory input) public pure returns (bytes32[] memory) {'));
+  let flattenLineIndex = contract.split('\n').indexOf(flattenLine);
+  let flatArrayLine = `        bytes32[] memory flatArray = new bytes32[](${public_inputs_sizes.reduce((a, b) => parseInt(a) + parseInt(b))});`;
+  contract = contract.split('\n').slice(0, flattenLineIndex + 1).join('\n') + '\n' + flatArrayLine + '\n' + contract.split('\n').slice(flattenLineIndex + 1).join('\n');
+  
+  let idxLine = `        uint256 idx = 0;\n`;
+  contract = contract.split('\n').slice(0, flattenLineIndex + 2).join('\n') + '\n' + idxLine + '\n' + contract.split('\n').slice(flattenLineIndex + 2).join('\n');
+  
+  let forLoopLines = [];
+  public_inputs.forEach((input, index) => {
+    if (public_inputs_sizes[index] === 1) {
+      forLoopLines.push(`        flatArray[idx++] = input.${input};`);
+    } else {
+      forLoopLines.push(`        for (uint i = 0; i < ${public_inputs_sizes[index]}; i++) flatArray[idx++] = input.${input}[i];`);
+    }
+  });
+  contract = contract.split('\n').slice(0, flattenLineIndex + 3).join('\n') + '\n' + forLoopLines.join('\n') + '\n' + contract.split('\n').slice(flattenLineIndex + 3).join('\n');
+  
+  fs.writeFileSync('./contracts/TechnicalPreview.sol', contract);
+}
+export function path_to_uint8array(path) {
+  let buffer = readFileSync(path);
+  return new Uint8Array(buffer);
+}
+const toFixedHex = (number, pad0x, length = 32) => {
+  let hexString = number.toString(16).padStart(length * 2, '0');
+  return (pad0x ? `0x` + hexString : hexString);
+}
+export function getSolidityHash(asset) {
+  return keccak256(asset);
+}
+export function generateHashPathInput(hash_path) {
+  let hash_path_input = [];
+  for (var i = 0; i < hash_path.length; i++) {
+    hash_path_input.push(`0x` + hash_path[i]);
+  }
+  return hash_path_input;
+}
+export function generateUTXO(batchSize, amounts, _secrets, BarretenbergApi) {
+  let utxos = []
+  for (let i = 0; i < batchSize; i++) {
+    let amountBN = amounts[i]
+    let utxo = {
+      secret: _secrets[i],
+      owner: BarretenbergApi.pedersenPlookupCompress([_secrets[i]]),
+      amountBN: amountBN,
+      amount: Fr.fromString(toFixedHex(Number(amountBN.toString()), true)),
+      assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), 'hex')),
+    }
+    utxos.push(utxo)
+  }
+  return utxos
+}
+export async function generateTreeProof(utxoIn, BarretenbergApi, contract) {
+  let trees = {
+    utxo_tree: new MerkleTree(treeConfig.utxoDepth, BarretenbergApi),
+    tx_tree: new MerkleTree(treeConfig.txDepth, BarretenbergApi),
+    historic_tree: new MerkleTree(treeConfig.stateDepth, BarretenbergApi),
+  }
+  let commitment = BarretenbergApi.pedersenPlookupCompress(
+    [utxoIn.owner, utxoIn.amount, utxoIn.assetType]
+  ).toString()
+  let old_root = await contract.getRootFromUtxo(commitment)
+  let utxo_list = await contract.getUtxoFromRoot(old_root)
+  let historic_roots = await contract.getValidRoots()
+  let oracle = BarretenbergApi.pedersenPlookupCompress([new Fr(0n)])
+  
+  for (let i = 0; i < utxo_list.length; i++) {
+    trees.utxo_tree.insert(utxo_list[i]);
+  }
+  
+  
+  let utxo_root = trees.utxo_tree.root()
+  trees.tx_tree.insert(utxo_root);
+  let tx_root_Fr = Fr.fromString(trees.tx_tree.root())
+  let batch = BarretenbergApi.pedersenPlookupCompress([tx_root_Fr, oracle])
+  let new_root = BarretenbergApi.pedersenPlookupCompress([batch, Fr.fromString(old_root)]).toString()
+  
+  for (let i = 0; i < historic_roots.length; i++) {
+    trees.historic_tree.insert(historic_roots[i]);
+  }
+  let proofs = {
+    utxo: {
+      leaf: commitment,
+      index: trees.utxo_tree.getIndex(commitment),
+      root: utxo_root,
+      hash_path: trees.utxo_tree.proof(
+        trees.utxo_tree.getIndex(commitment)
+      ).pathElements
+    },
+    tx: {
+      leaf: utxo_root,
+      index: trees.tx_tree.getIndex(utxo_root),
+      root: trees.tx_tree.root(),
+      hash_path: trees.tx_tree.proof(
+        trees.tx_tree.getIndex(utxo_root)
+      ).pathElements
+    },
+    historic: {
+      leaf: new_root,
+      index: trees.historic_tree.getIndex(new_root),
+      root: trees.historic_tree.root(),
+      hash_path: trees.historic_tree.proof(
+        trees.historic_tree.getIndex(new_root)
+      ).pathElements
+    }
+  }
+  return proofs
+}
+export function generateDataToml(oldRoot, newRoot, trees, api) {
+  let zeroHash = api.pedersenPlookupCompress([Fr.fromString(toFixedHex(0, true))])
+  const data = {
+    tx_in: new Array(16).fill(ZERO_VALUE),
+    secrets: new Array(16).fill('0'),
+    utxo_in: new Array(48).fill('0'),
+    utxo_out: new Array(48).fill('0'),
+    oracle: zeroHash.toString(),
+    old_root_proof: new Array(16).fill('0'),
+    old_root: ZERO_VALUE,
+    new_root: ZERO_VALUE,
+    current_root: trees.historic_tree.root(),
+    indexes: new Array(48).fill('0'),
+    hash_path: new Array(16 * treeSum).fill('0'),
+    commitment_out: new Array(16).fill(ZERO_VALUE),
+    amount_public_in: "0",
+    amount_public_out: "0",
+    nullifier_hashes: new Array(16).fill('0'),
+    recipient: Fr.fromString(toFixedHex(0, true)).toString()
+  }
+  if (oldRoot !== "0") data.old_root = oldRoot;
+  if (newRoot !== "0") data.new_root = newRoot;
+  return data
+}
+export async function generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipient, BarretenbergApi, contract) {
+  let utxoInLen = utxoIn.length
+  let utxoOutLen = utxoOut.length
+  
+  for (let i = 0; i < utxoInLen; i++) {
+    let ownerHex = utxoIn[i].owner.toString();
+    let amountHex = utxoIn[i].amount.toString();
+    let assetTypeHex = utxoIn[i].assetType.toString();
+    let note_commitment = BarretenbergApi.pedersenPlookupCompress([utxoIn[i].owner, utxoIn[i].amount, utxoIn[i].assetType]);
+    let utxoLeaf = note_commitment.toString()
+    data.secrets[i] = utxoIn[i].secret
+    data.nullifier_hashes[i] = BarretenbergApi.pedersenPlookupCompressFields(utxoIn[i].secret, utxoIn[i].secret)
+    data.old_root_proof[i] = await contract.getRootFromUtxo(utxoLeaf)
+    data.utxo_in[i * 3 + 0] = ownerHex
+    data.utxo_in[i * 3 + 1] = amountHex
+    data.utxo_in[i * 3 + 2] = assetTypeHex
+    data.indexes[i * 4 + 0] = treeProof[i].utxo.index
+    data.indexes[i * 4 + 1] = treeProof[i].tx.index
+    data.indexes[i * 4 + 2] = treeProof[i].historic.index
+    let utxoPath = treeProof[i].utxo.hash_path
+    let txPath = treeProof[i].tx.hash_path
+    let historicPath = treeProof[i].historic.hash_path
+    for (let j = 0; j < utxoPath.length; j++) {
+      data.hash_path[i * treeSum + 0 + j] = utxoPath[j]
+      data.hash_path[i * treeSum + treeConfig.utxoDepth + j] = txPath[j]
+    }
+    for (let k = 0; k < historicPath.length; k++) {
+      data.hash_path[i * treeSum + treeConfig.utxoDepth + treeConfig.txDepth + k] = historicPath[k]
+    }
+  }
+  
+  for (let i = 0; i < utxoOutLen; i++) {
+    let ownerHex = utxoOut[i].owner.toString();
+    let amountHex = utxoOut[i].amount.toString();
+    let assetTypeHex = utxoOut[i].assetType.toString();
+    let note_commitment = BarretenbergApi.pedersenPlookupCompress([utxoOut[i].owner, utxoOut[i].amount, utxoOut[i].assetType]);
+    let utxoLeaf = note_commitment.toString()
+    trees.utxo_tree.insert(utxoLeaf)
+    data.utxo_out[i * 3 + 0] = ownerHex
+    data.utxo_out[i * 3 + 1] = amountHex
+    data.utxo_out[i * 3 + 2] = assetTypeHex
+    data.commitment_out[i] = utxoLeaf
+  }
+  data.tx_in[0] = trees.utxo_tree.root()
+  data.amount_public_in = toFixedHex(Number(amountPublic.amountIn.toString()), true)
+  data.amount_public_out = toFixedHex(Number(amountPublic.amountOut.toString()), true)
+  data.recipient = recipient
+  data.current_root = trees.historic_tree.root()
+  dumpToml(data)
+}
+export function generateTestPublish(trees, data, api) {
+  
+  let utxoTree = trees.utxo_tree
+  let txTree = trees.tx_tree
+  let historicTree = trees.historic_tree
+  let utxoRoot = utxoTree.root()
+  txTree.insert(utxoRoot)
+  let txRoot = txTree.root()
+  let txRootFr = Fr.fromBufferReduce(Buffer.from(txRoot.slice(2), 'hex'))
+  let oracleFr = Fr.fromBufferReduce(toFixedHex(0, true))
+  let oracleHash = api.pedersenPlookupCompress([oracleFr])
+  let batch = api.pedersenPlookupCompressFields(txRootFr, oracleHash)
+  let oldHistoricRoot = Fr.fromBufferReduce(Buffer.from(data.new_root.slice(2), 'hex'))
+  let newHistoricRoot = api.pedersenPlookupCompress([batch, oldHistoricRoot])
+  let newHistoricRootHex = newHistoricRoot.toString()
+  historicTree.insert(newHistoricRootHex)
+  data.old_root = oldHistoricRoot.toString()
+  data.new_root = newHistoricRootHex
+  
+  
+  trees.utxoTreeOld = trees.utxo_tree
+  trees.txTreeOld = trees.tx_tree
+  trees.newHistoricRoot = newHistoricRootHex
+  trees.utxo_tree = new MerkleTree(4, api)
+  trees.tx_tree = new MerkleTree(4, api)
+  dumpToml(data)
+}
\ No newline at end of file
diff --git a/script/zk.js b/script/zk.js
new file mode 100644
index 0000000000000000000000000000000000000000..9021214b7e5db7cb4db3c98f73937233d5a8953e
--- /dev/null
+++ b/script/zk.js
@@ -0,0 +1,375 @@
+import { exec, execSync } from "child_process";
+import { newBarretenbergApiSync } from "@aztec/bb.js/dest/node/index.js";
+import { Fr } from "@aztec/bb.js/dest/node/types/index.js";
+import inquirer from "inquirer";
+
+import {
+    generateUTXO,
+    generateDataToml,
+    generateTestTransaction,
+    generateTreeProof,
+    generateTestPublish,
+    randomBytesFr,
+    readToml,
+    getSolidityHash,
+    treeConfig,
+    getInputs,
+    getInputsOrdered,
+    getPublicInputsOrdered,
+} from "./utils/helpers.js";
+import { MerkleTree } from "./utils/MerkleTree.mjs";
+import fs from "fs";
+import { ethers, JsonRpcProvider, ContractFactory } from "ethers";
+import PrivateTransferData from "../deps/TechnicalPreview.json" assert { type: "json" };
+import { Sequelize, DataTypes, Model } from "sequelize";
+import "dotenv/config";
+
+class UTXOdb extends Model {
+    secret;
+    amount;
+    assetType;
+    spent;
+}
+
+let sequelize = new Sequelize({
+    dialect: "sqlite",
+    storage: "txs.db",
+    logging: false,
+});
+
+UTXOdb.init(
+    {
+        secret: {
+            type: DataTypes.STRING,
+            allowNull: false,
+        },
+        amount: {
+            type: DataTypes.STRING,
+            allowNull: false,
+        },
+        assetType: {
+            type: DataTypes.INTEGER,
+        },
+        spent: {
+            type: DataTypes.BOOLEAN,
+        },
+        id: {
+            type: DataTypes.STRING,
+            allowNull: false,
+            primaryKey: true,
+        },
+    },
+    {
+        sequelize: sequelize,
+        tableName: "UTXOs",
+    }
+);
+
+
+await sequelize.sync();
+
+
+const fetchUTXOs = async () =>
+    await UTXOdb.findAll({ where: { spent: false } });
+const fetchSpentUTXOs = async () =>
+    await UTXOdb.findAll({ where: { spent: true } });
+const spendUTXO = async (UTXO) =>
+    await UTXOdb.update({ spent: true }, { where: { id: UTXO } });
+const saveUTXO = async (UTXO) => {
+    await UTXOdb.upsert({
+        secret: UTXO.secret.toString(),
+        amount: UTXO.amount.toString(),
+        assetType: 0,
+        id: UTXO.id.toString(),
+        spent: false,
+    });
+};
+
+const provider = new JsonRpcProvider(process.env.PROVIDER);
+
+const privateKey = process.env.PRIVATE_KEY;
+const wallet = new ethers.Wallet(privateKey, provider);
+
+const technicalPreviewContract = new ethers.Contract(
+    "0x8162d56A21D0ee799Eef055C3acC2b4F776f693a",
+    PrivateTransferData.abi,
+    wallet
+);
+
+export async function balance() {
+    const api = await newBarretenbergApiSync();
+    let utxos = await fetchUTXOs();
+
+    
+    let nullifierArray = [];
+    for (let i = 0; i < utxos.length; i++) {
+        nullifierArray.push(api.pedersenPlookupCompress([
+            Fr.fromString(utxos[i].dataValues.secret),
+            Fr.fromString(utxos[i].dataValues.secret)
+        ]).toString());
+    }
+    let spentArray = await technicalPreviewContract.getSpentNullifiers(nullifierArray);
+    
+    for (let i = 0; i < spentArray.length; i++) {
+        if (spentArray[i] === true) {
+            await spendUTXO(utxos[i].dataValues.id);
+            utxos.splice(i, 1);
+        }
+    }
+
+    if (utxos.length === 0) return ethers.formatEther(BigInt(0));
+    else {
+        let utxosBigInt = utxos.map((res) => BigInt(res.dataValues.amount));
+        return ethers.formatEther(utxosBigInt.reduce((a, b) => a + b));
+    }
+}
+export const deposit = async (args) => {
+    console.log(`** Compiling Transaction Circuit **`);
+
+    execSync(`nargo check`);
+    execSync(`nargo compile`);
+    let recipient, recipient_new;
+    const api = await newBarretenbergApiSync();
+
+    let trees = {
+        utxo_tree: new MerkleTree(4, api),
+        tx_tree: new MerkleTree(4, api),
+        historic_tree: new MerkleTree(9, api),
+        utxoTreeOld: new MerkleTree(4, api),
+        txTreeOld: new MerkleTree(4, api),
+        newHistoricRoot: "",
+    };
+    let utxoIn = [];
+    let utxoOut = [];
+    let treeProof = [];
+    let validRoots = await technicalPreviewContract.getValidRoots();
+    
+    
+    let oldRoot, newRoot;
+    if (validRoots.length === 1) {
+        oldRoot = validRoots[0];
+        newRoot = validRoots[0];
+    } else {
+        oldRoot = validRoots[validRoots.length - 2];
+        newRoot = validRoots[validRoots.length - 1];
+    }
+    let data = generateDataToml(oldRoot, newRoot, trees, api);
+    let amountPublic = {
+        amountIn: BigInt(0),
+        amountOut: BigInt(0),
+    };
+
+    recipient = `0x` + `dEaD`.padStart(64, "0");
+
+    console.log("** Populating UTXO tree.. **");
+
+    console.log("** Generating a batch of zkXFT UTXOs... **");
+
+    let batchSize0 = 1;
+    let secret0 = [];
+    for (let s = 0; s < batchSize0; s++) {
+        secret0.push(randomBytesFr(32));
+    }
+    let amountsOutUTXO = new Array(batchSize0).fill(BigInt(0));
+    amountsOutUTXO[0] = BigInt(args.amount);
+    utxoOut = generateUTXO(batchSize0, amountsOutUTXO, secret0, api);
+
+    amountPublic.amountIn = BigInt(args.amount);
+
+    generateTestTransaction(
+        utxoIn,
+        utxoOut,
+        trees,
+        treeProof,
+        amountPublic,
+        data,
+        recipient,
+        api,
+        technicalPreviewContract
+    );
+    console.log("** Creating Deposit and Publisher Proof... **");
+    generateTestPublish(trees, data, api);
+
+    execSync(`nargo prove`);
+
+    let proof = fs.readFileSync("./proofs/main.proof").toString();
+    proof = `0x` + proof;
+
+    let public_inputs = readToml("./Verifier.toml");
+    let tx = await technicalPreviewContract.publish(proof, public_inputs, {
+        value: 0, 
+    });
+    utxoIn = utxoOut;
+
+    let txReceipt = await provider.getTransactionReceipt(tx.hash);
+    while (txReceipt === null) {
+        txReceipt = await provider.getTransactionReceipt(tx.hash);
+    }
+
+    let utxoId = api.pedersenPlookupCompress([
+        utxoIn[0].owner,
+        utxoIn[0].amount,
+        utxoIn[0].assetType,
+    ]);
+
+    utxoOut[0].id = utxoId;
+
+    await saveUTXO(utxoOut[0]);
+    return [trees, data];
+};
+
+export const transact = async (args) => {
+    const api = await newBarretenbergApiSync();
+    
+    let amount = BigInt(args.amount);
+    let trees = {
+        utxo_tree: new MerkleTree(4, api),
+        tx_tree: new MerkleTree(4, api),
+        historic_tree: new MerkleTree(9, api),
+        utxoTreeOld: new MerkleTree(4, api),
+        txTreeOld: new MerkleTree(4, api),
+        newHistoricRoot: "",
+    };
+
+    console.log("** Populating Historic tree.. **");
+    
+    let validRoots = await technicalPreviewContract.getValidRoots();
+    for (let i = 0; i < validRoots.length; i++) {
+        trees.historic_tree.insert(validRoots[i]);
+    }
+
+    let spending = [];
+    let spendingSum = BigInt(0);
+    
+    let utxo_leaves = (await fetchUTXOs()).map((utxo) => {
+        let utxo_out = {
+            secret: Fr.fromString(utxo.dataValues.secret),
+            owner: api.pedersenPlookupCompress([
+                Fr.fromString(utxo.dataValues.secret),
+            ]),
+            amount: Fr.fromString(utxo.dataValues.amount),
+            assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
+            spent: utxo.dataValues.spent,
+            id: utxo.dataValues.id,
+        };
+        return utxo_out;
+    });
+
+    
+    let utxoOut = [];
+    let changeUTXO = {
+        secret: Fr.fromString("0x00"),
+        owner: api.pedersenPlookupCompress([Fr.fromString("0x00")]),
+        amount: Fr.fromString("0x00"),
+        assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
+    };
+    for (let i = 0; i < utxo_leaves.length; i++) {
+        if (spendingSum < amount) {
+            spending.push(utxo_leaves[i]);
+            spendingSum += BigInt(utxo_leaves[i].amount.toString());
+        }
+        
+        if (spendingSum > amount) {
+            let change = spendingSum - amount;
+            let secret = randomBytesFr(32);
+            changeUTXO = {
+                secret: secret,
+                owner: api.pedersenPlookupCompress([secret]),
+                amount: new Fr(change),
+                assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
+            };
+            changeUTXO.id = api.pedersenPlookupCompress([
+                changeUTXO.owner,
+                changeUTXO.amount,
+                changeUTXO.assetType,
+            ]);
+            utxoOut.push(changeUTXO);
+            break;
+        }
+    }
+
+    
+    let treeProofs = [];
+    for (let i = 0; i < spending.length; i++) {
+        let proofForUtxo = await generateTreeProof(
+            spending[i],
+            api,
+            technicalPreviewContract
+        );
+        treeProofs.push(proofForUtxo);
+    }
+
+    let batchSize1 = 1;
+
+    
+    
+    let oldRoot, newRoot;
+    if (validRoots.length === 1) {
+        oldRoot = validRoots[0];
+        newRoot = validRoots[0];
+    } else {
+        oldRoot = validRoots[validRoots.length - 2];
+        newRoot = validRoots[validRoots.length - 1];
+    }
+
+    let data = generateDataToml(oldRoot, newRoot, trees, api);
+
+    let amountPublic = {
+        amountIn: BigInt(0),
+        amountOut: BigInt(args.amount),
+    };
+
+    
+    let recipientPrompt = await inquirer.prompt({
+        type: "input",
+        name: "recipient",
+        message: "Enter the destination address: ",
+    });
+
+    let recipient_new =
+        `0x` + `${recipientPrompt.recipient.replace("0x", "")}`.padStart(64, "0");
+
+    await generateTestTransaction(
+        spending,
+        utxoOut,
+        trees,
+        treeProofs,
+        amountPublic,
+        data,
+        recipient_new,
+        api,
+        technicalPreviewContract
+    );
+
+    generateTestPublish(trees, data, api);
+
+    console.log(`** Compiling Transaction Circuit **`);
+
+    execSync(`nargo check`);
+    execSync(`nargo compile`);
+    console.log("** Generating transaction Parameters... **");
+
+    execSync(`nargo prove`);
+
+    let proof = fs.readFileSync("./proofs/main.proof").toString();
+    proof = `0x` + proof;
+
+    let public_inputs = readToml("./Verifier.toml");
+
+    let tx = await technicalPreviewContract.publish(proof, public_inputs, {
+        value: 0, 
+    });
+
+    
+    let txReceipt = await provider.getTransactionReceipt(tx.hash);
+    while (txReceipt === null) {
+        console.log("Waiting for tx confirmation...")
+        txReceipt = await provider.getTransactionReceipt(tx.hash);
+    }
+
+    for (let i = 0; i < spending.length; i++) {
+        spendUTXO(spending[i].id);
+    }
+    
+    await saveUTXO(changeUTXO);
+};
diff --git a/src/main.nr b/src/main.nr
new file mode 100644
index 0000000000000000000000000000000000000000..81af6cc19b9b448fec0a2b8d3f0d9a305755dbed
--- /dev/null
+++ b/src/main.nr
@@ -0,0 +1,99 @@
+use dep::std;
+global state_depth = 9;
+global utxo_depth = 4;
+global batch_depth = 4;
+fn main(
+  tx_in: pub [Field; 16],
+  current_root: pub Field,
+  amount_public_in: pub Field,
+  amount_public_out: pub Field,
+  commitment_out: pub [Field; 16],
+  recipient: pub Field,
+  oracle: pub Field,
+  old_root_proof: [Field; 16],
+  old_root: pub Field,
+  new_root: pub Field,
+  nullifier_hashes: pub [Field; 16],
+  secrets: [Field; 16],
+  utxo_in: [Field; 48], 
+  utxo_out: [Field; 48], 
+  indexes: [Field; 48], 
+  hash_path: [Field; 272], 
+) {
+    let trees: Field = 4; 
+    let mut sum_in: Field = amount_public_in;
+    let mut sum_out: Field = amount_public_out;
+    for i in 0..16 {
+        if (utxo_in[i*3 + 1] != 0) {
+            let owner = std::hash::pedersen([secrets[i]]);
+            assert(owner[0] == utxo_in[i*3 + 0]);
+            assert(nullifier_hashes[i] == std::hash::pedersen([secrets[i], secrets[i]])[0]);
+            let commitment_in = std::hash::pedersen([utxo_in[i*3 + 0], utxo_in[i*3 + 1], utxo_in[i*3 + 2]])[0];
+            let mut hash_path_utxo: [Field; utxo_depth] = [0; utxo_depth]; 
+            let mut hash_path_tx: [Field; batch_depth] = [0; batch_depth]; 
+            let mut hash_path_historic: [Field; state_depth] = [0; state_depth]; 
+            for j in 0..4 { 
+                hash_path_utxo[j] = hash_path[(state_depth + utxo_depth + batch_depth) * i + j];
+                hash_path_tx[j] = hash_path[(state_depth + utxo_depth + batch_depth) * i + utxo_depth + j];
+            }
+            for l in 0..state_depth { 
+                hash_path_historic[l] = hash_path[(state_depth + utxo_depth + batch_depth) * i + utxo_depth + batch_depth + l];
+            }
+            let index_utxo = indexes[trees * i + 0];
+            let index_tx = indexes[trees * i + 1];
+            let index_historic = indexes[trees * i + 2];
+            let utxo_root = std::merkle::compute_merkle_root(
+                commitment_in,
+                index_utxo,
+                hash_path_utxo
+                );
+            let tx_root = std::merkle::compute_merkle_root(
+                utxo_root,
+                index_tx,
+                hash_path_tx
+                );
+            let leaf_batch = std::hash::pedersen([tx_root, oracle])[0];
+            let leaf_historic = std::hash::pedersen([leaf_batch, old_root_proof[i]])[0];
+            let historic_root = std::merkle::compute_merkle_root(
+                leaf_historic, 
+                index_historic,
+                hash_path_historic
+                );
+            assert(historic_root == current_root);
+            sum_in += utxo_in[i*3 + 1];
+        }
+    }
+    for k in 0..16 {
+        if (utxo_out[k*3 + 1] != 0) {
+            let commitment_out_calc = std::hash::pedersen([utxo_out[k*3 + 0], utxo_out[k*3 + 1], utxo_out[k*3 + 2]]);
+            assert(commitment_out_calc[0] == commitment_out[k]);
+            sum_out += utxo_out[k*3 + 1];
+        } 
+        else {
+            let zero_hash = 0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725 as Field;
+            assert(commitment_out[k] == zero_hash);
+        }
+    }
+    let utxo_root_calc: Field = pedersen_tree_four(commitment_out);
+    assert(tx_in[0] == utxo_root_calc); 
+    let tx_root_calc: Field = pedersen_tree_four(tx_in); 
+    assert(oracle == std::hash::pedersen([0])[0]); 
+    let batch_root_calc: Field = std::hash::pedersen([tx_root_calc, oracle])[0];
+    let new_root_calc: Field = std::hash::pedersen([batch_root_calc, old_root])[0];
+    assert(new_root == new_root_calc); 
+    assert(sum_in == sum_out);
+    assert(recipient == recipient);
+}
+fn pedersen_tree_four(leaves: [Field; 16]) -> Field {
+    let mut tx_tree: [Field; 16] = leaves; 
+    for l in 0..8 {
+        tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0];
+    }
+    for l in 0..4 {
+        tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0];
+    }
+    for l in 0..2 {
+        tx_tree[l] = std::hash::pedersen([tx_tree[2*l], tx_tree[2*l + 1]])[0];
+    }
+    std::hash::pedersen([tx_tree[0], tx_tree[1]])[0] 
+}
\ No newline at end of file