Repository: ProvableHQ/sdk (language: rust)

Issue:
Create a package that can be created via `npm create` to create a basic web app using Aleo that has a similar structure to Aleo.tools
As part of the create-aleo-app, people should be able to use an `npm create` invocation to install the example versions of the Aleo web apps.


Discussion / hints:

Fix patch:
diff --git a/create-aleo-app/README.md b/create-aleo-app/README.md
new file mode 100644
index 000000000..eeb1104b3
--- /dev/null
+++ b/create-aleo-app/README.md
@@ -0,0 +1,34 @@
+# create-aleo-app
+
+## Scaffolding Your First Aleo Project
+
+> **Compatibility Note:**
+> [Node.js](https://nodejs.org/en/) version 18+
+
+With NPM:
+
+```bash
+$ npm create aleo-app@latest
+```
+
+Then follow the prompts!
+
+You can also directly specify the project name and the template you want to use via additional command line options. For example, to scaffold an Aleo + React project, run:
+
+```bash
+# npm 6.x
+npm create aleo-app@latest my-aleo-app --template react
+
+# npm 7+, extra double-dash is needed:
+npm create aleo-app@latest my-aleo-app -- --template react
+```
+
+Currently supported template presets include:
+
+- `react`
+
+You can use `.` for the project name to scaffold in the current directory.
+
+## More Information
+
+Based off of create-vite: https://github.com/vitejs/vite/tree/main/packages/create-vite
\ No newline at end of file
diff --git a/create-aleo-app/build.config.ts b/create-aleo-app/build.config.ts
new file mode 100644
index 000000000..178866390
--- /dev/null
+++ b/create-aleo-app/build.config.ts
@@ -0,0 +1,15 @@
+import { defineBuildConfig } from 'unbuild'
+
+export default defineBuildConfig({
+  entries: ['src/index'],
+  clean: true,
+  rollup: {
+    inlineDependencies: true,
+    esbuild: {
+      minify: true,
+    },
+  },
+  alias: {
+    prompts: 'prompts/lib/index.js',
+  },
+})
diff --git a/create-aleo-app/index.js b/create-aleo-app/index.js
new file mode 100755
index 000000000..f5e8e064a
--- /dev/null
+++ b/create-aleo-app/index.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+import './dist/index.mjs'
diff --git a/create-aleo-app/package.json b/create-aleo-app/package.json
new file mode 100644
index 000000000..e69209d6e
--- /dev/null
+++ b/create-aleo-app/package.json
@@ -0,0 +1,34 @@
+{
+  "name": "create-aleo-app",
+  "version": "0.0.1",
+  "type": "module",
+  "license": "GPL-3.0",
+  "collaborators": [
+    "The Aleo Team <hello@aleo.org>"
+  ],
+  "bin": {
+    "create-aleo-app": "index.js"
+  },
+  "files": [
+    "index.js",
+    "template-*",
+    "dist"
+  ],
+  "scripts": {
+    "dev": "unbuild --stub",
+    "build": "unbuild",
+    "typecheck": "tsc --noEmit",
+    "prepublishOnly": "npm run build"
+  },
+  "engines": {
+    "node": "^14.18.0 || >=16.0.0"
+  },
+  "devDependencies": {
+    "@types/minimist": "^1.2.2",
+    "@types/prompts": "^2.4.4",
+    "kolorist": "^1.8.0",
+    "minimist": "^1.2.8",
+    "prompts": "^2.4.2",
+    "unbuild": "^1.2.1"
+  }
+}
diff --git a/create-aleo-app/src/index.ts b/create-aleo-app/src/index.ts
new file mode 100755
index 000000000..a8ce0827b
--- /dev/null
+++ b/create-aleo-app/src/index.ts
@@ -0,0 +1,314 @@
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import minimist from "minimist";
+import prompts from "prompts";
+import { blue, cyan, red, reset, yellow } from "kolorist";
+
+const argv = minimist<{
+  t?: string;
+  template?: string;
+}>(process.argv.slice(2), { string: ["_"] });
+const cwd = process.cwd();
+
+type ColorFunc = (str: string | number) => string;
+type Framework = {
+  name: string;
+  display: string;
+  color: ColorFunc;
+  variants: FrameworkVariant[];
+};
+type FrameworkVariant = {
+  name: string;
+  display: string;
+  color: ColorFunc;
+  customCommand?: string;
+};
+
+const FRAMEWORKS: Framework[] = [
+  // {
+  //   name: 'vanilla',
+  //   display: 'Vanilla',
+  //   color: yellow,
+  //   variants: [
+  //     {
+  //       name: 'vanilla-ts',
+  //       display: 'TypeScript',
+  //       color: blue,
+  //     },
+  //     {
+  //       name: 'vanilla',
+  //       display: 'JavaScript',
+  //       color: yellow,
+  //     },
+  //   ],
+  // },
+  {
+    name: "react",
+    display: "React",
+    color: cyan,
+    variants: [
+      // {
+      //   name: 'react-ts',
+      //   display: 'TypeScript',
+      //   color: blue,
+      // },
+      {
+        name: "react",
+        display: "JavaScript",
+        color: yellow,
+      },
+    ],
+  },
+  // {
+  //   name: 'node',
+  //   display: 'Node.js',
+  //   color: lightBlue,
+  //   variants: [
+  //     {
+  //       name: 'node',
+  //       display: 'Node.js',
+  //       color: lightBlue,
+  //     },
+  //   ],
+  // },
+];
+
+const TEMPLATES = FRAMEWORKS.map(
+  (f) => (f.variants && f.variants.map((v) => v.name)) || [f.name],
+).reduce((a, b) => a.concat(b), []);
+
+const renameFiles: Record<string, string | undefined> = {
+  _gitignore: ".gitignore",
+};
+
+const defaultTargetDir = "aleo-project";
+
+async function init() {
+  const argTargetDir = formatTargetDir(argv._[0]);
+  const argTemplate = argv.template || argv.t;
+
+  let targetDir = argTargetDir || defaultTargetDir;
+  const getProjectName = () =>
+    targetDir === "." ? path.basename(path.resolve()) : targetDir;
+
+  let result: prompts.Answers<
+    "projectName" | "overwrite" | "packageName" | "framework" | "variant"
+  >;
+
+  try {
+    result = await prompts(
+      [
+        {
+          type: argTargetDir ? null : "text",
+          name: "projectName",
+          message: reset("Project name:"),
+          initial: defaultTargetDir,
+          onState: (state) => {
+            targetDir = formatTargetDir(state.value) || defaultTargetDir;
+          },
+        },
+        {
+          type: () =>
+            !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : "confirm",
+          name: "overwrite",
+          message: () =>
+            (targetDir === "."
+              ? "Current directory"
+              : `Target directory "${targetDir}"`) +
+            ` is not empty. Remove existing files and continue?`,
+        },
+        {
+          type: (_, { overwrite }: { overwrite?: boolean }) => {
+            if (overwrite === false) {
+              throw new Error(red("✖") + " Operation cancelled");
+            }
+            return null;
+          },
+          name: "overwriteChecker",
+        },
+        {
+          type: () => (isValidPackageName(getProjectName()) ? null : "text"),
+          name: "packageName",
+          message: reset("Package name:"),
+          initial: () => toValidPackageName(getProjectName()),
+          validate: (dir) =>
+            isValidPackageName(dir) || "Invalid package.json name",
+        },
+        {
+          type:
+            argTemplate && TEMPLATES.includes(argTemplate) ? null : "select",
+          name: "framework",
+          message:
+            typeof argTemplate === "string" && !TEMPLATES.includes(argTemplate)
+              ? reset(
+                  `"${argTemplate}" isn't a valid template. Please choose from below: `,
+                )
+              : reset("Select a framework:"),
+          initial: 0,
+          choices: FRAMEWORKS.map((framework) => {
+            const frameworkColor = framework.color;
+            return {
+              title: frameworkColor(framework.display || framework.name),
+              value: framework,
+            };
+          }),
+        },
+        {
+          type: (framework: Framework) =>
+            framework && framework.variants ? "select" : null,
+          name: "variant",
+          message: reset("Select a variant:"),
+          choices: (framework: Framework) =>
+            framework.variants.map((variant) => {
+              const variantColor = variant.color;
+              return {
+                title: variantColor(variant.display || variant.name),
+                value: variant.name,
+              };
+            }),
+        },
+      ],
+      {
+        onCancel: () => {
+          throw new Error(red("✖") + " Operation cancelled");
+        },
+      },
+    );
+  } catch (cancelled: any) {
+    console.log(cancelled.message);
+    return;
+  }
+
+  // user choice associated with prompts
+  const { framework, overwrite, packageName, variant } = result;
+
+  const root = path.join(cwd, targetDir);
+
+  if (overwrite) {
+    emptyDir(root);
+  } else if (!fs.existsSync(root)) {
+    fs.mkdirSync(root, { recursive: true });
+  }
+
+  // determine template
+  let template: string = variant || framework?.name || argTemplate;
+
+  const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
+  const pkgManager = pkgInfo ? pkgInfo.name : "npm";
+
+  console.log(`\nScaffolding project in ${root}...`);
+
+  const templateDir = path.resolve(
+    fileURLToPath(import.meta.url),
+    "../..",
+    `template-${template}`,
+  );
+
+  const write = (file: string, content?: string) => {
+    const targetPath = path.join(root, renameFiles[file] ?? file);
+    if (content) {
+      fs.writeFileSync(targetPath, content);
+    } else {
+      copy(path.join(templateDir, file), targetPath);
+    }
+  };
+
+  const files = fs.readdirSync(templateDir);
+  for (const file of files.filter((f) => f !== "package.json")) {
+    write(file);
+  }
+
+  const pkg = JSON.parse(
+    fs.readFileSync(path.join(templateDir, `package.json`), "utf-8"),
+  );
+
+  pkg.name = packageName || getProjectName();
+
+  write("package.json", JSON.stringify(pkg, null, 2) + "\n");
+
+  const cdProjectName = path.relative(cwd, root);
+  console.log(`\nDone. Now run:\n`);
+  if (root !== cwd) {
+    console.log(
+      `  cd ${
+        cdProjectName.includes(" ") ? `"${cdProjectName}"` : cdProjectName
+      }`,
+    );
+  }
+  switch (pkgManager) {
+    default:
+      console.log(`  ${pkgManager} install`);
+      console.log(`  ${pkgManager} run dev`);
+      break;
+  }
+  console.log();
+}
+
+function formatTargetDir(targetDir: string | undefined) {
+  return targetDir?.trim().replace(/\/+$/g, "");
+}
+
+function copy(src: string, dest: string) {
+  const stat = fs.statSync(src);
+  if (stat.isDirectory()) {
+    copyDir(src, dest);
+  } else {
+    fs.copyFileSync(src, dest);
+  }
+}
+
+function isValidPackageName(projectName: string) {
+  return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(
+    projectName,
+  );
+}
+
+function toValidPackageName(projectName: string) {
+  return projectName
+    .trim()
+    .toLowerCase()
+    .replace(/\s+/g, "-")
+    .replace(/^[._]/, "")
+    .replace(/[^a-z\d\-~]+/g, "-");
+}
+
+function copyDir(srcDir: string, destDir: string) {
+  fs.mkdirSync(destDir, { recursive: true });
+  for (const file of fs.readdirSync(srcDir)) {
+    const srcFile = path.resolve(srcDir, file);
+    const destFile = path.resolve(destDir, file);
+    copy(srcFile, destFile);
+  }
+}
+
+function isEmpty(path: string) {
+  const files = fs.readdirSync(path);
+  return files.length === 0 || (files.length === 1 && files[0] === ".git");
+}
+
+function emptyDir(dir: string) {
+  if (!fs.existsSync(dir)) {
+    return;
+  }
+  for (const file of fs.readdirSync(dir)) {
+    if (file === ".git") {
+      continue;
+    }
+    fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
+  }
+}
+
+function pkgFromUserAgent(userAgent: string | undefined) {
+  if (!userAgent) return undefined;
+  const pkgSpec = userAgent.split(" ")[0];
+  const pkgSpecArr = pkgSpec.split("/");
+  return {
+    name: pkgSpecArr[0],
+    version: pkgSpecArr[1],
+  };
+}
+
+init().catch((e) => {
+  console.error(e);
+});
diff --git a/create-aleo-app/template-react/.babelrc b/create-aleo-app/template-react/.babelrc
new file mode 100644
index 000000000..cf5ee0a17
--- /dev/null
+++ b/create-aleo-app/template-react/.babelrc
@@ -0,0 +1,6 @@
+{
+    "presets": [
+        "@babel/preset-env",
+        ["@babel/preset-react", { "runtime": "automatic" }]
+    ]
+}
diff --git a/create-aleo-app/template-react/.eslintrc.cjs b/create-aleo-app/template-react/.eslintrc.cjs
new file mode 100644
index 000000000..4dcb43901
--- /dev/null
+++ b/create-aleo-app/template-react/.eslintrc.cjs
@@ -0,0 +1,20 @@
+module.exports = {
+  root: true,
+  env: { browser: true, es2020: true },
+  extends: [
+    'eslint:recommended',
+    'plugin:react/recommended',
+    'plugin:react/jsx-runtime',
+    'plugin:react-hooks/recommended',
+  ],
+  ignorePatterns: ['dist', '.eslintrc.cjs'],
+  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
+  settings: { react: { version: '18.2' } },
+  plugins: ['react-refresh'],
+  rules: {
+    'react-refresh/only-export-components': [
+      'warn',
+      { allowConstantExport: true },
+    ],
+  },
+}
diff --git a/create-aleo-app/template-react/README.md b/create-aleo-app/template-react/README.md
new file mode 100644
index 000000000..36f0a87fb
--- /dev/null
+++ b/create-aleo-app/template-react/README.md
@@ -0,0 +1,7 @@
+# React + Aleo
+
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/AleoHQ/sdk/tree/testnet3/create-aleo-app/template-react)
+
+This template provides a minimal setup to get React and Aleo working in Vite with HMR and some ESLint rules.
+
+Note: Webpack is currently used for production builds due to a [bug](https://github.com/vitejs/vite/issues/13367) with Vite related to nested workers.
diff --git a/create-aleo-app/template-react/_gitignore b/create-aleo-app/template-react/_gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/create-aleo-app/template-react/_gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/create-aleo-app/template-react/index.html b/create-aleo-app/template-react/index.html
new file mode 100644
index 000000000..57aa8244e
--- /dev/null
+++ b/create-aleo-app/template-react/index.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="public/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Aleo + React</title>
+    <style>
+      body {
+        background: #000000;
+      }
+      .spinner {
+        margin: 150px auto 50px auto;
+        width: 40px;
+        height: 40px;
+        position: relative;
+        text-align: center;
+
+        -webkit-animation: sk-rotate 2s infinite linear;
+        animation: sk-rotate 2s infinite linear;
+      }
+
+      .dot1,
+      .dot2 {
+        width: 60%;
+        height: 60%;
+        display: inline-block;
+        position: absolute;
+        top: 0;
+        background-color: #00C0F9;
+        border-radius: 100%;
+
+        -webkit-animation: sk-bounce 2s infinite ease-in-out;
+        animation: sk-bounce 2s infinite ease-in-out;
+      }
+
+      .dot2 {
+        top: auto;
+        bottom: 0;
+        -webkit-animation-delay: -1s;
+        animation-delay: -1s;
+      }
+
+      @-webkit-keyframes sk-rotate {
+        100% {
+          -webkit-transform: rotate(360deg);
+        }
+      }
+      @keyframes sk-rotate {
+        100% {
+          transform: rotate(360deg);
+          -webkit-transform: rotate(360deg);
+        }
+      }
+
+      @-webkit-keyframes sk-bounce {
+        0%,
+        100% {
+          -webkit-transform: scale(0);
+        }
+        50% {
+          -webkit-transform: scale(1);
+        }
+      }
+
+      @keyframes sk-bounce {
+        0%,
+        100% {
+          transform: scale(0);
+          -webkit-transform: scale(0);
+        }
+        50% {
+          transform: scale(1);
+          -webkit-transform: scale(1);
+        }
+      }
+    </style>
+  </head>
+  <body>
+    <div id="root">
+      <div class="spinner">
+        <div class="dot1"></div>
+        <div class="dot2"></div>
+      </div>
+    </div>
+    <script type="module" src="/src/main.jsx"></script>
+  </body>
+</html>
diff --git a/create-aleo-app/template-react/package.json b/create-aleo-app/template-react/package.json
new file mode 100644
index 000000000..730a4addc
--- /dev/null
+++ b/create-aleo-app/template-react/package.json
@@ -0,0 +1,44 @@
+{
+  "name": "aleo-react-starter",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "webpack --config webpack.config.js",
+    "build:vite": "vite build",
+    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@aleohq/sdk": "0.5.6",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0"
+  },
+  "peerDependencies": {
+    "tslib": "^2.6.1"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.17.2",
+    "@babel/preset-env": "^7.16.11",
+    "@babel/preset-react": "^7.16.7",
+    "@types/react": "^18.2.15",
+    "@types/react-dom": "^18.2.7",
+    "@vitejs/plugin-react": "^4.0.4",
+    "babel-loader": "^8.2.3",
+    "copy-webpack-plugin": "^11.0.0",
+    "css-loader": "^6.6.0",
+    "eslint": "^8.45.0",
+    "eslint-plugin-react": "^7.32.2",
+    "eslint-plugin-react-hooks": "^4.6.0",
+    "eslint-plugin-react-refresh": "^0.4.3",
+    "file-loader": "^6.2.0",
+    "html-webpack-plugin": "^5.5.0",
+    "style-loader": "^3.3.1",
+    "svg-url-loader": "^8.0.0",
+    "vite": "^4.4.5",
+    "webpack": "^5.76.0",
+    "webpack-cli": "^5.1.4",
+    "webpack-dev-server": "^4.15.1"
+  }
+}
diff --git a/create-aleo-app/template-react/public/favicon.ico b/create-aleo-app/template-react/public/favicon.ico
new file mode 100644
index 000000000..3db34eccd
Binary files /dev/null and b/create-aleo-app/template-react/public/favicon.ico differ
diff --git a/create-aleo-app/template-react/src/App.css b/create-aleo-app/template-react/src/App.css
new file mode 100644
index 000000000..b9d355df2
--- /dev/null
+++ b/create-aleo-app/template-react/src/App.css
@@ -0,0 +1,42 @@
+#root {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
diff --git a/create-aleo-app/template-react/src/App.jsx b/create-aleo-app/template-react/src/App.jsx
new file mode 100644
index 000000000..6194f30bb
--- /dev/null
+++ b/create-aleo-app/template-react/src/App.jsx
@@ -0,0 +1,110 @@
+import { useEffect, useState } from "react";
+import reactLogo from "./assets/react.svg";
+import aleoLogo from "./assets/aleo.png";
+import "./App.css";
+import { useAleoWASM } from "./aleo-wasm-hook";
+
+function App() {
+  const [count, setCount] = useState(0);
+  const aleo = useAleoWASM();
+  const [account, setAccount] = useState(null);
+  const [loading, setLoading] = useState(false);
+
+  const generateAccount = () => {
+    setAccount(new aleo.PrivateKey());
+  };
+
+  const [worker, setWorker] = useState(null);
+
+  useEffect(() => {
+    if (worker === null) {
+      const spawnedWorker = spawnWorker();
+      setWorker(spawnedWorker);
+      return () => {
+        spawnedWorker.terminate();
+      };
+    }
+  }, []);
+
+  function spawnWorker() {
+    return new Worker(new URL("workers/worker.js", import.meta.url), {
+      type: "module",
+    });
+  }
+
+  function postMessagePromise(worker, message) {
+    return new Promise((resolve, reject) => {
+      worker.onmessage = (event) => {
+        resolve(event.data);
+      };
+      worker.onerror = (error) => {
+        reject(error);
+      };
+      worker.postMessage(message);
+    });
+  }
+
+  async function execute() {
+    const hello_hello_program =
+      "program hello_hello.aleo;\n" +
+      "\n" +
+      "function hello:\n" +
+      "    input r0 as u32.public;\n" +
+      "    input r1 as u32.private;\n" +
+      "    add r0 r1 into r2;\n" +
+      "    output r2 as u32.private;\n";
+
+    setLoading(true);
+    const result = await postMessagePromise(worker, {
+      type: "ALEO_EXECUTE_PROGRAM_LOCAL",
+      localProgram: hello_hello_program,
+      aleoFunction: "hello",
+      inputs: ["5u32", "5u32"],
+      privateKey: account.to_string(),
+    });
+    setLoading(false);
+
+    alert(JSON.stringify(result));
+  }
+
+  return (
+    <>
+      <div>
+        <a href="https://aleo.org" target="_blank">
+          <img src={aleoLogo} className="logo" alt="Aleo logo" />
+        </a>
+        <a href="https://react.dev" target="_blank">
+          <img src={reactLogo} className="logo react" alt="React logo" />
+        </a>
+      </div>
+      <h1>Aleo + React</h1>
+      <div className="card">
+        <button onClick={() => setCount((count) => count + 1)}>
+          count is {count}
+        </button>
+        <p>
+          <button onClick={generateAccount}>
+            {account
+              ? `Account is ${JSON.stringify(account.to_string())}`
+              : `Click to generate account`}
+          </button>
+        </p>
+        <p>
+          <button disabled={!account || loading} onClick={execute}>
+            {loading
+              ? `Executing...check console for details...`
+              : `Execute hello_hello.aleo`}
+          </button>
+        </p>
+        <p>
+          Edit <code>src/App.jsx</code> and save to test HMR
+        </p>
+      </div>
+      <p className="read-the-docs">
+        Click on the Aleo and React logos to learn more
+      </p>
+    </>
+  );
+}
+
+export default App;
diff --git a/create-aleo-app/template-react/src/aleo-wasm-hook.js b/create-aleo-app/template-react/src/aleo-wasm-hook.js
new file mode 100644
index 000000000..6daf2ee4e
--- /dev/null
+++ b/create-aleo-app/template-react/src/aleo-wasm-hook.js
@@ -0,0 +1,14 @@
+import { useEffect, useState } from "react";
+import * as sdk from "@aleohq/sdk";
+
+await sdk.initializeWasm();
+export const useAleoWASM = () => {
+    const [aleoInstance, setAleoInstance] = useState(null);
+
+    useEffect(() => {
+        if (aleoInstance === null) {
+            setAleoInstance(sdk);
+        }
+    }, []); // eslint-disable-line react-hooks/exhaustive-deps
+    return aleoInstance;
+};
diff --git a/create-aleo-app/template-react/src/assets/aleo.png b/create-aleo-app/template-react/src/assets/aleo.png
new file mode 100644
index 000000000..89c7035e2
Binary files /dev/null and b/create-aleo-app/template-react/src/assets/aleo.png differ
diff --git a/create-aleo-app/template-react/src/assets/react.svg b/create-aleo-app/template-react/src/assets/react.svg
new file mode 100644
index 000000000..6c87de9bb
--- /dev/null
+++ b/create-aleo-app/template-react/src/assets/react.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
\ No newline at end of file
diff --git a/create-aleo-app/template-react/src/index.css b/create-aleo-app/template-react/src/index.css
new file mode 100644
index 000000000..2c3fac689
--- /dev/null
+++ b/create-aleo-app/template-react/src/index.css
@@ -0,0 +1,69 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-text-size-adjust: 100%;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}
diff --git a/create-aleo-app/template-react/src/main.jsx b/create-aleo-app/template-react/src/main.jsx
new file mode 100644
index 000000000..8b679a24e
--- /dev/null
+++ b/create-aleo-app/template-react/src/main.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App.jsx";
+import "./index.css";
+import WorkerProvider from "./workers/WorkerProvider.jsx";
+
+ReactDOM.createRoot(document.getElementById("root")).render(
+  <React.StrictMode>
+    <WorkerProvider>
+      <App />
+    </WorkerProvider>
+  </React.StrictMode>,
+);
diff --git a/create-aleo-app/template-react/src/workers/WorkerContext.js b/create-aleo-app/template-react/src/workers/WorkerContext.js
new file mode 100644
index 000000000..78df7dfd7
--- /dev/null
+++ b/create-aleo-app/template-react/src/workers/WorkerContext.js
@@ -0,0 +1,5 @@
+import React from "react";
+
+const WorkerContext = React.createContext(null);
+
+export default WorkerContext;
diff --git a/create-aleo-app/template-react/src/workers/WorkerProvider.jsx b/create-aleo-app/template-react/src/workers/WorkerProvider.jsx
new file mode 100644
index 000000000..0cbdee49b
--- /dev/null
+++ b/create-aleo-app/template-react/src/workers/WorkerProvider.jsx
@@ -0,0 +1,42 @@
+import { useEffect, useState } from "react";
+import WorkerContext from "./WorkerContext";
+
+const WorkerProvider = ({ children }) => {
+    const [worker, setWorker] = useState(null);
+    const [workerReady, setWorkerReady] = useState(false);
+
+    useEffect(() => {
+        let worker = new Worker(new URL("./worker.js", import.meta.url), {
+            type: "module",
+        });
+        setWorker(worker);
+
+        worker.onmessage = (event) => {
+            if (event.data.type === "ALEO_WORKER_READY") {
+                setWorkerReady(true);
+            }
+        };
+
+        return () => {
+            worker.terminate();
+        };
+    }, []);
+
+    if (!workerReady) {
+        return (
+            <>
+                <div className="spinner">
+                    <div className="dot1"></div>
+                </div>
+            </>
+        );
+    }
+
+    return (
+        <WorkerContext.Provider value={worker}>
+            {children}
+        </WorkerContext.Provider>
+    );
+};
+
+export default WorkerProvider;
diff --git a/create-aleo-app/template-react/src/workers/keys.js b/create-aleo-app/template-react/src/workers/keys.js
new file mode 100644
index 000000000..136aeda87
--- /dev/null
+++ b/create-aleo-app/template-react/src/workers/keys.js
@@ -0,0 +1,45 @@
+const FEE_PROVER_URL =
+    "https://testnet3.parameters.aleo.org/fee.prover.36542ce";
+const FEE_VERIFIER_URL =
+    "https://testnet3.parameters.aleo.org/fee.verifier.2de311b";
+const JOIN_PROVER_URL =
+    "https://testnet3.parameters.aleo.org/join.prover.da05baf";
+const JOIN_VERIFIER_URL =
+    "https://testnet3.parameters.aleo.org/join.verifier.1489109";
+const SPLIT_PROVER_URL =
+    "https://testnet3.parameters.aleo.org/split.prover.8c585f2";
+const SPLIT_VERIFIER_URL =
+    "https://testnet3.parameters.aleo.org/split.verifier.8281688";
+const TRANSFER_PRIVATE_PROVER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_private.prover.2a9a6f2";
+const TRANSFER_PRIVATE_VERIFIER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_private.verifier.3a59762";
+const TRANSFER_PRIVATE_TO_PUBLIC_PROVER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_private_to_public.prover.cf3b952";
+const TRANSFER_PRIVATE_TO_PUBLIC_VERIFIER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_private_to_public.verifier.5bd459b";
+const TRANSFER_PUBLIC_PROVER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_public.prover.1117f0a";
+const TRANSFER_PUBLIC_VERIFIER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_public.verifier.d63af11";
+const TRANSFER_PUBLIC_TO_PRIVATE_PROVER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_public_to_private.prover.7b763af";
+const TRANSFER_PUBLIC_TO_PRIVATE_VERIFIER_URL =
+    "https://testnet3.parameters.aleo.org/transfer_public_to_private.verifier.25f6542";
+
+export {
+    FEE_PROVER_URL,
+    FEE_VERIFIER_URL,
+    JOIN_PROVER_URL,
+    JOIN_VERIFIER_URL,
+    SPLIT_PROVER_URL,
+    SPLIT_VERIFIER_URL,
+    TRANSFER_PRIVATE_PROVER_URL,
+    TRANSFER_PRIVATE_VERIFIER_URL,
+    TRANSFER_PUBLIC_PROVER_URL,
+    TRANSFER_PUBLIC_VERIFIER_URL,
+    TRANSFER_PRIVATE_TO_PUBLIC_PROVER_URL,
+    TRANSFER_PRIVATE_TO_PUBLIC_VERIFIER_URL,
+    TRANSFER_PUBLIC_TO_PRIVATE_PROVER_URL,
+    TRANSFER_PUBLIC_TO_PRIVATE_VERIFIER_URL,
+};
diff --git a/create-aleo-app/template-react/src/workers/worker.js b/create-aleo-app/template-react/src/workers/worker.js
new file mode 100644
index 000000000..ae0d3aa3b
--- /dev/null
+++ b/create-aleo-app/template-react/src/workers/worker.js
@@ -0,0 +1,484 @@
+import * as aleo from "@aleohq/sdk";
+
+await aleo.initializeWasm();
+await aleo.initThreadPool(10);
+
+const defaultHost = "https://vm.aleo.org/api";
+const keyProvider = new aleo.AleoKeyProvider();
+const programManager = new aleo.ProgramManager(
+  defaultHost,
+  keyProvider,
+  undefined,
+);
+
+keyProvider.useCache(true);
+
+self.postMessage({
+  type: "ALEO_WORKER_READY",
+});
+
+let lastLocalProgram = null;
+self.addEventListener("message", (ev) => {
+  if (ev.data.type === "ALEO_EXECUTE_PROGRAM_LOCAL") {
+    const { localProgram, aleoFunction, inputs, privateKey } = ev.data;
+
+    console.log("Web worker: Executing function locally...");
+    let startTime = performance.now();
+
+    (async function () {
+      try {
+        // Ensure the program is valid and that it contains the function specified
+        const program = programManager.createProgramFromSource(localProgram);
+        const program_id = program.id();
+        if (!program.hasFunction(aleoFunction)) {
+          throw `Program ${program_id} does not contain function ${aleoFunction}`;
+        }
+        const cacheKey = `${program_id}:${aleoFunction}`;
+
+        // Get the program imports
+        const imports =
+          programManager.networkClient.getProgramImports(localProgram);
+
+        // Get the proving and verifying keys for the function
+        if (lastLocalProgram !== localProgram) {
+          const keys = programManager.executionEngine.synthesizeKeypair(
+            localProgram,
+            aleoFunction,
+          );
+          programManager.keyProvider.cacheKeys(cacheKey, [
+            keys.provingKey(),
+            keys.verifyingKey(),
+          ]);
+          lastLocalProgram = localProgram;
+        }
+
+        // Pass the cache key to the execute function
+        const keyParams = new aleo.AleoKeyProviderParams({
+          cacheKey: cacheKey,
+        });
+
+        // Execute the function locally
+        let response = await programManager.executeOffline(
+          localProgram,
+          aleoFunction,
+          inputs,
+          imports,
+          keyParams,
+          undefined,
+          undefined,
+          aleo.PrivateKey.from_string(privateKey),
+        );
+
+        // Return the outputs to the main thread
+        console.log(
+          `Web worker: Local execution completed in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        const outputs = response.getOutputs();
+        console.log(`Function execution response: ${outputs}`);
+        self.postMessage({
+          type: "OFFLINE_EXECUTION_COMPLETED",
+          outputs,
+        });
+      } catch (error) {
+        console.error(error);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      }
+    })();
+  } else if (ev.data.type === "ALEO_EXECUTE_PROGRAM_ON_CHAIN") {
+    const {
+      remoteProgram,
+      aleoFunction,
+      inputs,
+      privateKey,
+      fee,
+      feeRecord,
+      url,
+    } = ev.data;
+
+    console.log("Web worker: Creating execution...");
+    let startTime = performance.now();
+
+    (async function () {
+      try {
+        const privateKeyObject = aleo.PrivateKey.from_string(privateKey);
+        // Ensure the program is valid and that it contains the function specified
+        const program = await programManager.networkClient.getProgramObject(
+          remoteProgram,
+        );
+        const program_id = program.id();
+        if (!program.hasFunction(aleoFunction)) {
+          throw `Program ${program_id} does not contain function ${aleoFunction}`;
+        }
+
+        // Get the proving and verifying keys for the function
+        const cacheKey = `${program_id}:${aleoFunction}`;
+        if (!programManager.keyProvider.containsKeys(cacheKey)) {
+          console.log(
+            `Web worker: Synthesizing proving & verifying keys for: '${program_id}:${aleoFunction}'`,
+          );
+          const keys = programManager.executionEngine.synthesizeKeypair(
+            remoteProgram,
+            aleoFunction,
+          );
+          programManager.keyProvider.cacheKeys(cacheKey, [
+            keys.provingKey(),
+            keys.verifyingKey(),
+          ]);
+        }
+
+        // Pass the cache key to the execute function
+        const keyParams = new aleo.AleoKeyProviderParams({
+          cacheKey: cacheKey,
+        });
+
+        // Set the host to the provided URL if provided
+        if (typeof url === "string") {
+          programManager.setHost(url);
+        }
+        const transaction = await programManager.execute(
+          program_id,
+          aleoFunction,
+          fee,
+          inputs,
+          undefined,
+          keyParams,
+          feeRecord,
+          undefined,
+          undefined,
+          privateKeyObject,
+        );
+
+        // Return the transaction id to the main thread
+        console.log(
+          `Web worker: On-chain execution transaction created in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        self.postMessage({
+          type: "EXECUTION_TRANSACTION_COMPLETED",
+          executeTransaction: transaction,
+        });
+      } catch (error) {
+        console.error(`Error creating execution transaction: ${error}`);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      } finally {
+        programManager.setHost(defaultHost);
+      }
+    })();
+  } else if (ev.data.type === "ALEO_ESTIMATE_EXECUTION_FEE") {
+    const { remoteProgram, aleoFunction, inputs, url } = ev.data;
+
+    console.log("Web worker: Estimating execution fee...");
+    let startTime = performance.now();
+
+    (async function () {
+      try {
+        // Ensure the program is valid and that it contains the function specified
+        const program = await programManager.networkClient.getProgramObject(
+          remoteProgram,
+        );
+        const program_id = program.id();
+        if (!program.getFunctions().includes(aleoFunction)) {
+          throw `Program ${program_id} does not contain function ${aleoFunction}`;
+        }
+        const cacheKey = `${program_id}:${aleoFunction}`;
+        const imports = await programManager.networkClient.getProgramImports(
+          remoteProgram,
+        );
+
+        // Get the proving and verifying keys for the function
+        if (!programManager.keyProvider.containsKeys(cacheKey)) {
+          console.log(
+            `Web worker: Synthesizing proving & verifying keys for: '${program_id}:${aleoFunction}'`,
+          );
+          const keys = programManager.executionEngine.synthesizeKeypair(
+            remoteProgram,
+            aleoFunction,
+          );
+          programManager.keyProvider.cacheKeys(cacheKey, [
+            keys.provingKey(),
+            keys.verifyingKey(),
+          ]);
+        }
+
+        // Estimate the execution fee
+        const [provingKey, verifyingKey] =
+          programManager.keyProvider.getKeys(cacheKey);
+        let executeFee =
+          await programManager.executionEngine.estimateExecutionFee(
+            new aleo.PrivateKey(),
+            remoteProgram,
+            aleoFunction,
+            inputs,
+            url,
+            false,
+            imports,
+            provingKey,
+            verifyingKey,
+          );
+
+        // Return the execution fee estimate to the main thread
+        console.log(
+          `Web worker: Execution fee estimated in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        console.log(`Execution Fee Estimation: ${executeFee} microcrdits`);
+        self.postMessage({
+          type: "EXECUTION_FEE_ESTIMATION_COMPLETED",
+          executionFee: Number(executeFee) / 1000000 + 0.01,
+        });
+      } catch (error) {
+        console.error(error);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      }
+    })();
+  } else if (ev.data.type === "ALEO_ESTIMATE_DEPLOYMENT_FEE") {
+    const { program, url } = ev.data;
+
+    console.log("Web worker: Estimating deployment fee...");
+
+    let startTime = performance.now();
+    (async function () {
+      try {
+        const imports = await programManager.networkClient.getProgramImports(
+          program,
+        );
+        console.log("Estimating deployment fee..");
+        let deploymentFee =
+          await programManager.executionEngine.estimateDeploymentFee(
+            program,
+            false,
+            imports,
+          );
+
+        // Return the deployment fee estimate to the main thread
+        console.log(
+          `Web worker: Deployment fee estimation completed in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        console.log(`Deployment Fee Estimation: ${deploymentFee} microcredits`);
+        self.postMessage({
+          type: "DEPLOYMENT_FEE_ESTIMATION_COMPLETED",
+          deploymentFee: Number(deploymentFee) / 1000000 + 0.01,
+        });
+      } catch (error) {
+        console.error(error);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      }
+    })();
+  } else if (ev.data.type === "ALEO_TRANSFER") {
+    const {
+      privateKey,
+      amountCredits,
+      recipient,
+      transfer_type,
+      amountRecord,
+      fee,
+      feeRecord,
+      url,
+    } = ev.data;
+
+    console.log(`Web worker: Creating transfer of type ${transfer_type}...`);
+    let startTime = performance.now();
+
+    (async function () {
+      try {
+        // Set the host to the provided URL if provided
+        if (typeof url === "string") {
+          programManager.setHost(url);
+        }
+
+        // Create the transfer transaction and submit it to the network
+        const transaction = await programManager.transfer(
+          amountCredits,
+          recipient,
+          transfer_type,
+          fee,
+          undefined,
+          amountRecord,
+          feeRecord,
+          aleo.PrivateKey.from_string(privateKey),
+        );
+
+        // Return the transaction id to the main thread
+        console.log(
+          `Web worker: Transfer transaction ${transaction} created in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        self.postMessage({
+          type: "TRANSFER_TRANSACTION_COMPLETED",
+          transferTransaction: transaction,
+        });
+      } catch (error) {
+        console.error(error);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      } finally {
+        programManager.setHost(defaultHost);
+      }
+    })();
+  } else if (ev.data.type === "ALEO_DEPLOY") {
+    const { program, privateKey, fee, feeRecord, url } = ev.data;
+
+    console.log("Web worker: Creating deployment...");
+
+    let startTime = performance.now();
+    (async function () {
+      try {
+        // Set the network client host if specified
+        if (typeof url === "string") {
+          programManager.setHost(url);
+        }
+
+        // Check if the program is valid
+        const programObject = programManager.createProgramFromSource(program);
+
+        // Check if the program already exists on the network. If so, throw an error
+        let programExists = false;
+        try {
+          await programManager.networkClient.getProgram(programObject.id());
+          programExists = true;
+        } catch (e) {
+          console.log(
+            `Program not found on the Aleo Network - proceeding with deployment...`,
+          );
+        }
+
+        if (programExists) {
+          throw `Program ${programObject.id()} already exists on the network`;
+        }
+
+        console.log("fee is: ", fee);
+        // Create the deployment transaction and submit it to the network
+        let transaction = await programManager.deploy(
+          program,
+          fee,
+          undefined,
+          feeRecord,
+          aleo.PrivateKey.from_string(privateKey),
+        );
+
+        // Return the transaction id to the main thread
+        console.log(
+          `Web worker: Deployment transaction ${transaction} created in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        self.postMessage({
+          type: "DEPLOY_TRANSACTION_COMPLETED",
+          deployTransaction: transaction,
+        });
+      } catch (error) {
+        console.log(error);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      } finally {
+        programManager.setHost(defaultHost);
+      }
+    })();
+  } else if (ev.data.type === "ALEO_SPLIT") {
+    const { splitAmount, record, privateKey, url } = ev.data;
+
+    console.log("Web worker: Creating split...");
+
+    let startTime = performance.now();
+    (async function () {
+      try {
+        // Set the network client host if specified
+        if (typeof url === "string") {
+          programManager.setHost(url);
+        }
+
+        // Create the split transaction and submit to the network
+        const transaction = await programManager.split(
+          splitAmount,
+          record,
+          aleo.PrivateKey.from_string(privateKey),
+        );
+
+        // Return the transaction id to the main thread
+        console.log(
+          `Web worker: Split transaction ${transaction} created in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        self.postMessage({
+          type: "SPLIT_TRANSACTION_COMPLETED",
+          splitTransaction: transaction,
+        });
+      } catch (error) {
+        console.log(error);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      } finally {
+        programManager.setHost(defaultHost);
+      }
+    })();
+  } else if (ev.data.type === "ALEO_JOIN") {
+    const { recordOne, recordTwo, fee, feeRecord, privateKey, url } = ev.data;
+
+    console.log("Web worker: Creating join...");
+
+    let startTime = performance.now();
+    (async function () {
+      try {
+        // Set the network client host if specified
+        if (typeof url === "string") {
+          programManager.setHost(url);
+        }
+
+        // Create the join transaction and submit it to the network
+        const transaction = await programManager.join(
+          recordOne,
+          recordTwo,
+          fee,
+          undefined,
+          feeRecord,
+          aleo.PrivateKey.from_string(privateKey),
+        );
+
+        // Return the transaction id to the main thread
+        console.log(
+          `Web worker: Join transaction ${transaction} created in ${
+            performance.now() - startTime
+          } ms`,
+        );
+        self.postMessage({
+          type: "JOIN_TRANSACTION_COMPLETED",
+          joinTransaction: transaction,
+        });
+      } catch (error) {
+        console.log(error);
+        self.postMessage({
+          type: "ERROR",
+          errorMessage: error.toString(),
+        });
+      } finally {
+        programManager.setHost(defaultHost);
+      }
+    })();
+  }
+});
diff --git a/create-aleo-app/template-react/vite.config.js b/create-aleo-app/template-react/vite.config.js
new file mode 100644
index 000000000..f50911ec4
--- /dev/null
+++ b/create-aleo-app/template-react/vite.config.js
@@ -0,0 +1,16 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [react()],
+  optimizeDeps: {
+    exclude: ["@aleohq/wasm"],
+  },
+  server: {
+    headers: {
+      "Cross-Origin-Opener-Policy": "same-origin",
+      "Cross-Origin-Embedder-Policy": "require-corp",
+    },
+  },
+});
diff --git a/create-aleo-app/template-react/webpack.config.js b/create-aleo-app/template-react/webpack.config.js
new file mode 100644
index 000000000..ea0472dd5
--- /dev/null
+++ b/create-aleo-app/template-react/webpack.config.js
@@ -0,0 +1,90 @@
+import CopyPlugin from "copy-webpack-plugin";
+
+import HtmlWebpackPlugin from "html-webpack-plugin";
+
+import path from "path";
+
+const appConfig = {
+  mode: "production",
+  entry: {
+    index: "./src/main.jsx",
+  },
+  output: {
+    path: path.resolve("dist"),
+    filename: "[name].bundle.js",
+  },
+  resolve: {
+    extensions: [".js", ".wasm", ".jsx"],
+  },
+  devServer: {
+    port: 3000,
+    headers: {
+      "Cross-Origin-Opener-Policy": "same-origin",
+      "Cross-Origin-Embedder-Policy": "require-corp",
+    },
+    client: {
+      overlay: false,
+    },
+  },
+  module: {
+    rules: [
+      {
+        test: /\.(js|jsx)$/,
+        exclude: /nodeModules/,
+        use: {
+          loader: "babel-loader",
+        },
+      },
+      {
+        test: /\.css$/,
+        use: ["style-loader", "css-loader"],
+      },
+      {
+        test: /\.(png|jpe?g|gif)$/i,
+        use: [
+          {
+            loader: "file-loader",
+            options: {
+              name: "[path][name].[ext]",
+            },
+          },
+        ],
+      },
+      {
+        test: /\.svg$/,
+        use: [
+          {
+            loader: "svg-url-loader",
+            options: {
+              limit: 8192,
+              noquotes: true,
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new CopyPlugin({
+      patterns: [{ from: "public", to: "public" }],
+    }),
+    new HtmlWebpackPlugin({
+      template: "./index.html",
+    }),
+  ],
+  performance: {
+    hints: false,
+    maxAssetSize: 13 * 1024 * 1024, // 12 MiB
+    maxEntrypointSize: 13 * 1024 * 1024, // 12 MiB
+  },
+  stats: {
+    warnings: false,
+  },
+  experiments: {
+    asyncWebAssembly: true,
+    topLevelAwait: true,
+  },
+  devtool: "source-map",
+};
+
+export default [appConfig];
diff --git a/create-aleo-app/tsconfig.json b/create-aleo-app/tsconfig.json
new file mode 100644
index 000000000..65fffe745
--- /dev/null
+++ b/create-aleo-app/tsconfig.json
@@ -0,0 +1,15 @@
+{
+  "include": ["build.config.ts", "src", "__tests__"],
+  "compilerOptions": {
+    "outDir": "dist",
+    "target": "ES2020",
+    "module": "ES2020",
+    "moduleResolution": "bundler",
+    "strict": true,
+    "skipLibCheck": true,
+    "declaration": false,
+    "sourceMap": false,
+    "noUnusedLocals": true,
+    "esModuleInterop": true
+  }
+}


Test patch: